我的编程空间,编程开发者的网络收藏夹
学习永远不晚

解析SpringBoot中使用LoadTimeWeaving技术实现AOP功能

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

解析SpringBoot中使用LoadTimeWeaving技术实现AOP功能

1.SpringBoot AOP功能

1.1 LTW与不同的切面织入时机

AOP——面向切面编程,通过为目标类织入切面的方式,实现对目标类功能的增强。按切面被织如到目标类中的时间划分,主要有以下几种:

  • 1.运行期织入

这是最常见的,比如在运行期通过为目标类生成动态代理的方式实现AOP就属于运行期织入,这也是Spring AOP中的默认实现,并且提供了两种创建动态代理的方式:JDK自带的针对接口的动态代理和使用CGLib动态创建子类的方式创建动态代理。

  • 2.编译期织入

使用特殊的编译器在编译期将切面织入目标类,这种比较少见,因为需要特殊的编译器的支持。

  • 3.类加载期织入

通过字节码编辑技术在类加载期将切面织入目标类中,这是本篇介绍的重点。它的核心思想是:在目标类的class文件被JVM加载前,通过自定义类加载器或者类文件转换器将横切逻辑织入到目标类的class文件中,然后将修改后class文件交给JVM加载。这种织入方式可以简称为LTW(LoadTimeWeaving)。

1.2 JDK实现LTW的原理

可以使用JKD的代理功能让代理器访问到JVM的底层组件,借此向JVM注册类文件转换器,在类加载时对类文件的字节码进行转换。具体而言,java.lang.instrument包下定义了ClassFileTransformer接口,该接口的作用如下面的注释所描述

* An agent provides an implementation of this interface in order
* to transform class files.
* The transformation occurs before the class is defined by the JVM.

可以通过实现该接口,并重写如下抽象方法自定义类文件转换规则

byte[]
transform(  ClassLoader         loader,
            String              className,
            Class<?>            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer)
    throws IllegalClassFormatException;

classfileBuffer是原始类文件对应的字节码数组,返回的byte[]为转化后的字节码数组,如果返回null,则表示不进行字节码处理。

而java.lang.instrument包下的Instrumentation接口则可以将我们自定义的ClassTransFormer向JVM内部的组件进行注册

void
addTransformer(ClassFileTransformer transformer);

在实际使用中,可以通过JVM的-javaagent代理参数在启动时获取JVM内部组件的引用,将ClassFileTransformer实例注册到JVM中,JVM在加载Class文件时,会先调用这个ClassTransformer的transform()方法对Class文件的字节码进行转换,比如织入切面中定义的横切逻辑,实现AOP功能。整个过程可以入下所示

1.3 如何在Spring中实现LTW

Spring中默认通过运行期生成动态代理的方式实现切面的织入,实现AOP功能,但是Spring也可以使用LTW技术来实现AOP,并且提供了细粒度的控制,支持在单个ClassLoader范围内实施类文件转换。

Spring中的org.springframework.instrument.classloading.LoadTimeWeaver接口定义了为类加载器添加ClassFileTransfomer的抽象

* Defines the contract for adding one or more
* {@link ClassFileTransformer ClassFileTransformers} to a {@link ClassLoader}.
*
public interface LoadTimeWeaver {

Spring的LTW支持AspectJ定义的切面,既可以是直接使用AspectJ语法定义的切面,也可以是使用@AspectJ注解,通过java类定义的切面。Spring LTW通过读取classpath下META-INF/aop.xml文件,获取切面类和要被切面织入的目标类的相关信息,通过LoadTimeWeaver在ClassLoader加载类文件时将切面织入目标类中,其工作原理如下所示

Spring中可以通过LoadTimeWeaver将Spring提供的ClassFileTransformer注册到ClassLoader中。在类加载期,注册的ClassFileTransformer读取类路径下META-INF/aop.xml文件中定义的切面类和目标类信息,在目标类的class文件真正被VM加载前织入切面信息,生成新的Class文件字节码,然后交给VM加载。因而之后创建的目标类的实例,就已经实现了AOP功能。

2. Springboot中使用LTW实现AOP的例子

实现一个简单的AOP需求,在方法调用前后打印出开始和结束的日志信息。

相关的maven依赖和插件

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <argLine>
                    -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                    -javaagent:"${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar"
                    <!-- -Dspring.profiles.active=test-->
                </argLine>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <agent>
                    ${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
                </agent>
                <agent>
                    ${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar
                </agent>
            </configuration>
        </plugin>
    </plugins>

</build>

这里通过maven插件的方式为JVM设置代理,通过-javaagent参数指定织入器类包的路径,这样就可以在类加载期将切面织入,更多关于javaagent的知识可以参考javaagent

织入目标类


@Component
public class LtwBean {
    public void test(){
        System.out.println("process.......");
    }
}

只有一个test()方法,通过@Componet注解向容器注册。

切面类


@Aspect
public class LogMethodInvokeAspect {

    @Pointcut("execution(public * com.takumiCX.ltw.*.*(..))")
    public void pointCut(){

    }
    @Around("pointCut()")
    public void advise(ProceedingJoinPoint pjp) throws Throwable {


        Signature signature = pjp.getSignature();
        System.out.println(signature+" start..... ");
        pjp.proceed();
        System.out.println(signature+" end......");
    }
}

@Aspect注解表示这是一个切面类

配置类


@Configuration
@ComponentScan("com.takumiCX.ltw")
@EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT)
public class CustomLtwConfig{
}

通过@@EnableLoadTimeWeaving开启LTW功能,可以通过属性aspectjWeaving指定LTW的开启策略

  1. ENABLED

开启LTW

  1. DISABLED

不开启LTW

  • AUTODETECT

如果类路径下能读取到META-INF/aop.xml文件,则开启LTW,否则关闭

  • 在META-INF文件夹下编写aop.xml文件

aop.xml文件内容

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

    <!--要织入切面的目标类-->
    <weaver>
        <include within="com.takumiCX.ltw..*" />
    </weaver>
    <!--切面类-->
    <aspects>
        <aspect name="com.takumiCX.ltw.aspect.LogMethodInvokeAspect" />
    </aspects>
</aspectj>

这样我们的Spring容器就能加载该文件读取到描述目标类和切面类的相关信息,容器在加载目标类的class文件到jvm之前,会将切面类中定义的增强逻辑织入到class文件中,真正加载到jvm中的是织入切面后的class文件,因而通过该class文件创建出的目标类的实例,不需要经过动态代理就能实现AOP相关功能。

测试类


@RunWith(SpringRunner.class)
@SpringBootTest(classes ={CustomLtwConfig.class})
public class LTWTest {
    @Autowired
    private LtwBean ltwBean;
    @Test
    public void testLTW() throws InterruptedException {

        ltwBean.test();

    }
}

最后的结果如下

方法调用前后分别记录的开始和结束的日志信息,说明我们的切面成功的织入到了目标类。但是这里可能有一个疑问,这真的是LTW(Load TimeWeaving)通过在类加载期织入切面起到的作用吗?有没有可能是LTW没起作用,是Spring AOP默认通过运行期生成动态代理的方式实现的AOP。
我们的LogMethodInvokeAspect切面类上并没有加@Component注解向容器注册,并且配置类CustomLtwConfig上也没有加@EnableAspectJAutoProxy注解开启Aspectj的运行时动态代理,所以这里基于动态代理的AOP并不会生效。

为了验证我们的想法,将aop.xml文件删除

重新运行测试代码

AOP没起到作用,说明刚才的AOP功能确实是通过LTW技术实现的。

当我们给切面类加上@Component注解,给配置类加上@EnableAspectJAutoProxy


@Aspect
@Component
public class LogMethodInvokeAspect {

@Configuration
@ComponentScan("com.takumiCX.ltw")
@EnableAspectJAutoProxy
@EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT)
public class CustomLtwConfig{
}

再次运行测试类时,发现AOP又生效了,这时候类路径下并没有aop.xml,所以这时候AOP是Spring在运行期通过动态代理的方式实现的。

3. 参考资料

《精通Spring4.x企业应用开发实战》
《spring揭秘》
https://sexycoding.iteye.com/blog/1062372

到此这篇关于SpringBoot中使用LoadTimeWeaving技术实现AOP功能的文章就介绍到这了,更多相关SpringBoot AOP功能内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

解析SpringBoot中使用LoadTimeWeaving技术实现AOP功能

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么在VUE中使用SpringBoot实现分页功能

这篇文章给大家介绍怎么在VUE中使用SpringBoot实现分页功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。1、效果展示2、VUE代码VUE之视图定义
2023-06-15

SpringBoot使用@Cacheable注解实现缓存功能流程详解

最近一直再学SpringBoot,在学习的过程中也有过很多疑问。为了解答自己的疑惑,也在网上查了一些资料,以下是对@Cacheable注解的一些理解
2023-01-12

android 性能分析(优化)-利用AOP技术,字节码方法插桩,实现 android 耗时统计

在android开发的过程中,不管是开发SDK还是开发App,我们时常有这么一个需求,性能优化,或者卡顿优化,通常进行这项工作的时候,我们可能会凭着主观的一些观点,去定位排查某一部分代码,常用的手段就是在可疑代码前后加时间戳相减来手动统计,
2022-06-06

使用注解怎么实现一个SpringBoot 接口防刷功能

这篇文章将为大家详细讲解有关使用注解怎么实现一个SpringBoot 接口防刷功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。项目结构如下:一、编写注解类 AccessLimitpacka
2023-06-06

在java中使用dom4j包如何实现一个解析XML功能

这篇文章将为大家详细讲解有关在java中使用dom4j包如何实现一个解析XML功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。具体如下:xml文件内容如下:2023-05-31

在SpringBoot中使用MyBatis如何实现一个应用访问功能

今天就跟大家聊聊有关在SpringBoot中使用MyBatis如何实现一个应用访问功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一,Pom文件
2023-05-31

在SpringBoot 中使用Mybatis Plus如何实现一个自动填充功能

本篇文章为大家展示了在SpringBoot 中使用Mybatis Plus如何实现一个自动填充功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一.应用场景平时在建对象表的时候都会有最后修改时间,最
2023-05-31

浅析vue项目中如何使用Intro.js实现用户指引功能

vue项目中怎么实现用户指引功能?下面本篇文章给大家介绍一下在vue项目中使用Intro.js实现用户指引功能的方法,希望对大家有所帮助!
2023-05-14

如何在springboot中使用mybatis-plus实现一个多表分页查询功能

这篇文章将为大家详细讲解有关如何在springboot中使用mybatis-plus实现一个多表分页查询功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。1.新建一个springboot工程
2023-06-07

如何在SpringBoot中使用Shiro怎么实现一个邮件验证码登录功能

如何在SpringBoot中使用Shiro怎么实现一个邮件验证码登录功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。导入依赖(pom.xml)