目录
一、对比
关于两者差异分析的博客很多,不展开分析了,直接从实践了解2个直观的差异:.class文件、运行时被代理对象的类型。
spring-aop | aspectj | |
实现代理的方式 | 动态代理: 运行时织入 | 静态代理: ① 编译期织入:把切面类和目标类放在一起用ajc编译; ② 编译后织入:已生成.class文件或打成jar包,再做增强处理; ③ 类加载时织入:在jvm加载目标类的时候,做字节码的替换 |
.class文件(idea查看) | 包含ajc编译产生的内容,以Capitalist类为例:
| 与对应的.java文件内容相同,以Capitalist类为例: |
运行时被代理对象的类型 | ![]() | ![]() |
以下介绍如何实现spring-aop、aspectj代理。先强调一点,并不是使用了@Aspect、@Pointcut注解就意味着使用的是aspectj代理,这只是spring框架借助aspectj的注解标识bean。
二、spring-aop
demo项目结构如下:
切面类:
package com.yeleits.test.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MonitorAspect {
@Pointcut("@annotation(com.yeleits.test.aspect.Monitor)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint jp) throws Throwable {
String className = jp.getSignature().getDeclaringType().getSimpleName();
String methodName = jp.getSignature().getName();
long start = System.currentTimeMillis();
try {
return jp.proceed();
} catch (Throwable e) {
throw e;
} finally {
long duration = System.currentTimeMillis() - start;
System.out.println(className + "." + methodName + "执行时间:" + duration + "ms");
}
}
}
package com.yeleits.test.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitor {
}
业务相关(2个类、1个接口,分3个文件):
package com.yeleits.test.pojo;
import com.yeleits.test.aspect.Monitor;
public class Capitalist {
private int assets = 999999999;
@Monitor
public void work() {
System.out.println("$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $");
}
public int getAssets() {
return assets;
}
}
public interface Person {
void work();
}
public class Worker implements Person {
@Monitor
@Override
public void work() {
System.out.println("* * * * * 搬砖 * * * * *");
}
}
执行逻辑:
package com.yeleits.test;
import com.yeleits.test.pojo.Capitalist;
import com.yeleits.test.pojo.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args){
ApplicationContext ac = new ClassPathXmlApplicationContext("aopannotation.xml");
Person worker = (Person) ac.getBean("worker");
worker.work();
Capitalist capitalist = (Capitalist) ac.getBean("capitalist");
capitalist.work();
}
}
spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean class="com.yeleits.test.aspect.SleepAspect"></bean>
<bean id="worker" class="com.yeleits.test.pojo.Worker"/>
<bean id="capitalist" class="com.yeleits.test.pojo.Capitalist"/>
</beans>
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yeleits.test</groupId>
<artifactId>aop-aspectj</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
</dependencies>
</project>
执行结果:
* * * * * 搬砖 * * * * *
Person.work执行时间:1ms
$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
Capitalist.work执行时间:13ms
资产:999999999
demo实现的功能是:对被@Monitor标注的方法实现耗时监控。
tips:MainApp中如果使用BeanFactory而不是ApplicationContext,不会实现代理,这是因为ApplicationContext启动时会扫描实现BeanPostProcessor接口的bean(参考:spring系列4-aop的实现-二-2)
三、aspectj
1、编译期织入(无lombok)
(1)基于Intellij编译
基于上述demo代码,增加两步配置:
第1步:配置编译器 | 第2步:配置facets |
![]() | ![]() |
清除之前编译的target文件夹,重新运行:
* * * * * 搬砖 * * * * *
Worker.work执行时间:0ms
$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
Capitalist.work执行时间:0ms
Capitalist.work执行时间:0ms
资产:999999999
可以看出切面起作用了,而且基于aspectj的capitalist代理的性能远快于基于cglib的spring-aop。但是capitalist类的work时间统计了两次,似乎是aspectj编译器的bug(参考https://blog.csdn.net/u011116672/article/details/63685340)。更换切点表达式,用非注解方式实现,不会出现问题:
@Pointcut("execution(* *.work(..))")
public void pointCut() {
}
tips1:采用非注解、注解的切面表达式,产生的MainApp.class文件不同,所以导致2次执行。
tips2:为什么spring-aop方式织入切面,worker和capitalist实际类型不同?简而言之,因为worker实现了接口类,是基于JDK的动态代理机制实现代理;capitalist没有实现接口,采用cglib方式实现代理。(参考:spring系列4-aop的实现)
(2)基于maven编译
基于上述demo代码,增加插件aspectj-maven-plugin:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.10</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
执行mvn clean compile,根据执行日志、生成的.class文件可以看出,成功完成了切面织入
2、编译后织入(解决lombok冲突)
(1)引入lombok依赖,并使用:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
package com.yeleits.test.pojo;
import com.yeleits.test.aspect.Monitor;
import lombok.Data;
@Data
public class Capitalist {
private int assets = 999999999;
@Monitor
public void work() {
System.out.println("$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $");
}
}
package com.yeleits.test;
import com.yeleits.test.pojo.Capitalist;
import com.yeleits.test.pojo.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args){
ApplicationContext ac = new ClassPathXmlApplicationContext("aopannotation.xml");
Person worker = (Person) ac.getBean("worker");
worker.work();
Capitalist capitalist = (Capitalist) ac.getBean("capitalist");
capitalist.work();
System.out.println("资产:" + capitalist.getAssets());
}
}
(2)尝试编译
基于Intellij编译报错:
Error:(16, 0) ajc: The method getAssets() is undefined for the type Capitalist
基于maven编译报错:
(3)增加aspectjrt依赖、修改插件aspectj-maven-plugin配置:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<build>
<plugins>
<!--引入aspectj compiler插件-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.10</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
<forceAjcCompile>true</forceAjcCompile>
<sources/>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<phase>process-classes</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<weaveDirectories>
<weaveDirectory>${project.build.directory}/classes</weaveDirectory>
</weaveDirectories>
</configuration>
</execution>
<execution>
<id>default-testCompile</id>
<phase>process-test-classes</phase>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<weaveDirectories>
<weaveDirectory>${project.build.directory}/test-classes</weaveDirectory>
</weaveDirectories>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
执行mvn clean process-classes,根据执行日志、生成的.class文件可以看出,成功完成了切面织入
为了通过idea执行编译后的.class文件,在run或者debug前,可以先删除运行前编译,避免idea错误地编译覆盖: