aop:在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式

  • 实现原理:使用动态代理
    • JDK 动态代理:基于反射生成代理类
    • cglib 动态代理:基于 ASM 字节码生成代理类
  • 应用场景:日志记录、事务处理、安全控制、异常处理、性能统计等

Spring AOP 的开发步骤

1、引入Spring AOP 相关依赖:

  1. <!-- Aspect -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-aspects</artifactId>
  5. <version>5.2.9.RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-aop</artifactId>
  10. <version>5.2.9.RELEASE</version>
  11. </dependency>

2、定义相关的业务逻辑类:

  1. @Service
  2. public class UserServiceImpl implements UserService {
  3. @Override
  4. public int add(int a, int b) {
  5. return a + b;
  6. }
  7. }

3、定义通知类(添加额外功能):实现相应的接口或者添加相应的注解

  • 接口:
    • MethodBeforeAdvice:前置通知
    • AfterAdvice:后置通知
    • AfterReturningAdvice:正常返回值通知,存在异常时不执行,方法因异常而结束,无返回值
    • ThrowsAdvice:异常通知
    • MethodInterceptor:环绕通知
  • 注解方式,注解在方法上:
    • 前置:@Before
    • 后置:@After
    • 返回:@AfterReturning
    • 异常:@AfterThrowing
    • 环绕通知:@Around

4、定义切入点表达式:根据表达式通配符匹配切入点

  • 语法:修饰符 返回值 包 方法名(参数表)
  • 注意事项:
    • 参数表中的参数类型需要使用全限定类名
    • 点号 “.” 匹配单级路径
    • 双点号“..”匹配多级路径
  • 切入点函数:
    • execution():功能最全
    • args():匹配参数,args(String , ..)
    • within():匹配类、包切入点表达式的匹配,within(*..UserService)
    • @annotation:为具有特殊注解的类进行切入,如 @annotation(注解定义的全限定名)
  • 切入点的逻辑运算:整合多个切入点进行工作
    • and:同时满足两个表达式,注意不能用于同种类型的切入点函数
    • or:只要满足一个切入点表达式,可以使用同种类型的切入函数

5、将切面类与业务逻辑类加入到容器中

6、使用 @Aspect 声明一个切面

  1. @Aspect
  2. @Component
  3. public class LogAspect {
  4. // 定义切点表达式
  5. @Pointcut("execution(* org.example.service..*.*(..))")
  6. public void myPoint() {
  7. }
  8. // 注意,如果引用的不是本类的切入点表达式,需要写类的全限定路径
  9. // JoinPoint必须出现在参数表的第一位
  10. @Before("myPoint()")
  11. public void logBefore(JoinPoint joinpoint) {
  12. // 获取方法签名
  13. Signature signature = joinpoint.getSignature();
  14. // 获取方法名称
  15. String name = signature.getName();
  16. // 获取方法传入的参数
  17. Object[] args = joinpoint.getArgs();
  18. System.out.println("方法名称:" + name);
  19. System.out.println("传入的参数:" + Arrays.toString(args));
  20. }
  21. @After("myPoint()")
  22. public void after(JoinPoint joinPoint) {
  23. System.out.println("方法执行完成!");
  24. }
  25. @AfterReturning(value = "myPoint()", returning = "result")
  26. public void Return(JoinPoint joinPoint, Object result) {
  27. // 可以在返回值切入表达式中使用returning接受方法的返回值
  28. System.out.println("方法的返回值为:" + result);
  29. }
  30. @AfterThrowing(value = "myPoint()", throwing = "exception")
  31. public void logException(JoinPoint joinPoint, Exception exception) {
  32. // 在异常表达式中接受函数出现的异常并作相应的处理
  33. System.out.println("发生了异常" + exception.getMessage());
  34. }
  35. // 上面几个方法和Around方法二选一即可
  36. @Around("myPoint()")
  37. public void log(ProceedingJoinPoint pjp) throws Throwable {
  38. // 获取方法签名
  39. Signature signature = pjp.getSignature();
  40. // 获取传入的参数
  41. Object[] args = pjp.getArgs();
  42. System.out.println("正在执行的方法:" + signature.getName()
  43. + "\t相关参数:" + Arrays.toString(args));
  44. try {
  45. // 执行目标方法并获取返回值
  46. Object result = pjp.proceed(args);
  47. // 打印返回值
  48. System.out.println("方法返回值:" + result);
  49. } catch (RuntimeException e) {
  50. System.out.println("执行目标方法出现异常:" + e.getMessage());
  51. }
  52. }
  53. }

7、给配置类中加上 @EnableAspectJAutoProxy 开启基于注解的aop模式

  1. @Configuration //声明配置类
  2. @ComponentScan(basePackages = {"org.example"})
  3. @EnableAspectJAutoProxy // 开启基于注解的aop模式
  4. public class DemoConfig {
  5. ...
  6. }

总结:
1、将业务类和切面类加入到容器中,并在切面类上标注 @Aspect 声明切面类
2、在切面类的通知方法上标注通知注解,告知 spring 在何时何地调用(切入点表达式,@Before 等)
3、在配置类上开启基于注解的aop模式

@EnableAspectJAutoProxy 注解

@EnableAspectJAutoProxy 注解的作用:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Import(AspectJAutoProxyRegistrar.class)
  5. public @interface EnableAspectJAutoProxy {...}
  6. class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
  7. /**
  8. * Register, escalate, and configure the AspectJ auto proxy creator based on the value
  9. * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
  10. * {@code @Configuration} class.
  11. */
  12. @Override
  13. public void registerBeanDefinitions(
  14. AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  15. AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
  16. AnnotationAttributes enableAspectJAutoProxy =
  17. AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
  18. if (enableAspectJAutoProxy != null) {
  19. if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
  20. AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
  21. }
  22. if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
  23. AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
  24. }
  25. }
  26. }
  27. }
  • @Import(AspectJAutoProxyRegistrar.class):利用@Import注解导入了 AspectJAutoProxyRegistrar 类
  • AspectJAutoProxyRegister 实现 ImportBeanDefinitionRegistrar 接口,并在 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry) 注册了 AnnotationAwareAspectJAutoProxyCreator.class 组件
  • AnnotationAwareAspectJAutoProxyCreator —> AspectJAwareAdvisorAutoProxyCreator —> AbstractAdvisorAutoProxyCreator —> AbstractAutoProxyCreator
  • AbstractAutoProxyCreator 继承了 ProxyProcessorSupport ,并实现了 SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware
    • SmartInstantiationAwareBeanPostProcessor —> InstantiationAwareBeanPostProcessor —> BeanPostProcessor :后置处理器
    • BeanFactoryAware:Spring提供的回调机制,会注入 BeanFactory

总结:@EnableAspectJAutoProxy 注解在 Spring 中使用 @Import( ImportBeanDefinitionRegistrar.class ) 注解注册一个后置处理器,该处理器能够获得 BeanFactory 并作相应的处理