AOP 实现之 ajc 编译器
AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能
除此以外,aspectj 提供了两种另外的 AOP 底层实现:
- 第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
- 第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能
- 作为对比,之前学习的代理是运行时生成新的字节码
简单比较的话:
- aspectj 在编译和加载时,修改目标字节码,性能较高
- aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
- 但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行
@Aspect // ⬅️注意此切面并未被 Spring 管理 public class MyAspect { private static final Logger log = LoggerFactory.getLogger(MyAspect.class); @Before("execution(* com.itheima.service.MyService.foo())") public void before() { log.debug("before()"); } }
@Service public class MyService { private static final Logger log = LoggerFactory.getLogger(MyService.class); public void foo() { log.debug("foo()"); } }
添加maven插件
aspectj-maven-plugin
用aspect编译器编译Java文件<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.14.0</version> <configuration> <complianceLevel>1.8</complianceLevel> <source>8</source> <target>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>
/* 注意几点 1. 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16 2. 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器 */ @SpringBootApplication public class A09 { private static final Logger log = LoggerFactory.getLogger(A09.class); public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A09.class, args); MyService service = context.getBean(MyService.class); log.debug("service class: {}", service.getClass()); service.foo(); context.close(); // new MyService().foo(); } }
运行结果,并没有生成代理类
点开MyService.class文件反编译
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // @Service public class MyService { private static final Logger log = LoggerFactory.getLogger(MyService.class); public MyService() { } public void foo() { MyAspect.aspectOf().before(); log.debug("foo()"); } }
对静态方法增强
@Service public class MyService { private static final Logger log = LoggerFactory.getLogger(MyService.class); //静态方法 public static void foo() { log.debug("foo()"); } }
public class A09 { private static final Logger log = LoggerFactory.getLogger(A09.class); public static void main(String[] args) { MyService.foo(); } }
静态方法增强
总结
- 编译器也能修改 class 实现增强
- 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强
注意
- 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
- 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器
AOP 实现之 agent 类加载
@Aspect // ⬅️注意此切面并未被 Spring 管理 public class MyAspect { private static final Logger log = LoggerFactory.getLogger(MyAspect.class); @Before("execution(* com.onethink.service.MyService.*())") public void before() { log.debug("before()"); } }
@Service public class MyService { private static final Logger log = LoggerFactory.getLogger(MyService.class); final public void foo() { log.debug("foo()"); this.bar(); } public void bar() { log.debug("bar()"); } }
在resources/META-INF目录下新增一个aop文件
<aspectj> <aspects> <aspect name="com.onethink.aop.MyAspect"/> <weaver options="-verbose -showWeaveInfo"> <include within="com.onethink.service.MyService"/> <include within="com.onethink.aop.MyAspect"/> </weaver> </aspects> </aspectj>
/* 注意几点 1. 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16 2. 运行时需要在 VM options 里加入 -javaagent:C:/Users/manyh/.m2/repository/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar 把其中 E:\evn\maven\maven_repository 改为你自己 maven 仓库起始地址 */ @SpringBootApplication public class A10 { private static final Logger log = LoggerFactory.getLogger(A10.class); public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A10.class, args); MyService service = context.getBean(MyService.class); // ⬇️MyService 并非代理, 但 foo 方法也被增强了, 做增强的 java agent, 在加载类时, 修改了 class 字节码 log.debug("service class: {}", service.getClass()); service.foo(); // context.close(); /* 学到了什么 1. aop 的原理并非代理一种, agent 也能, 只要字节码变了, 行为就变了 */ } }
[AppClassLoader@63947c6b] info AspectJ Weaver Version 1.9.7 built on Thursday Jun 24, 2021 at 16:14:45 PDT [AppClassLoader@63947c6b] info register classloader jdk.internal.loader.ClassLoaders$AppClassLoader@63947c6b [AppClassLoader@63947c6b] info using configuration /E:/code/java/spring_learn/demo6_advanced_aspectj_02/target/classes/META-INF/aop.xml [AppClassLoader@63947c6b] info register aspect com.onethink.aop.MyAspect [AppClassLoader@63947c6b] warning javax.* types are not being woven because the weaver option '-Xset:weaveJavaxPackages=true' has not been specified . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.5.5) 2023-04-02 16:44:07.236 INFO 22256 --- [ main] com.onethink.A10 : Starting A10 using Java 17.0.5 on OneThink with PID 22256 (E:\code\java\spring_learn\demo6_advanced_aspectj_02\target\classes started by moonlight in E:\code\java\spring_learn\show) 2023-04-02 16:44:07.237 DEBUG 22256 --- [ main] com.onethink.A10 : Running with Spring Boot v2.5.5, Spring v5.3.10 2023-04-02 16:44:07.237 INFO 22256 --- [ main] com.onethink.A10 : No active profile set, falling back to default profiles: default [AppClassLoader@63947c6b] weaveinfo Join point 'method-execution(void com.onethink.service.MyService.foo())' in Type 'com.onethink.service.MyService' (MyService.java:13) advised by before advice from 'com.onethink.aop.MyAspect' (MyAspect.java) [AppClassLoader@63947c6b] weaveinfo Join point 'method-execution(void com.onethink.service.MyService.bar())' in Type 'com.onethink.service.MyService' (MyService.java:18) advised by before advice from 'com.onethink.aop.MyAspect' (MyAspect.java) 2023-04-02 16:44:07.796 INFO 22256 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2023-04-02 16:44:07.803 INFO 22256 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-04-02 16:44:07.803 INFO 22256 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.53] 2023-04-02 16:44:07.837 INFO 22256 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2023-04-02 16:44:07.837 INFO 22256 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 573 ms 2023-04-02 16:44:08.035 INFO 22256 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2023-04-02 16:44:08.042 INFO 22256 --- [ main] com.onethink.A10 : Started A10 in 1.028 seconds (JVM running for 1.546) 2023-04-02 16:44:08.044 DEBUG 22256 --- [ main] com.onethink.A10 : service class: class com.onethink.service.MyService 2023-04-02 16:44:08.049 DEBUG 22256 --- [ main] com.onethink.aop.MyAspect : before() 2023-04-02 16:44:08.049 DEBUG 22256 --- [ main] com.onethink.service.MyService : foo() 2023-04-02 16:44:08.049 DEBUG 22256 --- [ main] com.onethink.aop.MyAspect : before() 2023-04-02 16:44:08.049 DEBUG 22256 --- [ main] com.onethink.service.MyService : bar() [MethodUtil@534e6c75] info AspectJ Weaver Version 1.9.7 built on Thursday Jun 24, 2021 at 16:14:45 PDT [MethodUtil@534e6c75] info register classloader sun.reflect.misc.MethodUtil@534e6c75 [MethodUtil@534e6c75] info using configuration /E:/code/java/spring_learn/demo6_advanced_aspectj_02/target/classes/META-INF/aop.xml [MethodUtil@534e6c75] info register aspect com.onethink.aop.MyAspect
分析
.class文件并没有增强代码而是在类加载运行阶段进行增强
使用
jad com.onethink.service.MyService