三,Spring AOP
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
Spring AOP
1 代理设计模式
1.1 解决的问题
Service层中除了核心代码(调用dao+逻辑判断外还有事务控制这种额外的增强功能。现在我们将核心业务代码和事务控制增强编码在一起直接定义在service层日后还可能会新增其它的额外功能要求比如日志记录、性能分析。那么Service层就会变得特别臃肿更致命的是添加新的增强功能就要修改原有代码。
那么到底要不要在Service中添加额外功能?
-
软件设计者不要定义额外功能代码后会造成代码的频繁修改不利于维护。
-
功能调用方(Controller: 要调用者需要使用到这些额外功能。
有没有同时调和2者矛盾的解决方案呢?答案就是代理设计模式。
1.2 代理模式
生活中房东有房源提供租房的核心功能但是房东不愿意提供 广告、带看房 等功能因为麻烦。而房客必须要使用 广告、随时看房 的功能。
矛盾的解决方案中介
中介代理房东的出租方法同时提供额外功能。租客不再和房东打交道而是和中介打交道。这样不需要房东直接提供额外服务而租客也能享受到租房该有的额外服务。
1.3 静态代理模式
Service和Controller的矛盾也可以通过添加一个中介类(代理类解决。代理类代理Service的原始方法同时提供额外功能。
通过代理类为原始类添加额外功能好处避免原始类因为额外功能而频繁修改提高程序的可维护性。
示例
接口
public interface UserService {
public boolean login(String username, String password);
public void removeUser(Integer id);
}
原始类
public class UserServiceImpl implements UserService {
@Override
public boolean login(String username, String password) {
System.out.println("username = [" + username + "], password = [" + password + "]");
System.out.println("登录成功");
return false;
}
@Override
public void removeUser(Integer id) {
System.out.println("删除成功id="+id);
}
}
代理类
public class UserServiceProxy implements UserService {
private UserService service ;
public UserServiceProxy(UserService service){
this.service = service;
}
@Override
public boolean login(String username, String password) {
System.out.println("记录日志");
return service.login(username,password);
}
@Override
public void removeUser(Integer id) {
System.out.println("记录日志");
service.removeUser(id);
}
}
Controller:
public class RemoveUserController extends HttpServlet {
public void service(HttpServletRequest req,HttpServletResponse resp){
//收参
String idStr = req.getParameter("id");
Integer id = Integer.parseInt(idStr);
//调用业务层
UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.removeUser(id);
//跳转
...
}
}
名词(术语解释
- 原始类(目标类提供核心功能的类
- 原始方法(目标方法提供核心功能的方法原始类中定义的方法
- 额外功能(增强处理用于增强原始方法功能的代码
4个注意点
- 代理类和原始类要实现相同接口
- 代理类提供额外功能
- 代理类中调用目标方法
- 调用者只和代理类打交道不再和原始类型直接建立联系
2 Spring 动态代理【重点】
静态代理的问题
- 随着额外功能的增多代理类数量过多不利于管理
- 代理类存在冗余会出现多个代理类为不同的原始类提供同1个功能。
开发时静态代理没有价值不会使用。
解决方案Spring的动态代理
Spring动态代理代理类不需要程序员手动编码由Spring框架动态生成增强功能的代理类。
好处提高开发效率降低额外功能的冗余。
2.1 开发步骤
准备工作项目中导入spring-aop aspectjweaver
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
-
创建原始对象
<bean id="userService" class="com.bz.service.impl.UserServiceImpl"/>
-
定义额外功能定义增强类实现Spring内置的接口
public class MyBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("记录日志"); } }
-
配置自定义的增强类
<!-- 配置增强类将额外功能交由Spring管理--> <bean id="beforeAdvice" class="com.bz.advice.MyBeforeAdvice"/>
-
定义切入点(pointcut决定了额外功能的添加位置
-
组装
<aop:config> <!-- 定义切入点额外功能添加的位置 --> <aop:pointcut id="servicePointCut" expression="execution(* com.bz.service..*.*(..))"/> <!-- 组装 --> <aop:advisor advice-ref="beforeAdvice" pointcut-ref="servicePointCut"/> </aop:config>
-
此时通过iduserService获取到就是有增强方法的对象。
2.2 实现的设计原理
3 增强(advice
Advice(通知、增强为目标方法添加的额外功能。根据增强(额外功能添加的位置可以分成前置增强、后置增强、异常增强以及环绕增强。
3.1 前置增强
前置增强增强是在目标方法前执行实现MethodBeforeAdvice.
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
/**
* method: 原始方法(目标方法
* args原始方法执行时实参列表
* target原始对象
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("method = " + method);
System.out.println("args = " + args);
System.out.println("target = " + target);
System.out.println("记录日志");
}
}
3.2 后置增强
后置增强增强在目标方法后执行,必须实现AfterReturningAdvice
public class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
/**
* returnValue: 目标方法的返回值
* method: 目标方法
* args调用目标方法的实参列表
* target: 原始对象
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置增强");
}
}
3.3 异常增强
异常增强增强在目标方法发生异常时执行必须实现ThrowsAdvice。
注意ThrowsAdvice是一个标记接口实现的方法要从源码中copy
/* <p>Some examples of valid methods would be: * <pre class="code">public void afterThrowing(Exception ex)</pre> * <pre class="code">public void afterThrowing(RemoteException)</pre> * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre> * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre> */
public class MyThrowsAdvice implements ThrowsAdvice {
/**
*
* @param method 目标方法
* @param args 目标方法调用时的实参列表
* @param target 原始对象
* @param ex 目标方法执行时的异常
*/
public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
System.out.println("异常增强");
}
}
3.4 环绕增强【重点】
环绕增强在目标方法前、后以及发生异常时执行的增强必须实现org.aopalliance.intercept.MethodInterceptor。
注意接口所在的包。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
/*
MethodInvocation: 方法调用者
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前置增强");
invocation.getMethod();//获取原始方法
invocation.getArguments();//获取原始方法调用的实参列表
invocation.getThis();//获取原始对象
Object result = null;
try {
//放行流程执行目标方法
result = invocation.proceed();
System.out.println("后置增强");
}catch(Exception e){
e.printStackTrace();
System.out.println("异常增强");
}finally {
System.out.println("最终增强");
}
return result;
}
}
使用环绕增强可以代替前置、后置以及异常增强。
4 切入点表达式
切入点(切点需要添加额外功能(增强、增强处理的方法的位置通过pointcut标签定义切点。
4.1 execution表达式【重点】
execution表达式通过表达式可以定义到方法级别。
实战通常给service添加增强(额外功能
<aop:pointcut id="servicePointCut" expression="execution(* com.bz.service..*.*(..))"/>
4.2 args表达式
args表达式根据形参列表进行匹配.
args(参数表达式)
具体语法细节同execution表达式参数部分一样。
<aop:pointcut id="servicePointCut" expression="args(..)"/>
4.3 within表达式
within表达式根据全类名进行匹配
语法细节和execution表达式包名和类名部分相同注意使用within必须要精确到实现类。
<aop:pointcut id="servicePointCut" expression="within(com.bz.service.impl.*)"/>
4.4 @annotation表达式
@annotation(注解全类名)匹配所有使用特定注解描述的方法。
-
自定义注解
//注解是特殊的接口 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface MyAnnotation { // public String name(); 注解中属性和方法是一体的 }
-
使用注解描述方法,注意方法不能是抽象方法
public class UserServiceImpl implements UserService { @Override @MyAnnotation public boolean login(String username, String password) { System.out.println("username = [" + username + "], password = [" + password + "]"); System.out.println("登录成功"); return false; } @MyAnnotation @Override public void removeUser(Integer id) { System.out.println("删除成功id="+id); int i = 10/0; } }
-
@annotation(注解全类名)
<aop:pointcut id="servicePointCut" expression="@annotation(com.bz.annotation.MyAnnotation)"/>
4.5 表达式的运算符
切点表达式之间可以进行运算&&(and) ||(or) !(not)
&& (and)求交集
|| (or)求并集
! (not)取反
由于&在xml有特殊含义建议使用相同作用的关键字and
<aop:pointcut id="servicePointcut" expression="@annotation(com.bz.annotation.MyAnnotation) and args(java.lang.Integer,..)"/>
<aop:pointcut id="servicePointcut" expression="!args(java.lang.Integer,..)"/>
5 Spring的AOP【重点】
AOP(Aspect Oriented Programming面向切面编程。
OOP(Object Oriented Programming面向对象编程。
AOP解决共性功能的抽取以及重用配合OOP更好的完成编程。
5.1 OOP下共性功能的抽取重用
OOP以对象为基本编程对象通过对象间的协调、配合完成功能。面向对象中通过继承抽取共性以及重用共性功能。
5.2 Spring AOP
AOP的基础概念
- 增强(增强共性的额外功能比如日志、事务、性能分析
- 切入点(切点添加额外功能的位置
- 织入(编织将增强(增强添加到切点的过程
- 切面增强在切点位置置入后形成的一个几何概念
AOP面向切面编程
以横切的思想在程序运行过程中动态的将额外功能添加到切点处。好处灵活、强大、不需要修改原始的目标类。
AOP是在OOP基础上完成对OOP的补充。
Spring AOP开发步骤
- 配置原始对象
- 编码定义增强类
- 配置增强类
- 定义切入点
- 组装切面
Spring AOP的应用场景
- 在不修改源码的基础上动态的添加功能
- service层日志、事务、性能监控
6 Spring中的事务控制
事务用来保证业务操作完整性的一种数据库机制。
添加位置在业务层中进行事务控制业务层中一个业务方法表示一个完成的功能。
6.1 事务复习
JDBC中事务控制
conn.setAutoCommit(false);
业务逻辑+调用dao
conn.commmit();//成功
conn.rollback();//失败
MyBaits中事务控制
//mybatis默认禁用自动提交
业务逻辑+调用dao
sqlSession.commit();//成功
sqlSession.rollback();//失败
6.2 Spring事务控制
Spring提供了2种事务控制方式
- 编程式事务控制在代码中定义事务控制的代码不常用。
- 声明式事务控制借助Spring AOP实现将事务控制的代码定义成增强(增强;通过切入点将增强编织到service方法中。
Spring AOP方式事务控制的思路
添加依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
Spring AOP方式事务控制的步骤
-
定义原始对象
<!-- 定义service对象--> <bean id="userService" class="com.bz.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean>
-
定义增强类(事务控制
Spring内置DataSourceTransactionManager
-
配置DataSourceTransactionManager增强类
<!-- 配置事务管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"/> </bean> <!-- 配置事务增强(增强--> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- 进一步配置方法的事务控制细节 --> <tx:attributes> <!-- 所有show开头的方法添加只读的事务 --> <tx:method name="show*" read-only="true"/> <!-- show开头的方法外的其他所有方法开启事务 --> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
注意tx:advice是http://www.springframework.org/schema/tx定义的标签。
-
定义切点
-
编织切面
<aop:config> <aop:pointcut id="servicePointcut" expression="execution(* com.bz.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/> </aop:config>
7 总结SM项目开发步骤
-
搭建开发环境
-
新建web项目(补全项目结构)
-
导入依赖pom.xml引入依赖
数据库依赖
mysql-connector-java.jar
druid
spring依赖
spring-context
aspectjweaver
mybatis依赖:
mybatis
slf4j-log4j12
mybatis和spring整合
spring-jdbc
mybatis-spring
servlet+jsp+jstl依赖
servlet-api
jsp-api
jstl
springmvc依赖
spring-webmvc
hutool工具
hutool-all
-
配置文件和工具类
jdbc.properties
lo4j.properties
mybatis-config.xml(不再需要)xxxMapper.xml
web.xml
applicationContext.xml
MyBatisUtils.java(不再需要) -
配置文件初始化
web.xml中配置Spring监听器创建Spring工厂
-
-
建表
-
实体
-
dao
- 接口
- 实现: mapper.xml中定义sql语句
-
service
- 接口
- 实现不再编程式的管理事务
-
test
-
Controller+jsp
-
集成测试
pom.xml
<!-- jdbc依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- 阿里巴巴连接池依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.24</version>
</dependency>
<!--引入Spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<!-- spring 整合 mybatis 依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<!-- servlet jsp jstl 依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/taglibs/standard -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!-- SpringMVC依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!--
hutool工具类
-->
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.2.3</version>
</dependency>
<!-- junit测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.12</version>
</dependency>
web.xml
<!-- 配置spring配置文件的路径-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 监听器监听web应用启动根据上面配置的spring配置文件路径创建Spring工厂-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
applicationContext.xml
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 创建连接池 DataSource -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 必须的配置 -->
<property name="url" value="${url}"/>
<property name="driverClassName" value="${driverClassName}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
<!-- 额外的配置-->
</bean>
<!-- 定义SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="druidDataSource"/>
<!--
配置实体类的包名自动为实体配置短类名的别名
-->
<property name="typeAliasesPackage" value="com.bz.entity"/>
<property name="mapperLocations">
<!-- 配置mapper.xml的路径-->
<list>
<value>classpath:com/bz/mapper/*Mapper.xml</value>
</list>
</property>
</bean>
<!--
自动创建Mapper实现类对象
自动扫描basePackage包下的Mapper接口自动创建Mapper接口的实现类对象
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--
mapper实现类对象的id规则接口名首字母小写
UserMapper ==> userMapper
BookMapper ==> bookMapper
-->
<property name="basePackage" value="com.bz.mapper"/>
</bean>
<!-- 定义service对象-->
<bean id="userService" class="com.bz.service.impl.UserServiceImpl">
<property name="userMapper" ref="userMapper"/>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!-- 配置事务增强-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 所有show开头的方法添加只读的事务 -->
<tx:method name="show*" read-only="true"/>
<!-- show开头的方法外的其他所有方法开启事务 -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.bz.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
源代码地址如下:https://download.csdn.net/download/qq_36827283/87383453