AOP 使用场景

是当你执行一个方法时,我可以在方法执行前后,通知我的需要做点什么,同时我可以控制当前这个方法能否执行,感觉就像上帝视角看待这个方法执行情况,我可以获取方法的参数,可以设置方法的返回结果。

AOP用来封装横切关注点,具体可以在下面的场景中使用:

  • Authentication 权限
  • Caching 缓存
  • Context passing 内容传递
  • Error handling 错误处理
  • Lazy loading 懒加载
  • Debugging  调试
  • logging, tracing, profiling and monitoring 记录跟踪 优化 校准
  • Performance optimization 性能优化
  • Persistence  持久化
  • Resource pooling 资源池
  • Synchronization 同步
  • Transactions 事务

    AOP 相关术语

    Joinpoint(连接点):
    所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。

Pointcut(切入点):
所谓的切入点,就是拦截方法设置的规则。

Advice(通知/增强):
就是可以设置在方法之前拦截或者方法执行之后拦截或者方法出异常后拦截,或者方法之前和之后都拦截。我们将这些拦截场景称为通知/增强。

通知的类型:前置通知、后置通知、异常通知、最终通知、环绕通知。

Aspect(切面):
所谓的切面就是我们的拦截处理类

Weaving(织入):
把切面加入到对象,并创建出代理对象的过程。(该过程由Spring来完成)
image.png

Spring的AOP与动态代理

动态代理模式的缺陷是:

  1. 实现类必须要实现接口 -JDK动态代理
  2. 无法通过规则制定拦截无需功能增强的方法。

Spring-AOP主要弥补了第二个不足,通过规则设置来拦截方法,并对方法做统一的增强。
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
如果类实现类接口,那么使用JDK动态代理,否则使用CGLIB动态代理。

Spring AOP 执行顺序

AOP 常用注解

  • @Before
    • 前置通知:目标方法之前执行
  • @After
    • 后置通知:目标方法之后执行(始终执行)
  • @AfterReturning
    • 返回通知:执行方法结束前执行(异常不执行)
  • @AfterThrowing
    • 异常通知:出现异常的时候执行
  • @Around
    • 环绕通知:环绕目标方法执行

小知识:

SpringBoot1.X 版本对应 Spring4
SpringBoot 2.X 版本对应 Spring5

在 Spring4 版本下 AOP 的执行顺序:

image.png

在 Spring5 版本下 AOP 的执行顺序:

image.png

多个AOP执行先后顺序

众所周知,spring声明式事务是基于AOP实现的,那么,如果我们在同一个方法自定义多个AOP,我们如何指定他们的执行顺序呢?

  1. 通过实现org.springframework.core.Ordered接口
  1. @Component
  2. @Aspect
  3. @Slf4j
  4. public class MessageQueueAopAspect1 implements Ordered{@Override
  5. public int getOrder() {
  6. // TODO Auto-generated method stub
  7. return 2;
  8. }
  9. }
  1. 通过注解
  1. @Component
  2. @Aspect
  3. @Slf4j
  4. @Order(1)
  5. public class MessageQueueAopAspect1{
  6. ...
  7. }
  1. 通过配置文件配置
  1. <aop:config expose-proxy="true">
  2. <aop:aspect ref="aopBean" order="0">
  3. <aop:pointcut id="testPointcut" expression="@annotation(xxx.xxx.xxx.annotation.xxx)"/>
  4. <aop:around pointcut-ref="testPointcut" method="doAround" />
  5. </aop:aspect>
  6. </aop:config>

测试:

  1. @Component
  2. @Aspect
  3. @Slf4j
  4. public class MessageQueueAopAspect1 implements Ordered{
  5. @Resource(name="actionMessageProducer")
  6. private IProducer<MessageQueueInfo> actionProducer;
  7. @Pointcut("@annotation(com.xxx.annotation.MessageQueueRequire1)")
  8. private void pointCutMethod() {
  9. }
  10. //声明前置通知
  11. @Before("pointCutMethod()")
  12. public void doBefore(JoinPoint point) {
  13. log.info("MessageQueueAopAspect1:doBefore");
  14. return;
  15. }
  16. //声明后置通知
  17. @AfterReturning(pointcut = "pointCutMethod()", returning = "returnValue")
  18. public void doAfterReturning(JoinPoint point,Object returnValue) {
  19. log.info("MessageQueueAopAspect1:doAfterReturning");
  20. }
  21. //声明例外通知
  22. @AfterThrowing(pointcut = "pointCutMethod()", throwing = "e")
  23. public void doAfterThrowing(Exception e) {
  24. log.info("MessageQueueAopAspect1:doAfterThrowing");
  25. }
  26. //声明最终通知
  27. @After("pointCutMethod()")
  28. public void doAfter() {
  29. log.info("MessageQueueAopAspect1:doAfter");
  30. }
  31. //声明环绕通知
  32. @Around("pointCutMethod()")
  33. public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
  34. log.info("MessageQueueAopAspect1:doAround-1");
  35. Object obj = pjp.proceed();
  36. log.info("MessageQueueAopAspect1:doAround-2");
  37. return obj;
  38. }
  39. @Override
  40. public int getOrder() {
  41. return 1001;
  42. }
  43. }
  1. @Component
  2. @Aspect
  3. @Slf4j
  4. public class MessageQueueAopAspect2 implements Ordered{
  5. @Resource(name="actionMessageProducer")
  6. private IProducer<MessageQueueInfo> actionProducer;
  7. @Pointcut("@annotation(com.xxx.annotation.MessageQueueRequire2)")
  8. private void pointCutMethod() {
  9. }
  10. //声明前置通知
  11. @Before("pointCutMethod()")
  12. public void doBefore(JoinPoint point) {
  13. log.info("MessageQueueAopAspect2:doBefore");
  14. return;
  15. }
  16. //声明后置通知
  17. @AfterReturning(pointcut = "pointCutMethod()", returning = "returnValue")
  18. public void doAfterReturning(JoinPoint point,Object returnValue) {
  19. log.info("MessageQueueAopAspect2:doAfterReturning");
  20. }
  21. //声明例外通知
  22. @AfterThrowing(pointcut = "pointCutMethod()", throwing = "e")
  23. public void doAfterThrowing(Exception e) {
  24. log.info("MessageQueueAopAspect2:doAfterThrowing");
  25. }
  26. //声明最终通知
  27. @After("pointCutMethod()")
  28. public void doAfter() {
  29. log.info("MessageQueueAopAspect2:doAfter");
  30. }
  31. //声明环绕通知
  32. @Around("pointCutMethod()")
  33. public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
  34. log.info("MessageQueueAopAspect2:doAround-1");
  35. Object obj = pjp.proceed();
  36. log.info("MessageQueueAopAspect2:doAround-2");
  37. return obj;
  38. }
  39. @Override
  40. public int getOrder() {
  41. return 1002;
  42. }
  43. }
  1. @Transactional(propagation=Propagation.REQUIRES_NEW)
  2. @MessageQueueRequire1
  3. @MessageQueueRequire2
  4. public PnrPaymentErrCode bidLoan(String id){
  5. ...
  6. }

看看执行结果:

Spring AOP 基础 - 图4

从上面的测试我们看到,确实是order越小越是最先执行,但更重要的是最先执行的最后结束。

这个不难理解,Spring AOP就是面向切面编程,什么是切面,画一个图来理解下:

Spring AOP 基础 - 图5

由此得出:spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。

如果我们要在同一个方法事务提交后执行自己的AOP,那么把事务的AOP order设置为2,自己的AOP order设置为1,然后在doAfterReturn里边处理自己的业务逻辑。

@Aspect切面使用 Spring @Autowired

在Spring中一般使用@Aspect注解来定义一个切面,但是测试发现:

  1. @Aspect
  2. @Component
  3. public class IpWhiteListAspect {
  4. @Autowired
  5. private IpWhiteListConfig ipWhiteListConfig;

使用上述代码,运行的时候发现 bean ipWhiteListConfig为null,解决方法可以参见这里

这里稍微解释下,切面bean是在Spring容器之外创建的且是单例,因此无法使用Spring的注入。

采用xml配置

  1. <bean id="切面bean的id" class="切面的类"
  2. factory-method="aspectOf" />

采用注解方式配置

  1. @Configuration
  2. @ComponentScan("com.kirillch.eqrul")
  3. public class AspectConfig {
  4. @Bean
  5. public EmailAspect theAspect() {
  6. EmailAspect aspect = Aspects.aspectOf(EmailAspect.class);
  7. return aspect;
  8. }
  9. }
  1. @Aspect
  2. public class EmailAspect {
  3. @Autowired
  4. EmailService emailService;
  5. }

手动注入

  1. @Component
  2. public class SpringApplicationContextHolder implements ApplicationContextAware {
  3. private static ApplicationContext applicationContext = null;
  4. public static ApplicationContext getApplicationContext() {
  5. return applicationContext;
  6. }
  7. @Override
  8. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  9. this.applicationContext = applicationContext;
  10. }
  11. }

或者更详细一点的:

  1. @Component
  2. public class SpringApplicationContextHolder implements ApplicationContextAware {
  3. // Spring应用上下文环境
  4. private static ApplicationContext applicationContext;
  5. /**
  6. * 实现ApplicationContextAware接口的回调方法,设置上下文环境
  7. *
  8. * @param applicationContext
  9. */
  10. public void setApplicationContext(ApplicationContext applicationContext) {
  11. SpringApplicationContextHolder.applicationContext = applicationContext;
  12. }
  13. /**
  14. * @return ApplicationContext
  15. */
  16. public static ApplicationContext getApplicationContext() {
  17. return applicationContext;
  18. }
  19. /**
  20. * 获取对象
  21. *
  22. * @param name
  23. * @return Object
  24. * @throws BeansException
  25. */
  26. public static Object getBean(String name) throws BeansException {
  27. return applicationContext.getBean(name);
  28. }
  29. public static Object getBean(String name, Class<?> cla) throws BeansException {
  30. return applicationContext.getBean(name, cla);
  31. }
  32. public static Object getBean(Class<?> cla) throws BeansException {
  33. return applicationContext.getBean(cla);
  34. }
  35. }

然后使用applicationContext获取注入到Spring容器的Aspect的Bean。

  1. @DependsOn("springApplicationContextHolder")
  2. @Component
  3. @Aspect
  4. public class SomeAspect {
  5. @Autowired
  6. private SomeBean someBean;
  7. public static SomeAspect aspectOf() {
  8. return SpringApplicationContextHolder.getApplicationContext().getBean(SomeAspect.class);
  9. }

这样便可以在切面中自动注入了。

AOP小结

为什么要有AOP 编程?

问题:service层在实际开发中要处理事务,日志等操作,这些操作是和业务无关代码,但是又不能少,而且每个方法都有,大量重复,如何把这些代码抽取出去?

解决方案:此种场景只能使用Java的动态代理技术解决。为Service层的类创建代理对象。在代理对象中为各个方法进行增强,使用的时候直接面向代理对象,调用代理对象方法。代理对象底层还是会调用真实对象的方法,但是在方法执行之前,之后,异常,最终都可以做相应增强。

代理模式

Java中有一种设计模式,代理模式,讲解就上面问题解决方案,是一种思想。
动态代理技术:实际上就是对代理模式思想的一种具体实现。

JDK动态代理-Java官方

使用方法Object proxy = Proxy.newProxuInstacne(classLoader, interfaces, h)

  1. classLoader: 类加载器
  2. 被代理对象的接口的字节码
  3. 处理器,开发者具体做增强的地方。开发需要自己创建匿名内部类

缺点:只能代理有接口的类

CGLIB 第三方代理

使用类Enhancer(setClassLoader、setSuperclass、setCallBack)

优点:既可以代理有接口的,又可以代理没有接口的类。
缺点:但是不能代理 final修饰类或者方法。

动态代理能解决主要问题:代码重复问题,但是引来新的小问题(所有方法都代理,不能按照指定规则配置代理方法等等)。

Spring AOP

Spring的AOP在代理的基础之上,通过指定规则代理指定的方法。
使用SpringAOP后,开发者不需要编写过多的java代码,主要编写 Spring配置代码。

参考文献

https://www.mekau.com/4880.html
https://blog.csdn.net/xiaoyiaoyou/article/details/45972363 AOP Autowired https://blog.csdn.net/zlp1992/article/details/81037529 https://stackoverflow.com/questions/9633840/spring-autowired-bean-for-aspect-aspect-is-null AOP 先后顺序 https://blog.csdn.net/hxpjava1/article/details/55504513