Spring框架AOP学习
说明 :AOP部分:参考网上课程,自己写的总结,如有侵权,联系删除。
目录
SpringAOP_AOP简介

AOP的全称是Aspect Oriented Programming,即面向切面编程。 是实现功能统一维护的一种技术,它将业务逻辑的各个部分进行隔 离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高 了开发效率。
- 作用:在不修改源码的基础上,对已有方法进行增强。
- 实现原理:动态代理技术。
- 优势:减少重复代码、提高开发效率、维护方便
- 应用场景:事务处理、日志管理、权限控制、异常处理等方面。
SpringAOP_AOP相关术语

| 名称 | 说明 |
| Joinpoint(连接点) | 指能被拦截到的点,在Spring中只有方法能被拦截。(路上的车好多) |
| Pointcut(切点) | 指要对哪些连接点进行拦截,即被增强的方法。 (拦住一个车) |
| Advice(通知) | 指拦截后要做的事情,即切点被拦截后执行的方法。 |
| Aspect(切面) | 切点+通知称为切面 |
| Target(目标) | 被代理的对象 |
| Proxy(代理) | 代理对象 |
| Weaving(织入) | 生成代理对象的过程 |
SpringAOP_AOP入门
AspectJ是一个基于Java语言的AOP框架,在Spring框架中建议使用 AspectJ实现AOP。
接下来我们写一个AOP入门案例:dao层的每个方法结束后都可以打印一条日志:
引入依赖
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
编写连接点(就是好多方法,可以切的都是拦截点)
@Repository
public class UserDao {
public void add(){
System.out.println("用户新增");
}
public void delete(){
System.out.println("用户删除");
}
public void update(){
System.out.println("用户修改");
}
}
编写通知类
public class MyAspectJAdvice {
// 后置通知
public void myAfterReturning() {
System.out.println("打印日志...");
}
}
配置切面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.dream"></context:component-scan>
<!-- 通知对象 -->
<bean id="myAspectJAdvice" class="com.dream.advice.MyAspectAdvice"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspectJAdvice">
<!-- 配置切点 -->
<aop:pointcut id="myPointcut" expression="execution(* com.dream.dao.UserDao.*(..))"/>
<!-- 配置通知,这个可以配置前置。。。多个 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
测试
public class UserDaoTest {
@Test
public void testAdd(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.add();
}
@Test
public void testDelete(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.delete();
}
@Test
public void testUpdate(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.update();
}
}
SpringAOP_通知类型
AOP有以下几种常用的通知类型:
| 通知类型 | 描述 |
| 前置通知(before) | 在方法执行前添加功能 |
| 后置通知(after-returning) | 在方法正常执行后添加功能 |
| 异常通知(after-throwing) | 在方法抛出异常后添加功能 |
| 最终通知(after) | 无论方法是否抛出异常,都会执行该通知 |
| 环绕通知 (around) | 在方法执行前后添加功能 |
编写通知方法
// 通知类
public class MyAspectAdvice {
// 后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.println("切点方法名:" +joinPoint.getSignature().getName());
System.out.println("目标对象:" + joinPoint.getTarget());
System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!");
}
// 前置通知
public void myBefore() {
System.out.println("前置通知...");
}
// 异常通知
public void myAfterThrowing(Exception ex) {
System.out.println("异常通知...");
System.err.println(ex.getMessage());
}
// 最终通知
public void myAfter() {
System.out.println("最终通知");
}
// 环绕通知
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前");
Object obj = proceedingJoinPoint.proceed(); // 执行方法
System.out.println("环绕后");
return obj;
}
}
配置切面
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspectJAdvice">
<!-- 配置切点 -->
<aop:pointcut id="myPointcut" expression="execution(* com.dream.dao.UserDao.*(..))"/>
<!-- 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointcut"></aop:before>
<!-- 后置通知 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="ex"/>
<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
<!-- 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
</aop:aspect>
</aop:config>
SpringAOP_切点表达式
使用AspectJ需要使用切点表达式配置切点位置,写法如下:
- 标准写法:访问修饰符 返回值 包名.类名.方法名(参数列表)
- 访问修饰符可以省略。
- 返回值使用 * 代表任意类型。
- 包名使用 * 表示任意包,多级包结构要写多个 * ,使用 *.. 表示任意包结构
- 类名和方法名都可以用 * 实现通配。
- 参数列表
- 基本数据类型直接写类型
- 引用类型写 包名.类名
- * 表示匹配一个任意类型参数
- .. 表示匹配任意类型任意个数的参数
- 全通配: * *..*.*(..)
SpringAOP_多切面配置
我们可以为切点配置多个通知,形成多切面,比如希望dao层的每 个方法结束后都可以打印日志并发送邮件:
添加编写发送邮件的通知:
public class MyAspectJAdvice2 {
// 后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.println("发送邮件");
}
}
配置切面:
<!-- 通知对象 -->
<bean id="myAspectJAdvice" class="com.dream.advice.MyAspectAdvice"></bean>
<bean id="myAspectJAdvice2" class="com.dream.advice.MyAspectAdvice2"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspectJAdvice">
<!-- 配置切点 -->
<aop:pointcut id="myPointcut" expression="execution(* *..*.*(..))"/>
<!-- 后置通知 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
</aop:aspect>
<aop:aspect ref="myAspectJAdvice2">
<aop:pointcut id="myPointcut2" expression="execution(* com.dream.dao.UserDao.*(..))"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut2"/>
</aop:aspect>
</aop:config>
SpringAOP_注解配置AOP
Spring可以使用注解代替配置文件配置切面:
- Spring可以使用注解代替配置文件配置切面:
<!-- 开启注解配置Aop --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> - 在通知类上方加入注解 @Aspect
- 在通知方法上方加入注解@Before/@AfterReturning/@AfterThrowing/@After/@Around
@Aspect @Component public class MyAspectAdvice { // 后置通知 @AfterReturning("execution(* com.dream.dao.UserDao.*(..))") public void myAfterReturning(JoinPoint joinPoint) { System.out.println("切点方法名:" + joinPoint.getSignature().getName()); System.out.println("目标对象:" + joinPoint.getTarget()); System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!"); } // 前置通知 @Before("execution(* com.dream.dao.UserDao.*(..))") public void myBefore() { System.out.println("前置通知..."); } // 异常通知 @AfterThrowing(value = "execution(* com.dream.dao.UserDao.*(..))",throwing = "ex") public void myAfterThrowing(Exception ex) { System.out.println("异常通知..."); System.err.println(ex.getMessage()); } // 最终通知 @After("execution(* com.dream.dao.UserDao.*(..))") public void myAfter() { System.out.println("最终通知"); } // 环绕通知 @Around("execution(* com.dream.dao.UserDao.*(..))") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕前"); Object obj = proceedingJoinPoint.proceed(); // 执行方法 System.out.println("环绕后"); return obj; } }
如何为一个类下的所有方法统一配置切点:
1.在通知类中添加方法配置切点(单独创建一个方法)
@Pointcut("execution(* com.dream.dao.UserDao.*(..))") public void pointCut(){}2.在通知方法上使用定义好的切点
@Before("pointCut()") public void myBefore(JoinPoint joinPoint) { System.out.println("前置通知..."); } @AfterReturning("pointCut()") public void myAfterReturning(JoinPoint joinPoint) { System.out.println("后置通知..."); }
配置类如何代替xml中AOP注解支持?
在配置类上方添加@EnableAspectJAutoProxy即可
@Configuration @ComponentScan("com.dream") @EnableAspectJAutoProxy public class SpringConfig { }
SpringAOP_原生Spring实现AOP
除了AspectJ,Spring支持原生方式实现AOP。原生方式实现AOP只有四种通知类型
- 引入依赖
<!-- 原生AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.13</version> </dependency> - 编写通知类
// Spring原生Aop的通知类 public class SpringAop implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor { /** * 前置通知 * @param method 目标方法 * @param args 目标方法的参数列表 * @param target 目标对象 * @throws Throwable */ @Override public void before(Method method,Object[] args, Object target) throws Throwable { System.out.println("前置通知"); } /** * 后置通知 * @param returnValue 目标方法的返回值 * @param method 目标方法 * @param args 目标方法的参数列表 * @param target 目标对象 * @throws Throwable */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("后置通知"); } /** * 环绕通知 * @param invocation 目标方法 * @return * @throws Throwable */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("环绕前"); Object proceed =invocation.proceed(); System.out.println("环绕后"); return proceed; } /** * 异常通知 * @param ex 异常对象 */ public void afterThrowing(Exception ex){ System.out.println("发生异常了!"); } }Spring原生方式实现AOP时,只支持四种通知类型:
通知类型 实现接口 前置通知 MethodBeforeAdvice 后置通知 AfterReturningAdvice 异常通知 ThrowsAdvice 环绕通知 MethodInterceptor - 编写配置类
<!-- 通知对象 --> <bean id="springAop" class="com.dream.advice.SpringAop"></bean> <!-- 配置代理对象 --> <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 配置目标对象 --> <property name="target" ref="userDao"></property> <!-- 配置通知 --> <property name="interceptorNames"> <list> <value>springAop</value> </list> </property> <!-- 代理对象的生成方式 true:使用CGLib ,false:使用原生JDK生成--> <property name="proxyTargetClass" value="true"></property> </bean> - 测试类
public class UserDaoTest2 { @Test public void testAdd(){ ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml"); UserDao userDao = (UserDao)ac.getBean("userDaoProxy"); // 获取的是代理对象 userDao.update(); } }
测试的时候报错:就是不知道啥情况呢哎*********************
java.lang.ClassCastException: class com.sun.proxy.$Proxy27 cannot be cast to class com.dream.dao.UserDao (com.sun.proxy.$Proxy27 and com.dream.dao.UserDao are in unnamed module of loader 'app')
SpringAOP_SchemaBased实现AOP
SchemaBased(基础模式)配置方式是指使用Spring原生方式定义通知,而使用AspectJ框架配置切面。
- 编写通知类
public class SpringAop implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor { /** * 前置通知 * @param method 目标方法 * @param args 目标方法的参数列表 * @param target 目标对象 * @throws Throwable */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("前置通知"); } /** * 后置通知 * @param returnValue 目标方法的返回值 * @param method 目标方法 * @param args 目标方法的参数列表 * @param target 目标对象 * @throws Throwable */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("后置通知"); } /** * 环绕通知 * @param invocation 目标方法 * @return * @throws Throwable */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("环绕前"); Object proceed =invocation.proceed(); System.out.println("环绕后"); return proceed; } /** * 异常通知 * @param ex 异常对象 */ public void afterThrowing(Exception ex){ System.out.println("发生异常了!"); } } -
配置切面
<!-- 通知对象 --> <bean id="springAop2" class="com.dream.aop.SpringAop2"/> <!-- 配置切面 --> <aop:config> <!-- 配置切点--> <aop:pointcut id="myPointcut" expression="execution(* com.dream.dao.UserDao.*(..))"/> <!-- 配置切面:advice-ref:通知对象 pointcut-ref:切点 --> <aop:advisor advice-ref="springAop2" pointcut-ref="myPointcut"/> </aop:config>
这个是可以的,感觉第三种也不太好用