相关概念

  • JoinPoint:拦截点。Spring 中,拦截点只支持方法。
  • Pointcut:切入点,指要对哪些 JoinPoint 进行拦截。
  • Advice(通知/增强):拦截到 JoinPoint 后执行的代码。
    • 通知分为:前置、后置、异常、最终、环绕、引介通知
  • Introduction:引介是一种特殊的通知,在不修改类代码的前提下,在运行期为类添加方法或属性。
  • Target:目标,代理的目标对象
  • Weaving:织入,指 为增强目标对象,创建代理对象的过程。
    • Spring 传统 aop 采用动态代理,AspectJ 采用编译期织入和类加载期织入。
  • Proxy:一个类被增强后,就产生一个结果代理。
  • Aspect:切面,指切入点和通知的统称。

Spring 传统 AOP

  • Spring 在运行期间,生成动态代理对象,不需要特殊编译器
  • 如果目标类实现了接口,Spring 就采用 JDK 的动态代理,根据接口生成代理
  • 如果目标类没有实现任何接口,Spring 就采用 CGLIB 生成目标类的子类进行增强。

  • 应优先对接口创建代理,便于程序解耦

  • 目标类和方法不要用 final 修饰,因为不能被代理,从而不生效

    • JDK 动态代理针对接口生成子类,接口中方法不能使用 final 修饰
    • CGLIB 针对目标类生成子类,如果目标被标记 final 不能被继承重写,从而失效

      AspectJ

  • AspectJ 是基于 Java 语言的 AOP 框架

  • Spring2.0 后整合了 AspectJ,较传统 Spring aop 更方便快捷
  • AspectJ 采用编译期织入和类加载期织入。

    Pointcut 切入点声明

  • 一个切入点表达式决定了哪些方法会真正被增强。

  • 一个切入点标签包含一个名称和任意数量的参数。方法的真正内容是不相干的,并且实际上它应该是空的。

下面的示例中定义了一个名为 ‘businessService’ 的切入点,该切入点将与 com.tutorialspoint 包下的类中可用的每一个方法相匹配

  1. import org.aspectj.lang.annotation.Pointcut;
  2. @Pointcut("execution(* com.xyz.myapp.service.*.*(..))") // expression
  3. private void businessService() {} // signature

下面的示例中定义了一个名为 ‘getname’ 的切入点,该切入点将与 com.tutorialspoint 包下的 Student 类中的 getName() 方法相匹配

  1. @Pointcut("execution(* com.tutorialspoint.Student.getName(..))")
  2. private void getname() {}

Advice 通知声明

通知类型:

  • @Before 前置通知
  • @AfterReturning 后置通知
  • @Around 环绕通知
  • @AfterThrowing 异常抛出通知
  • @After 最终通知
  • @DeclareParents 引介通知

假设已经定义了一个Pointcut方法 businessService():

  1. @Before("businessService()")
  2. public void doBeforeTask(){
  3. ...
  4. }

也可直接设置 Pointcut

  1. @Before("execution(* com.xyz.myapp.service.*.*(..))")
  2. public doBeforeTask(){
  3. ...
  4. }

示例:记录超时 Service 日志

引入依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-aop</artifactId>
  4. </dependency>

创建 在 aspect 下创建 ServiceLogAspect

  1. @Aspect
  2. @Component
  3. public class ServiceLogAspect {
  4. public static final Logger log = LoggerFactory.getLogger(ServiceLogAspect.class);
  5. /**
  6. * AOP 通知
  7. * 1. 前置通知:在方法调用前执行
  8. * 2. 后置通知:在方法正常调用后执行
  9. * 3. 环绕通知:在方法调用之前和之后,都分别可以执行的通知
  10. * 4. 异常通知:在方法调用过程中发生异常时通知
  11. * 5. 最终通知:在方法调用之后执行
  12. */
  13. /**
  14. * 切面表达式:
  15. * execution 代表所要执行的表达式主体
  16. * 参数一:方法返回类型 * 代表所有类型
  17. * 参数二:aop 监控的类所在的包
  18. * .. 代表该包及其子包下的所有类 * 代表所有类
  19. * *(..) * 代表类中的方法名,(..) 表示方法中的任何参数
  20. */
  21. @Around("execution(* com.ylq.service.impl..*.*(..))")
  22. public Object recordTimeLog(ProceedingJoinPoint joinPoint) throws Throwable {
  23. // 输出类名 和方法名
  24. log.info("====开始执行 {}.{}====", joinPoint.getTarget().getClass(), joinPoint.getSignature().getName());
  25. long begin = System.currentTimeMillis();
  26. // 执行目标 service
  27. Object result = joinPoint.proceed();
  28. long end = System.currentTimeMillis();
  29. long takeTime = end - begin;
  30. if (takeTime > 3000) {
  31. log.error("====执行结束,耗时{}毫秒====", takeTime);
  32. } else if (takeTime > 2000) {
  33. log.warn("====执行结束,耗时{}毫秒====", takeTime);
  34. } else {
  35. log.info("====执行结束,耗时{}毫秒====", takeTime);
  36. }
  37. return result;
  38. }
  39. }