目录
一、对比
二、spring-aop
三、aspectj
1、编译期织入(无lombok)
(1)基于Intellij编译
(2)基于maven编译
2、编译后织入(解决lombok冲突)
关于两者差异分析的博客很多,不展开分析了,直接从实践了解2个直观的差异:.class文件、运行时被代理对象的类型。
spring-aop | aspectj | |
实现代理的方式 | 动态代理: 运行时织入 |
静态代理: ① 编译期织入:把切面类和目标类放在一起用ajc编译; ② 编译后织入:已生成.class文件或打成jar包,再做增强处理; ③ 类加载时织入:在jvm加载目标类的时候,做字节码的替换 |
.class文件(idea查看) | 包含ajc编译产生的内容,以Capitalist类为例:
|
|
运行时被代理对象的类型 |
以下介绍如何实现spring-aop、aspectj代理。先强调一点,并不是使用了@Aspect、@Pointcut注解就意味着使用的是aspectj代理,这只是spring框架借助aspectj的注解标识bean。
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配置文件:
pom.xml:
4.0.0
com.yeleits.test
aop-aspectj
1.0-SNAPSHOT
org.springframework
spring-context
5.3.2
org.springframework
spring-beans
5.3.2
org.springframework
spring-aop
5.3.2
org.aspectj
aspectjweaver
1.7.4
执行结果:
* * * * * 搬砖 * * * * *
Person.work执行时间:1ms
$ ¥ $ ¥ $ 数钱 $ ¥ $ ¥ $
Capitalist.work执行时间:13ms
资产:999999999
demo实现的功能是:对被@Monitor标注的方法实现耗时监控。
tips:MainApp中如果使用BeanFactory而不是ApplicationContext,不会实现代理,这是因为ApplicationContext启动时会扫描实现BeanPostProcessor接口的bean(参考:spring系列4-aop的实现-二-2)
基于上述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的实现)
基于上述demo代码,增加插件aspectj-maven-plugin:
org.codehaus.mojo
aspectj-maven-plugin
1.10
1.8
1.8
compile
test-compile
执行mvn clean compile,根据执行日志、生成的.class文件可以看出,成功完成了切面织入
(1)引入lombok依赖,并使用:
org.projectlombok
lombok
1.16.20
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配置:
org.aspectj
aspectjrt
1.8.9
org.codehaus.mojo
aspectj-maven-plugin
1.10
1.8
1.8
true
true
ignore
UTF-8
true
default-compile
process-classes
compile
${project.build.directory}/classes
default-testCompile
process-test-classes
test-compile
${project.build.directory}/test-classes
执行mvn clean process-classes,根据执行日志、生成的.class文件可以看出,成功完成了切面织入
为了通过idea执行编译后的.class文件,在run或者debug前,可以先删除运行前编译,避免idea错误地编译覆盖: