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>
-
此时,通过id:userService获取到就是有增强方法的对象。
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