环境:SpringBoot2.7.18
1. 简介
Spring框架通过多种机制增强代码功能,实现切面编程(AOP)。核心之一是通过动态代理技术,在运行时为Bean织入(动态代理)额外功能(如日志、安全等),无需修改源代码。此外,利用Java Agent技术(如AspectJ Weaver),可以在JVM层面拦截类加载过程,动态修改类字节码,从而实现更广泛的AOP支持。最后,AspectJ-Maven-Plugin编译插件在编译时直接修改源代码或字节码,确保切面逻辑与业务代码无缝集成,优化了性能并减少了运行时开销。这些技术使得开发者能更灵活地管理横切关注点,提升代码模块性和可维护性。接下来我们将详细的介绍这3种AOP实现的方式。
2. 实战案例
2.1 准备环境
Service类
@Service
public class UserService {
public void save() {
System.out.println("save...") ;
}
}
接下来的所有示例都将围绕着上面这个Service。
运行测试类
SpringApplication app = new SpringApplication(AppApplication.class) ;
app.setWebApplicationType(WebApplicationType.NONE) ;
ConfigurableApplicationContext context = app.run(args) ;
UserService us = context.getBean(UserService.class) ;
us.save() ;
启动测试类
2.2 代理方式
该种方式,是我们工作中用的最为普遍的方式,因为该种方式灵活,无需修改代码,适用于运行时切面增强,易于理解和集成。如下示例:
定义切面
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.pack..*Service.*(..))")
private void log() {}
@Before("log()")
public void before(JoinPoint jp) {
System.out.println("before, " + jp.getSignature()) ;
}
}
动态代理方式,只需要定义上面的切面Bean类。
运行测试代码,输出结果
before, void com.pack.aop.agent.UserService.save()
save...
通过动态代理方式,只需要在项目中定义@Aspect切面即可完成增强逻辑。我们将获取的UserService Class打印如下:
图片
通过CGLIB生成了代理类。
2.3 Agent增强方式
该种方式是在JVM层面拦截,支持更广泛的AOP场景,性能优化潜力大(相比较于上面代理方式)。要实现这种方式,我们首先需要定义aop.xml文件(META-INF中)
<aspectj>
<weaver>
<!-- 对哪些类进行增强 -->
<include within="com.pack.aop.agent..*" />
</weaver>
<!-- 定义切面类,可以定义多个 -->
<aspects>
<aspect name="com.pack.aop.agent.LogAspect" />
</aspects>
</aspectj>
接下来就运行时还需要配置jvm运行时参数
-javaagent:d:/maven/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar
注:这里的版本最后根据你当前环境的版本来指定。
运行测试代码:
图片
我们的业务代码被增强了,同时UserService并没有创建代理。通过反编译查看UserService。
图片
编译后的字节码也没有任何的变化。Agent的原理就在进行类加载时对类进行增强。
2.4 编译插件方式
动态代理的方式,通过对目标类生成代理,在执行目标方法前执行增强逻辑Advice,这种方式多少对性能是有影响的。而编译插件方式是在编译时增强,性能最佳,深度集成,减少运行时开销。
添加maven插件
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
<skip>true</skip>
</configuration>
<executions>
<execution>
<configuration>
<skip>false</skip>
</configuration>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
接下来我们可以将LogAspect类上的@Component注解删除了,现在不需要了。重新编译项目
mvn clean compile
再次运行测试代码;
图片
我们的代码同样被增强了,同时打印了UserService类,该类并没有被代理。反编译该类。
图片
通过反编译得知,在编译阶段就对我们的代码进行了增强。这也是此种方式性能最佳的原因。
总结:以上三种方式增强代码:动态代理灵活轻量,运行时织入;Java Agent在JVM层面拦截类加载,支持广泛AOP场景,性能优化潜力大但配置相对复杂;AspectJ-Maven-Plugin编译时修改字节码,减少运行时开销,支持复杂逻辑但需重新编译。