第九讲--AOP实现之AspectJ(编译器增强)

第九讲-AOP实现之AspectJ(编译器增强)

该讲使用AspectJ来演示AOP的动态代理(我这里使用JDK1.8来演示)

首先导入相关依赖:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
</dependency>

并加入相关配置(从官网拷贝过来的,大致就是编译出来的目标类版本等等):

<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>

编写一个服务类:

package com.cherry.chapter2.a09;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    public void foo(){
        log.debug("foo()");
    }
}

编写一个切面类,为MyService的foo方法做前置增强

@Aspect
public class MyAspect {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    @Before("execution(* com.cherry.chapter2.a09.MyService.foo())")
    public void before(){
        log.debug("before()");
    }
}

注意将该类所在的模块使用maven工具编译一下,而不是使用IDEA的编译按钮,如下面的所示(我的是模块chapter2):

image-20240726183507751

然后编写主方法直接运行测试:

@SpringBootApplication
public class A09Application {
    private static final Logger log = LoggerFactory.getLogger(A09Application.class);
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A09Application.class);
        MyService service = new MyService();
        log.debug("service class:{}",service.getClass());
        service.foo();
        context.close();
    }
}

运行结果如下:

2024-07-26 18:29:00.109 DEBUG 7444 --- [           main] com.cherry.chapter2.a09.A09Application   : service class:class com.cherry.chapter2.a09.MyService
2024-07-26 18:29:00.110 DEBUG 7444 --- [           main] com.cherry.chapter2.a09.MyService        : before()
2024-07-26 18:29:00.111 DEBUG 7444 --- [           main] com.cherry.chapter2.a09.MyService        : foo()

我们发现这个做增强的就是这个本类的对象,而不是所谓的xxxcgLibProxyXXX(也就是说做前置增强并没有使用到代理),这说明了AOP的实现不止代理。

此处的代理使用的是AspectJ编译器增强,这个增强的的逻辑是把我们的目标类的字节码文件进行了改写,并且加入了前置增强,如下面就是改写后的MyService.class:

public class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    public MyService() {
    }
    
    public void foo() {
        MyAspect.aspectOf().before();
        log.debug("foo()");
}

我们发现,编译后的目标类的方法前多了一个方法的调用,该方法的作用就是找到对应的前置通知,然后调用方法通知方法。

既然classs都被改写了,那还需要目标代理吗?就不需要了,况且也和Sprign容器也就没什么关系了,注意看我写的目标类,类上并没有加上@Component注解。这里来演示一下,不使用容器直接运行测试一下:

package com.cherry.chapter2.a09;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class A09Application {
    private static final Logger log = LoggerFactory.getLogger(A09Application.class);
    public static void main(String[] args) {
//        ConfigurableApplicationContext context = SpringApplication.run(A09Application.class);
//        MyService service = new MyService();
        new MyService().foo();
//        log.debug("service class:{}",service.getClass());
//        service.foo();
//        context.close();
    }
}
18:44:52.233 [main] DEBUG com.cherry.chapter2.a09.MyService - before()
18:44:52.238 [main] DEBUG com.cherry.chapter2.a09.MyService - foo()

我们发现,不借助Spring容器也实现了目标类的功能增强!

这种使用AspectJ做方法增强有什么好处呢?可以突破一些代理的限制,因为代理本质上是通过方法重写来实现的,那么目标类是一个静态方法,动态代理能做增强吗?显然是不能的,但是AspectJ编译器增强是可以的,例如(注意使用maven cmopile重新编译一下):

public class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    public static void foo(){
        log.debug("foo()");
    }
}
18:50:57.741 [main] DEBUG com.cherry.chapter2.a09.MyService - before()
18:50:57.745 [main] DEBUG com.cherry.chapter2.a09.MyService - foo()

我们发现,使用AspectJ编译器增强是可以实现静态方法的功能增强的。最后我们再看一下目标类反编译后的字节码文件:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
public class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    public MyService() {
    }

    public static void foo() {
        MyAspect.aspectOf().before();
        log.debug("foo()");
    }
}
posted @   LilyFlower  阅读(32)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示