强大!SpringBoot通过三种方式实现AOP切面,第三种方式性能极佳

开发 前端
动态代理灵活轻量,运行时织入;Java Agent在JVM层面拦截类加载,支持广泛AOP场景,性能优化潜力大但配置相对复杂;AspectJ-Maven-Plugin编译时修改字节码,减少运行时开销,支持复杂逻辑但需重新编译。​

环境: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编译时修改字节码,减少运行时开销,支持复杂逻辑但需重新编译。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2022-05-30 07:07:35

Java监听文件Java 8

2014-12-31 17:42:47

LBSAndroid地图

2021-11-05 21:33:28

Redis数据高并发

2021-06-24 08:52:19

单点登录代码前端

2023-06-20 12:15:46

ChatGPT人工智能责任

2010-03-12 17:52:35

Python输入方式

2024-09-20 05:49:04

SpringBoot后端

2012-07-17 09:16:16

SpringSSH

2018-04-02 14:29:18

Java多线程方式

2010-09-07 16:31:27

CSS

2023-12-04 09:31:13

CSS卡片

2024-07-01 12:42:58

2018-07-10 14:23:02

2014-07-30 17:10:38

LVS集群负载均衡

2009-07-03 18:32:18

JSP页面跳转

2019-11-20 18:52:24

物联网智能照明智能恒温器

2011-04-08 11:13:50

CISCO IOS令牌桶双桶

2010-06-13 16:04:14

MySQL三种安装方式

2020-11-01 17:10:46

异步事件开发前端
点赞
收藏

51CTO技术栈公众号