SpringAOP深入学习
----------------------Spring AOP介绍------------------
1.编程范式概念
面向过程编程:C
面向对象编程:c++,Java
函数式编程
事件驱动编程:GUI编程
面向切面编程(AOP)
2.AOP是什么
(1)是一种编程范式,不是编程语言
(2)解决特定问题,不能解决所有问题
(3是OOP的补充,不是替代。
3.AOP初衷:
1.解决代码重复问题,增加代码的可读性与可维护性
2.关注点分离,使程序员可以将更多的精力放在开发主要功能中。
4.SpringAOP的两种实现方式
1.基于xml配置的方式
2.基于注解的方式(重点)
5.AOP中名词:
Aspect:切面(切入点+通知)
join point:连接点,所有可以织入通知的方法
point cut:切入点,需要|已经织入通知的方法
advice:通知。需要增强的代码
weaving:织入,动词,将通知应用到切点的过程
target:目标对象
proxy:代理对象
注解AOP:
表达式:
- 选择器类型
- wildcards通配符:
* 匹配任意数量的字符
+ 匹配定指定类及其子类
.. 一般用于匹配任意数的子包或参数
- operators:运算符
&& 与操作符
|| 或操作符
! 非操作符
6.将通知织入切点的时机
1.编译时期(Aspect)
2.类加载时期(Aspect5+)
3.运行时期(Spring AOP)
从静态代理到动态代理(基于接口的代理与基于继承的代理)。
---------------------------Spring AOP实战--------------------
1.基于xml配置方式的AOP实现
1.编写通知类:(通过反射精确的定位到每个方法可以做一些详细的处理,两种方式定位到方法)
package cn.qlq.XMLaspect; import java.lang.reflect.Method; import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; //xml切面 public class MyAdvice { // 前置通知 public void before(JoinPoint joinPoint) throws Exception { System.out.println("---------------前置通知开始~~~~~~~~~~~"); // 获取到类名 String targetName = joinPoint.getTarget().getClass().getName(); System.out.println("代理的类是:" + targetName); // 获取到方法名 String methodName = joinPoint.getSignature().getName(); System.out.println("增强的方法是:" + methodName); // 获取到参数 Object[] parameter = joinPoint.getArgs(); System.out.println("传入的参数是:" + Arrays.toString(parameter)); // 获取字节码对象 Class<?> targetClass = Class.forName(targetName); // 获取所有的方法 Method[] methods = targetClass.getMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == parameter.length) { System.out.println("找到这个方法"); //处理一些业务逻辑 break; } } } System.out.println("---------------前置通知结束~~~~~~~~~~~"); } // 后置通知(异常发生后不会调用) public void afterRunning() { System.out.println("这是后置通知(异常发生后不会调用)"); } // 环绕通知(推荐下面这种方式获取方法) public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("----------------环绕通知之前 的部分----------------"); // 获取到类名 String targetName = pjp.getTarget().getClass().getName(); System.out.println("代理的类是:" + targetName); // 获取到参数 Object[] parameter = pjp.getArgs(); System.out.println("传入的参数是:" + Arrays.toString(parameter)); // 获取到方法签名,进而获得方法 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); System.out.println("增强的方法名字是:" + method.getName()); //处理一些业务逻辑 // 获取参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); System.out.println("参数类型是:" + parameterTypes.toString()); //让方法执行(proceed是方法的返回结果,可以针对返回结果处理一下事情) System.out.println("--------------方法开始执行-----------------"); Object proceed = pjp.proceed(); //环绕通知之后的业务逻辑部分 System.out.println("----------------环绕通知之后的部分----------------"); return proceed; } // 异常通知 public void afterException() { System.out.println("这是异常通知(发生异常后调用)~~~~~~~~~~~"); } // 最终通知(发生异常也会在最终调用) public void after() { System.out.println("这是后置通知(发生异常也会在最终调用)"); } }
2.编写service接口和实现类(需要被增强的类)
- UserService.java
package cn.qlq.service; import java.sql.SQLException; public interface UserService { void save(); void save(String userId)throws Exception; void update(); void delete(); void find(); }
- UserServiceImpl.java
package cn.qlq.service; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Override public void save() { System.out.println("保存用户。。。无参数"); } @Override public void save(String userId) throws Exception { int i = 1 / 0; System.out.println("保存用户.....(有参数)"); } @Override public void update() { System.out.println("更新用户...."); } @Override public void delete() { System.out.println("删除用户...."); } @Override public void find() { System.out.println("查找用户"); } }
3.xml配置切入点+通知形成切面
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd "> <!-- 1.首先导入aop约束,在beans元素添加命名空间 --> <context:component-scan base-package="cn.qlq.service"></context:component-scan> <!-- 3.配置通知对象 --> <bean name="advice" class="cn.qlq.XMLaspect.MyAdvice"></bean> <!-- 4.通知对象织入目标对象 .expression:切入点表达式。id:切入点名字 public void cn.qlq.service.UserServiceImpl.save() 对指定类型指定返回值的空参方法进行增强 void cn.qlq.service.UserServiceImpl.save() public可以省去,默认public * cn.qlq.service.UserServiceImpl.save() 对返回类型不做要求,可以对任何返回类型织入 * cn.qlq.service.UserServiceImpl.*() 对返回类型不做要求,对方法名字不做要求 * cn.qlq.service.UserServiceImpl.*(..) 对返回类型不做要求,对方法名字不做要求,对参数也不做要求 * cn.qlq.service.*ServiceImpl.*(..) 对service包下所有以ServiceImpl结尾的类中的任意参数的任意方法增强 * cn.qlq.service..*ServiceImpl.*(..) 与上面不同的是对service包的子包也要进行增强(一般不用) --> <aop:config> <!-- 配置切点 --> <aop:pointcut expression="execution(* cn.qlq.service.*ServiceImpl.*(..))" id="pc"/> <!-- 将通知织入切点形成切面 --> <aop:aspect ref="advice"> <aop:before method="before" pointcut-ref="pc"/> <aop:after-returning method="afterRunning" pointcut-ref="pc"/> <aop:after method="after" pointcut-ref="pc"/> <aop:around method="around" pointcut-ref="pc"/> <aop:after-throwing method="afterException" pointcut-ref="pc"/> </aop:aspect> </aop:config> </beans>
4.编写测试类:
package cn.qlq.test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import cn.qlq.service.UserService; //帮我们创建容器 @RunWith(SpringJUnit4ClassRunner.class) //指定创建容器时使用的配置文件 @ContextConfiguration("classpath:cn/qlq/XMLaspect/applicationContext.xml") public class SpringXMLAopTest { @Resource(name="userService") private UserService us; @Test public void fun1() throws Exception{ us.save("111"); } }
结果:
---------------前置通知开始~~~~~~~~~~~
代理的类是:cn.qlq.service.UserServiceImpl
增强的方法是:save
传入的参数是:[111]
找到这个方法
---------------前置通知结束~~~~~~~~~~~
----------------环绕通知之前 的部分----------------
代理的类是:cn.qlq.service.UserServiceImpl
传入的参数是:[111]
增强的方法名字是:save
参数类型是:[Ljava.lang.Class;@5eebd82d
--------------方法开始执行-----------------
保存用户.....(有参数)
这是后置通知(异常发生后不会调用)
这是后置通知(发生异常也会在最终调用)
----------------环绕通知之后的部分----------------
现在在程序中抛出一个异常,查看执行结果
@Override public void save(String userId) throws Exception { int i = 1 / 0; System.out.println("保存用户.....(有参数)"); }
结果:
---------------前置通知开始~~~~~~~~~~~ 代理的类是:cn.qlq.service.UserServiceImpl 增强的方法是:save 传入的参数是:[111] 找到这个方法 ---------------前置通知结束~~~~~~~~~~~ ----------------环绕通知之前 的部分---------------- 代理的类是:cn.qlq.service.UserServiceImpl 传入的参数是:[111] 增强的方法名字是:save 参数类型是:[Ljava.lang.Class;@7022c24e --------------方法开始执行----------------- 这是后置通知(发生异常也会在最终调用) 这是异常通知(发生异常后调用)~~~~~~~~~~~
通知执行顺序:
(1)没有异常(不会走异常通知)
前置通知->环绕通知之前部分->后置通知(异常不调用)->最终通知(异常也会调用)->环绕通知之后的部分
(2)有异常(不会走环绕通知的后半部分和后置通知)
前置通知->环绕通知之前部分->最终通知(异常也会调用)->异常通知
2.基于注解方式的AOP实现(重点)
1.applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd "> <!-- 1.开启注解AOP --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 2.配置扫描的包 --> <context:component-scan base-package="cn.qlq"></context:component-scan> </beans>
2.通知类:
MyAdvice.java
package cn.qlq.annotationAOP; import java.lang.reflect.Method; import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; @Aspect // 表示该类是一个通知类 @Component // 交给spring管理 public class MyAdvice { // 定义一个空方法,借用其注解抽取切点表达式 @Pointcut("execution(* cn.qlq.service.*ServiceImpl.*(..))") public void pc() { } // 前置通知 @Before("MyAdvice.pc()") public void before(JoinPoint joinPoint) throws Exception { System.out.println("---------------前置通知开始(注解)~~~~~~~~~~~"); // 获取到类名 String targetName = joinPoint.getTarget().getClass().getName(); System.out.println("代理的类是:" + targetName); // 获取到方法名 String methodName = joinPoint.getSignature().getName(); System.out.println("增强的方法是:" + methodName); // 获取到参数 Object[] parameter = joinPoint.getArgs(); System.out.println("传入的参数是:" + Arrays.toString(parameter)); // 获取字节码对象 Class<?> targetClass = Class.forName(targetName); // 获取所有的方法 Method[] methods = targetClass.getMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == parameter.length) { System.out.println("找到这个方法"); break; } } } System.out.println("---------------前置通知结束(注解)~~~~~~~~~~~"); } // 后置通知(异常发生后不会调用) @AfterReturning("MyAdvice.pc()") public void afterRunning() { System.out.println("这是后置通知(异常发生后不会调用)。。。。(注解)"); } // 环绕通知 @Around("MyAdvice.pc()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("----------------环绕通知之前 的部分(注解)----------------"); // 获取到类名 String targetName = pjp.getTarget().getClass().getName(); System.out.println("代理的类是:" + targetName); // 获取到参数 Object[] parameter = pjp.getArgs(); System.out.println("传入的参数是:" + Arrays.toString(parameter)); // 获取到方法签名,进而获得方法 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); System.out.println("增强的方法名字是:" + method.getName()); // 获取参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); System.out.println("参数类型是:" + parameterTypes.toString()); //让方法执行(proceed是拦截的方法的执行返回值,可以针对返回值做一些处理) System.out.println("--------------方法开始执行-----------------"); Object proceed = pjp.proceed(); //环绕通知之后的业务逻辑部分 System.out.println("----------------环绕通知之后的部分(注解)----------------"); return proceed; } // 异常通知 @AfterThrowing("MyAdvice.pc()") public void afterException() { System.out.println("这是异常通知(发生异常后调用)~~~~~~~~~~~(注解)"); } // 最终通知(发生异常也会在最终调用) @After("MyAdvice.pc()") public void after() { System.out.println("这是最终通知(发生异常也会在最终调用)........(注解)"); } }
3.UserService.java与UserServiceImpl.java同上
4.测试类:
package cn.qlq.test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import cn.qlq.service.UserService; //帮我们创建容器 @RunWith(SpringJUnit4ClassRunner.class) //指定创建容器时使用的配置文件 @ContextConfiguration("classpath:cn/qlq/annotationAOP/applicationContext.xml") public class SpringAnnotationAopTest { @Resource(name="userService") private UserService us; @Test public void fun1(){ us.save("111"); } }
结果:
----------------环绕通知之前 的部分(注解)---------------- 代理的类是:cn.qlq.service.UserServiceImpl 传入的参数是:[111] 增强的方法名字是:save 参数类型是:[Ljava.lang.Class;@61d60df3 --------------方法开始执行(注解)------------------ ---------------前置通知开始(注解)~~~~~~~~~~~ 代理的类是:cn.qlq.service.UserServiceImpl 增强的方法是:save 传入的参数是:[111] 找到这个方法 ---------------前置通知结束(注解)~~~~~~~~~~~ 保存用户.....(有参数) ----------------环绕通知之后的部分(注解)----------------- 这是最终通知(发生异常也会在最终调用)........(注解) 这是后置通知(异常发生后不会调用)。。。。(注解)
注意,为了抽取切点表达式,我们将上面的通知类改为如下:
package cn.qlq.annotationAOP; import java.lang.reflect.Method; import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; @Aspect // 表示该类是一个通知类 @Component // 交给spring管理 public class MyAdvice { // 定义一个空方法,借用其注解抽取切点表达式 @Pointcut("execution(* cn.qlq.service.*ServiceImpl.*(..))") public void pc() { } // 前置通知(下面两种方式均可以) // @Before("MyAdvice.pc()") @Before("pc()") public void before(JoinPoint joinPoint) throws Exception { System.out.println("---------------前置通知开始(注解)~~~~~~~~~~~"); // 获取到类名 String targetName = joinPoint.getTarget().getClass().getName(); System.out.println("代理的类是:" + targetName); // 获取到方法名 String methodName = joinPoint.getSignature().getName(); System.out.println("增强的方法是:" + methodName); // 获取到参数 Object[] parameter = joinPoint.getArgs(); System.out.println("传入的参数是:" + Arrays.toString(parameter)); // 获取字节码对象 Class<?> targetClass = Class.forName(targetName); // 获取所有的方法 Method[] methods = targetClass.getMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == parameter.length) { System.out.println("找到这个方法"); break; } } } System.out.println("---------------前置通知结束(注解)~~~~~~~~~~~"); } // 后置通知(异常发生后不会调用) @AfterReturning("MyAdvice.pc()") public void afterRunning() { System.out.println("这是后置通知(异常发生后不会调用)。。。。(注解)"); } // 环绕通知 @Around("MyAdvice.pc()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("----------------环绕通知之前 的部分(注解)----------------"); // 获取到类名 String targetName = pjp.getTarget().getClass().getName(); System.out.println("代理的类是:" + targetName); // 获取到参数 Object[] parameter = pjp.getArgs(); System.out.println("传入的参数是:" + Arrays.toString(parameter)); // 获取到方法签名,进而获得方法 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); System.out.println("增强的方法名字是:" + method.getName()); // 处理一些业务逻辑 // 获取参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); System.out.println("参数类型是:" + parameterTypes.toString()); // 让方法执行(proceed是方法的返回结果,可以针对返回结果做一些处理) System.out.println("--------------方法开始执行(注解)------------------"); Object proceed = pjp.proceed(); // 环绕通知之后的业务逻辑部分 System.out.println("----------------环绕通知之后的部分(注解)-----------------"); return proceed; } // 异常通知 @AfterThrowing("MyAdvice.pc()") public void afterException() { System.out.println("这是异常通知(发生异常后调用)~~~~~~~~~~~(注解)"); } // 最终通知(发生异常也会在最终调用) @After("MyAdvice.pc()") public void after() { System.out.println("这是最终通知(发生异常也会在最终调用)........(注解)"); } }
现在serviceImpl模拟抛出异常:
@Override public void save(String userId) throws Exception { int i = 1 / 0; System.out.println("保存用户.....(有参数)"); }
结果:
----------------环绕通知之前 的部分(注解)---------------- 代理的类是:cn.qlq.service.UserServiceImpl 传入的参数是:[111] 增强的方法名字是:save 参数类型是:[Ljava.lang.Class;@54b2a02f --------------方法开始执行(注解)------------------ ---------------前置通知开始(注解)~~~~~~~~~~~ 代理的类是:cn.qlq.service.UserServiceImpl 增强的方法是:save 传入的参数是:[111] 找到这个方法 ---------------前置通知结束(注解)~~~~~~~~~~~ 这是最终通知(发生异常也会在最终调用)........(注解) 这是异常通知(发生异常后调用)~~~~~~~~~~~(注解)
总结:
1.注解解释
@Aspect:指明当前类是通知类
@Before:前置通知
@After:最终通知,发生异常也会调用,方法执行完之后执行
@AfterReturning:后置通知(异常发生不会调用),方法成功执行之后。
@Around:环绕通知
@AfterThrowing:异常通知,抛出异常之后
@Pointcut:抽取切点表达式,便于修改切点表达式
2.通知执行顺序(顺序和xml方式有点不一样)
没异常:(不会执行异常通知)
环绕通知之前部分->前置通知->环绕通知之后部分->最终通知->后置通知
有异常(不会执行后置通知和环绕通知后半部分)
环绕通知之前部分->前置通知->最终通知->异常通知
3.注解AOP几种切点表达式选择的使用:
1.within("xxx")表达式
// 匹配UserServiceImpl下面的所有方法 @Pointcut("within(cn.qlq.service.UserServiceImpl)") public void pc() { }
// 匹配cn.qlq包及其子包下面所有类的所有方法 @Pointcut("within(cn.qlq..*)") public void pc() { }
2.对象匹配:
-
1.this
// 匹配AOP对象的目标对象为指定类型的方法,即cn.qlq.service.UserService的AOP代理对象的方法 @Pointcut("target(cn.qlq.service.UserService)") public void pc() { }
-
2.target
// 匹配实现UserService的目标对象,而不是代理对象,这里就是UserService的方法 @Pointcut("target(cn.qlq.service.UserService)") public void pc() { }
-
3.bean
// 匹配注入到spring中所有以Service结尾的类的方法 @Pointcut("bean(*Service)") public void pc() { }
3.参数匹配
// 匹配所有以方法名save开头且只有一个String类型参数的方法 @Pointcut("execution(* *..save*(String))") public void pc() { }
// 匹配所有只有一个String类型参数的方法 @Pointcut("args(String)") public void pc() { }
// 匹配所有以方法名save开头且第一个参数类型为String类型的方法 @Pointcut("args(* *..save*(String,..))") public void pc() { }
// 匹配第一个参数类型为String类型的方法 @Pointcut("args(String,..)") public void pc() { }
4.注解匹配:
(1)自定义注解:
package cn.qlq.annotationAOP; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno { // 定义注解的属性,这不是方法,没有默认值就是必填属性 String name(); // 有默认值,就是可选属性 int value() default 20; }
(2)方法声明加注解
@MyAnno(name="自定义注解") @Override public void save(String userId) throws Exception { System.out.println("保存用户.....(有参数)"); }
(3)利用注解表达式匹配上面表达式
- 匹配方法的注解
// 匹配方法标有cn.qlq.annotationAOP.MyAnno注解的方法 @Pointcut("@annotation(cn.qlq.annotationAOP.MyAnno)") public void pc() { }
- 匹配类的注解
// 匹配标有cn.qlq.annotationAOP.MyAnno注解的类下面的方法,要求annotation的RetentionPolicy为CLASS @Pointcut("@whithin(cn.qlq.annotationAOP.MyAnno)") public void pc() { }
// 匹配标有cn.qlq.annotationAOP.MyAnno注解的类下面的方法,要求annotation的RetentionPolicy为RUNTIME @Pointcut("@target(cn.qlq.annotationAOP.MyAnno)") public void pc() { }
- 匹配参数的注解
// 匹配传入的参数类型标有MyAnno注解的方法 @Pointcut("@args(cn.qlq.annotationAOP.MyAnno)") public void pc() { }
5.execution表达式
格式:
有问号的代表可以省略。
演变过程:
public void cn.qlq.service.UserServiceImpl.save() 对指定类型指定返回值的空参方法进行增强
void cn.qlq.service.UserServiceImpl.save() public可以省去,默认public
* cn.qlq.service.UserServiceImpl.save() 对返回类型不做要求,可以对任何返回类型织入
* cn.qlq.service.UserServiceImpl.*() 对返回类型不做要求,对方法名字不做要求
* cn.qlq.service.UserServiceImpl.*(..) 对返回类型不做要求,对方法名字不做要求,对参数也不做要求
* cn.qlq.service.*ServiceImpl.*(..) 对service包下所有以ServiceImpl结尾的类中的任意参数的任意方法增强
* cn.qlq.service.*ServiceImpl.*(..) throws java.sql.SQLException; 对service包下所有以ServiceImpl结尾的类中的任意参数的任意方法且抛出的异常类型是SQLException的方法增强
* cn.qlq.service..*ServiceImpl.*(..) 与上面不同的是对service包的子包也要进行增强(一般不用)
4.五种通知详解:
1.前置通知:@Before
查看源码:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Before { String value(); String argNames() default ""; }
第一个value是切点表达式,第二个argName属性不太理解?前置通知可用于参数校验,比如通过下列方式获取参数:
// 匹配传入的参数类型标有MyAnno注解的方法 @Pointcut("@annotation(cn.qlq.annotationAOP.MyAnno)") public void pc() { } // 前置通知(下面两种方式均可以) // @Before("MyAdvice.pc()") @Before(value = "pc() && args(arg)") public void before(JoinPoint joinPoint,Object arg) throws Exception { System.out.println("---------------前置通知开始(注解)~~~~~~~~~~~"); System.out.println("参数是:"+arg.toString()); System.out.println("---------------前置通知结束(注解)~~~~~~~~~~~"); }
需要增强的方法:
@MyAnno(name="自定义注解") @Override public boolean save(String userId) throws Exception { System.out.println("保存用户.....(有参数):"+userId); return true; }
测试:
package cn.qlq.test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import cn.qlq.service.UserService; //帮我们创建容器 @RunWith(SpringJUnit4ClassRunner.class) //指定创建容器时使用的配置文件 @ContextConfiguration("classpath:cn/qlq/annotationAOP/applicationContext.xml") public class SpringAnnotationAopTest { @Resource(name="userService") private UserService us; @Test public void fun1() throws Exception{ us.save("111"); } }
结果:
---------------前置通知开始(注解)~~~~~~~~~~~
参数是:111
---------------前置通知结束(注解)~~~~~~~~~~~
2.最终通知,方法执行完之后:@After
查看源码:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface After { String value(); String argNames() default ""; }
第一个value是切点表达式,第二个argName属性暂时不太理解?
3.后置通知,方法成功执行之后:@AfterReturning
查看源码:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AfterReturning { String value() default ""; String pointcut() default ""; String returning() default ""; String argNames() default ""; }
比上面多了一个returning,是方法的返回值,可以在返回通知中获取方法返回值,然后进行一些处理。例如:
// 后置通知(异常发生后不会调用) @AfterReturning(value="MyAdvice.pc()",returning = "result") public void afterRunning(Object result) { System.out.println(result);//获取方法的返回值 System.out.println("这是后置通知(异常发生后不会调用)。。。。(注解)"); }
4.环绕通知:@Around(一般有环绕通知就不需要其他四种通知)-----------重要
查看源码:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Around { String value(); String argNames() default ""; }
环绕通知需要有返回值,而且有异常通知一般不需要其它四种通知
package cn.qlq.annotationAOP; import java.lang.reflect.Method; import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; @Aspect // 表示该类是一个通知类 @Component // 交给spring管理 public class MyAdvice { // 匹配传入的参数类型标有MyAnno注解的方法 @Pointcut("@annotation(cn.qlq.annotationAOP.MyAnno)") public void pc() { } // 环绕通知 @Around("MyAdvice.pc()") public Object around(ProceedingJoinPoint pjp){ System.out.println("----------------环绕通知之前 的部分(相当于前置通知)----------------"); // 获取到类名 String targetName = pjp.getTarget().getClass().getName(); System.out.println("代理的类是:" + targetName); // 获取到参数 Object[] parameter = pjp.getArgs(); System.out.println("传入的参数是:" + Arrays.toString(parameter)); // 获取到方法签名,进而获得方法 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); System.out.println("增强的方法名字是:" + method.getName()); // 处理一些业务逻辑 // 获取参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); System.out.println("参数类型是:" + parameterTypes.toString()); // 让方法执行 System.out.println("--------------方法开始执行(注解)------------------"); Object proceed=null; try { proceed = pjp.proceed(); // 环绕通知之后的业务逻辑部分 System.out.println("----------------环绕通知之后的部分(相当于后置通知AfterReturning)-----------------"); } catch (Throwable e) { System.out.println("-------------环绕通知的异常部分(相当于异常通知AfterThrowing)--------------------------"); e.printStackTrace(); }finally { System.out.println("-------------环绕通知的最终部分部分(相当于最终通知After)--------------------------"); } return proceed; } }
结果:
----------------环绕通知之前 的部分(相当于前置通知)---------------- 代理的类是:cn.qlq.service.UserServiceImpl 传入的参数是:[111] 增强的方法名字是:save 参数类型是:[Ljava.lang.Class;@1548a36c --------------方法开始执行(注解)------------------ 保存用户.....(有参数):111 ----------------环绕通知之后的部分(相当于后置通知AfterReturning)----------------- -------------环绕通知的最终部分部分(相当于最终通知After)--------------------------
如果我们在环绕通知验证完权限之后,如果权限不够我们可以不执行proceed = pjp.proceed();方法,也就是不执行对应的方法。可以灵活的控制方法的执行与否。
5.异常通知: AfterThrowing
查啊看源码:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AfterThrowing { String value() default ""; String pointcut() default ""; String throwing() default ""; String argNames() default ""; }
5.SpringAOP代理的实现原理以及选择代理的方式参考:
http://www.cnblogs.com/qlqwjy/p/7550609.html
6.Spring多个AOP作用于同一目标对象采用的责任链模式
0.简单的类图
1.自己维护逻辑关系的责任链
Handler.java
package cn.qlq.chai; /** * * @author: qlq * @date : 2018年4月8日下午5:09:07 */ public abstract class Handler { private Handler sucessor; public Handler getSucessor() { return sucessor; } public void setSucessor(Handler sucessor) { this.sucessor = sucessor; } public void execute(){ handleProcess(); if(sucessor != null){ sucessor.execute(); } } protected abstract void handleProcess(); }
测试类:
package cn.qlq.chai; /** * * @author: qlq * @date : 2018年4月8日下午5:09:42 */ public class Client { static class HandlerA extends Handler{ @Override protected void handleProcess() { System.out.println("handle by a"); } } static class HandlerB extends Handler{ @Override protected void handleProcess() { System.out.println("handle by b"); } } static class HandlerC extends Handler{ @Override protected void handleProcess() { System.out.println("handle by c"); } } public static void main(String[] args){ Handler handlerA = new HandlerA(); Handler handlerB = new HandlerB(); Handler handlerC = new HandlerC(); handlerA.setSucessor(handlerB); handlerB.setSucessor(handlerC); handlerA.execute(); } }
结果:
handle by a
handle by b
handle by c
2.通过一个集合维护责任链:(类似于递归模式)
ChainHandler.java
package cn.qlq.chai; /** * * @author: qlq */ public abstract class ChainHandler { public void execute(Chain chain) { handleProcess(); chain.proceed(); } protected abstract void handleProcess(); }
Chain.java
package cn.qlq.chai; import java.util.List; /** * * @author: qlq */ public class Chain { private List<ChainHandler> handlers; private int index = 0; public Chain(List<ChainHandler> handlers) { this.handlers = handlers; } public void proceed() { if (index >= handlers.size()) { return; } handlers.get(index++).execute(this); } }
测试类:
package cn.qlq.chai; import java.util.Arrays; import java.util.List; /** * * @author: qlq */ public class ChainClient { static class ChainHandlerA extends ChainHandler { @Override protected void handleProcess() { System.out.println("handle by chain a"); } } static class ChainHandlerB extends ChainHandler { @Override protected void handleProcess() { System.out.println("handle by chain b"); } } static class ChainHandlerC extends ChainHandler { @Override protected void handleProcess() { System.out.println("handle by chain c"); } } public static void main(String[] args) { List<ChainHandler> handlers = Arrays.asList(new ChainHandlerA(), new ChainHandlerB(), new ChainHandlerC()); Chain chain = new Chain(handlers); chain.proceed(); } }
结果:
handle by a
handle by b
handle by c
3.Spring的链式调用:(采用上面第二种方式)
public Object proceed() throws Throwable { // We start with an index of -1 and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { // Dynamic matching failed. // Skip this interceptor and invoke the next in the chain. return proceed(); } } else { // It's an interceptor, so we just invoke it: The pointcut will have // been evaluated statically before this object was constructed. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }
关于spring配置总结:
<!-- 0.开启aop,对类代理使用cglib代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 1.开启注解AOP -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 2.开启注解管理aop事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
最后我靠着自己的理解写了一个注解AOP+注解的日志记录功能,参考我的另一篇博客:http://www.cnblogs.com/qlqwjy/p/8747476.html