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

目录

  • 1. 关于LoadTimeWeaving

    • 1.1 LTW与不同的切面织入时机
    • 1.2 JDK实现LTW的原理
    • 1.3 如何在Spring中实现LTW
  • 2. Springboot中使用LTW实现AOP的例子
  • 3. 参考资料

1. 关于LoadTimeWeaving

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

  • 织入目标类
/**
 * @author: takumiCX
 * @create: 2018-12-19
 **/
@Component
public class LtwBean {

    public void test(){
        System.out.println("process.......");
    }

}

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

  • 切面类
/**
 * @author: takumiCX
 * @create: 2018-12-19
 **/
@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注解表示这是一个切面类

  • 配置类
/**
 * @author: takumiCX
 * @create: 2018-12-19
 **/
@Configuration
@ComponentScan("com.takumiCX.ltw")
@EnableLoadTimeWeaving(aspectjWeaving=AUTODETECT)
public class CustomLtwConfig{
}

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

  1. ENABLED
    开启LTW
  2. DISABLED
    不开启LTW
  3. 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相关功能。

  • 测试类
/**
 * @author: takumiCX
 * @create: 2018-12-20
 **/
@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

/**
 * @author: takumiCX
 * @create: 2018-12-19
 **/
@Aspect
@Component
public class LogMethodInvokeAspect {
/**
 * @author: takumiCX
 * @create: 2018-12-19
 **/
@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

原文地址:https://www.cnblogs.com/takumicx/p/10150344.html

时间: 2024-07-29 00:29:23

SpringBoot中使用LoadTimeWeaving技术实现AOP功能的相关文章

使用CGLIB实现AOP功能与AOP概念解释

使用CGLIB实现AOP功能 在Java里面,我们要产生某个对象的代理对象,这个对象必须要有一个特点,即这个对象必须实现一个接口,动态代理技术只能基于接口进行代理.有时候我们在做开发的时候,这个对象就没有实现接口,有人可能会说,它既然没有接口,那我就给它定义一个接口,这是不行的,因为有时候我们拿到一个对象,而这个对象是服务器产生给我们的,是服务器提供给我们的,又不是我们自己写的,动不动就给它定义一个接口,给它找个爸爸,哪那行呢?但我们现在要对它进行增强,这时用动态代理技术就不行了,动态代理技术只

SpringBoot中如何灵活的实现接口数据的加解密功能?

数据是企业的第四张名片,企业级开发中少不了数据的加密传输,所以本文介绍下SpringBoot中接口数据加密.解密的方式. 本文目录 一.加密方案介绍二.实现原理三.实战四.测试五.踩到的坑 一.加密方案介绍 对接口的加密解密操作主要有下面两种方式: 自定义消息转换器 优势:仅需实现接口,配置简单.劣势:仅能对同一类型的MediaType进行加解密操作,不灵活. 使用spring提供的接口RequestBodyAdvice和ResponseBodyAdvice 优势:可以按照请求的Referrer

Springboot 中AOP的使用

面向切面编程(Aspect Oriented Programming) 是软件编程思想发展到一定阶段的产物,是面向对象编程的有益补充.AOP一般适用于具有横切逻辑的场合,如访问控制.事务管理.性能检测等. 日志.异常处理.事务控制等都是一个健壮的业务系统所必须的.但是为了保证系统健壮可用,就要再众多业务方法中反复编写类似的代码,使得原本就很复杂的业务处理代码变得更加复杂.业务功能的开发者还要考两次这些额外的代码是否处理正确,是否有遗漏的地方,如果需要修改日志信息的格式或者安全验证的规则,或者再增

你相信么,只需一个函数5行JS代码即可在Javascript中实现完整的AOP功能

你相信么,只需一个函数5行JS代码即可在Javascript中实现完整的AOP功能, 你相信么,在JavaScript只需一个函数5行代码即可实现完整的面向方面AOP编程功能.这5行代码的功能包括: 无限层次的函数无害拦截 函数执行前拦截 检查函数的参数值 重新设定函数的参数值 函数执行后拦截 检查函数执行后的返回结果 重新设定函数的返回结果 虽然动态函数式语言的效率是一个存在的问题,但是对于它的高度灵活性,简直让人令人惊叹不已,剧赞. 这个小小的函数源自于和爱明兄的一次讨论:在javascri

在.NET项目中使用PostSharp,实现AOP面向切面编程处理

PostSharp是一种Aspect Oriented Programming 面向切面(或面向方面)的组件框架,适用在.NET开发中,本篇主要介绍Postsharp在.NET开发中的相关知识,以及一些如日志.缓存.事务处理.异常处理等常用的切面处理操作. AOP(Aspect-Oriented Programming)是一种将函数的辅助性功能与业务逻辑相分离的编程泛型(programming paradigm),其目的是将横切关注点(cross-cutting concerns)分离出来,使得

AOP技术研究--AOP概述

1 前言 1.1 软件编程技术发展 软件编程技术与程序设计语言是分不开的.过去的几十年中,程序设计语言对抽象机制的支持程度不断提高:从机器语言到汇编语言,到高级语言,再到面向对象语言.每一种新的程序设计语言的出现都带来软件编程方法的飞跃.汇编语言出现后,开发人员避免了直接使用0-l编码,而是利用符号来表示机器指令,从而更方便地编写程序.当程序规模继续增大的时候,出现了以Fortran.C.Pascal等为代表的高级语言,这些高级语言使得编写复杂的程序变得容易,开发人员可以更好地应付日益复杂的代码

[SpringBoot]深入浅出剖析SpringBoot中Spring Factories机制

微信号:GitShare微信公众号:爱折腾的稻草如有问题或建议,请在公众号留言[1] 前续 为帮助广大SpringBoot用户达到"知其然,更需知其所以然"的境界,作者将通过SpringBoot系列文章全方位对SpringBoot2.0.0.RELEASE版本深入分解剖析,让您深刻的理解其内部工作原理. 1.[SpringBoot]利用SpringBoot快速构建并启动项目 2.[SpringBoot]详解SpringBoot应用的启动过程 3.[SpringBoot]深入浅出剖析Sp

记录一下在SpringBoot中实现简单的登录认证

代码参考博客: https://blog.csdn.net/weixin_37891479/article/details/79527641 在做学校的课设的时候,发现了安全的问题,就不怀好意的用户有可能跳过登录直接访问系统的界面和使用里面的功能,于是想为系统加个安全验证.现在常用的安全框架我知道的就是Shiro还有SpringSecurity,但这次我不打算用框架,采用拦截器filter和session技术实现了一个基于session的登录认证. 先简单地说一下原理: session和cook

springboot中使用自定义线程池ThreadPoolTaskExecutor

java5以后,线程有了很大的变化,在使用上更加方便功能更佳强大,Springboot里面进行了进一步的封装. 我们来看一个例子 package com.executor; import java.util.concurrent.Executor;import java.util.concurrent.RejectedExecutionHandler;import java.util.concurrent.ThreadPoolExecutor; import org.springframewor