java中AOP实践

Xiao Qiang Lv4

如何在spring boot项目上实现

什么是AOP

  • 基本概念,AOP 的全称是Aspect Oriented Programming,看到这里大家可能会想到我们 OOP 面向对象编程,这两者有什么关系和区别呢?两者是不同的编程范式,OOP 是主要使用对象的模式来解决问题,在对象中会定义属性和方法。AOP 主要关注点在 crosscutting concerns。这样的好处就是可以是这些公共的方法模块化,而不是在这里放一点又在那里放一点。AOP可能为能带来的问题就是anti-pattern,具体的问题就是action at a distance
    • 什么是 crosscutting concerns,简而述之就是那些需要项目大规模使用的一些方法,比如所日志,监控,全局异常捕获等等。
    • 什么是action at a distance,eg: 定义了一个人类Class,然后又在这个类中定义了一些人类的基本属性,speaking,learning以及sleeping等等这些。这个speaking行为对于不同的子类又会有不同的行为,比如说,中国人说中文,美国人说英文。这些具体的实现没有在每个子类中实现,而是放在了一个其他的类中,那么当我们在调用某个人的speaking方法,可能用的语言不一样,但是这个在哪里实现的,我们一开始是不知道的。就感觉这些行为像魔法一样,在我们不知道的情况下就执行了
  • AOP中的基本术语
    • Aspect,在aspect中就是定义我们需要的cross-cutting functionality,可能会有多个,比如LogManagementAspect
    • JoinPoint,程序运行时的某个时间点,可能是某个方法的执行,某个类的创建
    • PointCut,一系列能和JoinPoint匹配的表达式
    • Advice,当进去JoinPoint之后执行的一些操作,一个advice一般和一个CutPoint表达式匹配
    • Weaving,用不同的方式(compile-time Weaving, Post-Compile Weaving, Load-Time Weaving)将aspect和java相结合
  • AOP的主流框架

如何使用AOP

  • AspectJ

    • 定义AspectJ runTime需要的Aspect的依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
    </dependency>
    • 定义Weaver来使用动态代理,its actually a bytecode weaver which weaves aspects into classes at load time.
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
    </dependency>
    • 定义plugin,执行weaver,将aspectj文件编译成class文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <build>
    <plugins>
    <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
    <dependencies>
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>${aspectj.version}</version>
    </dependency>
    </dependencies>
    <configuration>
    <complianceLevel>1.8</complianceLevel>
    <source>1.8</source>
    <target>1.8</target>
    <showWeaveInfo>true</showWeaveInfo>
    <verbose>true</verbose>
    <Xlint>ignore</Xlint>
    <encoding>UTF-8 </encoding>
    </configuration>
    <executions>
    <execution>
    <goals>
    <!-- use this goal to weave all your main classes -->
    <goal>compile</goal>
    <!-- use this goal to weave all your test classes -->
    <goal>test-compile</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    <build>
    • 执行Maven->Plugins->aspectj:compile

    • 定义Aspect,文件的后缀名是*.aj,在这个文件中定义切面的方法,在Aspect中可以定义JoinPoint和Advice,编译之后就有.class文件,对应CutPoint就可以在程序中可以被匹配,上述方式为Compile-Time Weaving

      aop proxy details
    • weaving的AspectJ Development Tools

    • 验证,runmvn pacakage, 测试Aspect是否成功 mvn -Dtest=AccountTest test,跑完测试之后发现,Aspect实际上是在操作一个不同的Account,我们使用的是Account对象的地址值来判断是否操作的是同一个对象。这里要注意一下,直接使用intelliJ的test,无法被Aspect捕获

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      Running org.zqf.aop.AspectJService.AccountTest
      -------this---------- org.zqf.aop.AspectJService.Account@5ccd43c2
      Balance before withdrawal: 20
      Account address: org.zqf.aop.AspectJService.Account@5ccd43c2
      Withdraw ammout: 5
      Balance after withdrawal : 15
      Balance before withdrawal: 20
      Account address: org.zqf.aop.AspectJService.Account@368239c8
      Withdraw ammout: 100
      Withdrawal Rejected!
      Balance after withdrawal : 20
    • 其他的weaving方式

      • post weaving,当jar编译打包之后,再把aspect和代码编织起来
      • Load-Time weaving,使用Javaagent代理
    • 使用@Aspect注解方式来实现Aspect,需要自己定义annotation,定义loading-time weaver需要weave的class和Aspect

  • Spring AOP

    • 相比于使用aspectj,Spring aop上手简单,定义好aspect,在aspect中定义JoinPoint和Advice就可以使用了

      • 定义个Spring boot的component或者service的spring bean
      1
      2
      3
      4
      5
      6
      @Service
      public class XxxService {
      public Integer XxxMethod(arg ...) {
      ... ...
      }
      }
      • 定义Aspect,主要定义Advice
      • 定义bean的xml,其中要定义java bean的service以及Aspect,对于Aspect还需要定义JoinPoint和Advice
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <aop:config>

      <aop:aspect id="aspects" ref="<bean id>">
      <aop:pointcut id="point join id"
      expression="cut point experssion" />
      <aop:after-returning method="Aspect advice method name"
      returning="returnValue" pointcut-ref="pointCut id" />
      </aop:aspect>

      </aop:config>
      • AccountService使用了CGLIB代理,描述了这个对象的pointCut和Advice
        aop proxy details
      • tips:如果要模拟spring-boot application test,需要有个main函数的启动类,加上spring-boot的注解@SpringBootApplication,就不需要使用xml的形式注入bean
  • PointCut expression

  • Spring AOP

    • 相比于使用aspectj,Spring aop上手简单,定义好aspect,在aspect中定义JoinPoint和Advice就可以使用了

      • 定义个Spring boot的component或者service的spring bean
      1
      2
      3
      4
      5
      6
      @Service
      public class XxxService {
      public Integer XxxMethod(arg ...) {
      ... ...
      }
      }
      • 定义Aspect,主要定义Advice
      • 定义bean的xml,其中要定义java bean的service以及Aspect,对于Aspect还需要定义JoinPoint和Advice
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <aop:config>

      <aop:aspect id="aspects" ref="<bean id>">
      <aop:pointcut id="point join id"
      expression="cut point experssion" />
      <aop:after-returning method="Aspect advice method name"
      returning="returnValue" pointcut-ref="pointCut id" />
      </aop:aspect>

      </aop:config>
      • AccountService使用了CGLIB代理,描述了这个对象的pointCut和Adviceaop proxy details
      • tips:如果要模拟spring-boot application test,需要有个main函数的启动类,加上spring-boot的注解@SpringBootApplication,就不需要使用xml的形式注入bean
  • PointCut expression

  • 使用注解的形式定义AOP

    • 自定义一个annotation
    • 在业务场景中使用注解
    • 定义Aspect,在Aspect中定义PointCut,使用表达式的模式 @Pointcut("@annotation(secured)")

使用AOP的场景

  • 日志

  • 监控

  • cache

  • 功能性的业务

  • 日志

  • 监控

  • cache

  • 功能性的业务

避坑

  • 无法命中自己类中的切点,当该切点的方法在当前类的内部的时候

references

  • Title: java中AOP实践
  • Author: Xiao Qiang
  • Created at : 2023-03-14 17:00:27
  • Updated at : 2025-03-08 10:49:30
  • Link: http://fdslk.github.io/tech/java/aop/2023/03/14/java-aop/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments