一、AspectJ
spectJ是Java的一个AOP框架,可以单独使用,也可以整合到其它框架中。
AspectJ是Eclipse旗下的一个项目。至于它和Spring AOP的关系,不妨可将Spring AOP看成是Spring这个庞大的集成框架为了集成AspectJ而出现的一个模块。
毕竟很多地方都是直接用到AspectJ里面的代码。典型的比如 @Aspect
,@Around
,@Pointcut
注解等等。而且从相关概念以及语法结构上而言,两者其实非常非常相似。比如 Pointcut
的表达式语法以及 Advice
的种类,都是一样一样的。
与 Spring AOP 最大的区别在于两者实现AOP的底层原理不太一样:
Spring AOP
: 基于代理(Proxying
)AspectJ
: 基于字节码操作(Bytecode Manipulation
)
织入方式
AspectJ
提供了三种方式来实现 AOP
,通过在类加载的不同时间段来完成相关代码的织入以达到目的。具体有以下三种方式:
- 编译期(
compiler-time
)织入:在类进行编译的时候就将相应的代码织入到元类文件的.class
文件中 - 编译后(
post-compiler
)织入:在类编译后,再将相关的代码织入到.class
文件中 - 加载时(
load-time
) 织入:在JVM
加载.class
文件的时候将代码织入
springAOP使用运行时织入(runtime weaving)
- 在运行时织入,是使用目标对象的代理对象织入的。
AspectJ代码织入原理
其实就是在编译时,修改源代码,在对应的方法最前面加上织入的代码
1、ajc 增强
先看aop
的第一种实现ajc
编译器代码增强,这是一种编译时的代码增强。
新建一个普通的maven项目
添加依赖
使用
ajc
编译器进行代码增强,首先需要在pom.xml
文件中加入ajc
编译器插件依赖
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</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>
加入aspectjweaver
的依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
需要增强的类MyService
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
public void foo() {
log.debug("foo()");
}
}
切面类MyAspect
,编写execution
表达式,对MyService
类的foo()
方法进行增强
@Aspect // 注意此切面并未被 Spring 管理,本项目pom文件中根本没有引入spring的相关类
public class MyAspect {
private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
@Before("execution(* top.jacktgq.service.MyService.foo())")
public void before() {
log.debug("before()");
}
}
测试代码
public class Aop_Aspectj_Test {
@Test
public void testAopAjc() {
new MyService().foo();
}
}
编译项目,这里需要使用maven
来编译,打开idea
中的maven
面板,点击compile
然后再运行测试代码,可以看到创建MyService
对象并调用foo()
方法会先执行切面类中的before()
方法
运行以后代码并没有增强。这是由于idea中在执行代码之前会默认编译一遍代码,这本来是正常的,可是,如果使用maven来编译代码,会在执行代码前将maven编译的代码覆盖,这就会导致maven的ajc编译器增强的代码被覆盖,所以会看不到最终的运行效果。
解决办法:在idea设置-编译器中将自动构建项目的选项勾上,就不会出现多次编译覆盖的问题了。
反编译class文件
打开编译后的MyService.class
文件,双击以后idea会反编译该字节码文件,可以看到foo()
方法体的开头加了一行代码,这就是增强的代码,这是ajc
编译器在编译MyService
类的时候为我们添加的代码,这是一种编译时的增强。
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
public void foo() {
MyAspect.aspectOf().before();//自动帮我们添加的代码
log.debug("foo()");
}
}
2、agent增强
现在来看aop
的另外一种实现agent
增强,这是一种类加载时的代码增强。
- 新建一个普通的maven项目
- 加入
aspectjweaver
的依赖
- 加入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
需要增强的类MyService
@Slf4j
public class MyService {
public void foo() {
log.debug("foo()");
bar();
}
public void bar() {
log.debug("bar()");
}
}
切面类MyAspect
,编写execution
表达式,对MyService
类的foo()
方法进行增强
@Aspect // 注意此切面并未被 Spring 管理,本项目pom文件中根本没有引入spring的相关类
@Slf4j
public class MyAspect {
@Before("execution(* top.jacktgq.service.MyService.*())")
public void before() {
log.debug("before()");
}
}
测试代码
public class Aop_agent_Test {
@Test
public void testAopAgent() throws Exception {
MyService myService = new MyService();
myService.foo();
System.in.read();
}
}
运行时需要在 VM options 里加入 -javaagent:jar包路径\aspectjweaver-1.9.7.jar
注:还需要在resources/META-INF
目录下建一个aop.xml
配置文件,内容如下,aspectj
会自动扫描到这个配置文件,不加这个配置文件不会出效果。
<aspectj>
<aspects>
<aspect name="top.jacktgq.aop.MyAspect"/>
<weaver options="-verbose -showWeaveInfo">
<include within="top.jacktgq.service.MyService"/>
<include within="top.jacktgq.aop.MyAspect"/>
</weaver>
</aspects>
</aspectj>
运行测试代码,可以看到创建MyService
对象并调用foo()
方法会先执行切面类中的before()
方法
打开编译后的MyService.class文件,双击以后idea会反编译该字节码文件,可以看到foo()方法体中并没有添加多余的代码,所以就不是编译时增强了,而是类加载的时候增强的,这里可以借助阿里巴巴的Arthas工具,下载地址:https://arthas.aliyun.com/doc/en/download.html,解压以后进入到arthas的bin目录下,启动黑窗口,输入java -jar .\arthas-boot.jar,在输出的java进程列表里面找到我们要连接的进程,输入对应进程的序号,我这里是,连接上以后会打印ARTHAS的logo
再输入jad top.jacktgq.service.MyService
反编译内存中的MyService
类
可以看到foo()
和bar()
方法体的第一行都加了一行代码,这就说明通过添加虚拟机参数-javaagent
的方式可以在类加载的时候对代码进行增强。
所以这种方法的织入,即使是在本类的方法中通过 this.本类其他方法
也可以被增强。而代理方法织入的则不行。
二、Advisor切面
Spring 提供Advisor
切面和 @Aspect
切面:
Advisor
低级切面:更偏向于底层,在Spring源码中使用@Aspect
高级切面:是高度封装之后的,更适用于普通开发的使用,其底层也是使用Advisor
1、接口及目标类实现
先创建一个包含又foo和bar方法的接口,再创建一个Target1目标类实现该接口
interface I1 {
void foo();
void bar();
}
static class Target1 implements I1 {
public void foo() {
System.out.println("target1 foo");
}
public void bar() {
System.out.println("target1 bar");
}
}
static class Target2 {
public void foo() {
System.out.println("target2 foo");
}
public void bar() {
System.out.println("target2 bar");
}
}
2、准备切点
创建切点对象,调用setExpression设置切点表达式
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
3、准备通知
MethodInterceptor advice = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before...");
Object res = invocation.proceed();//调用目标
System.out.println("after...");
return res;
}
};
4、准备切面
创建DefaultPointcutAdvisor
对象,传入切点和通知
然后再创建ProxyFactory
对象,添加目标对象和切面,最后通过代理对象调用方法完成测试
(ProxyFactory
内部会根据不同的情况选择cglib或者jdk的代理)
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
ProxyFactory factory = new ProxyFactory();
factory.setTarget(new Target1());//添加目标
factory.addAdvisor(advisor);//添加切面
I1 proxy = (I1) factory.getProxy();
proxy.foo();
proxy.bar();
5、选择代理的方式
ProxyFactory
内部存在一个成员变量proxyTargetClass
决定spring的代理方式
proxyTargetClass = false
:如果目标实现接口,则用 jdk 动态代理proxyTargetClass = false
:如果目标没实现接口,则用 cglib 动态代理proxyTargetClass = true
:无论如何都用 cglib 动态代理
(1)情况1
proxyTargetClass = false
,而且目标实现了接口, 使用 JdkDynamicAopProxy
实现(默认proxyTargetClass
为alse)
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1);//添加目标
factory.addAdvisor(advisor);//添加切面
factory.setInterfaces(target1.getClass().getInterfaces());//添加目标类上实现的接口
//factory.setProxyTargetClass(false);
I1 proxy = (I1) factory.getProxy();
proxy.foo();
proxy.bar();
(2)情况2
proxyTargetClass = false
,而且目标没有接口, 使用 ObjenesisCglibAopProxy
实现
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1);//添加目标
factory.addAdvisor(advisor);//添加切面
//factory.setProxyTargetClass(false);
I1 proxy = (I1) factory.getProxy();
proxy.foo();
proxy.bar();
(3)情况3
proxyTargetClass = true
总是使用 ObjenesisCglibAopProxy
实现
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,advice);
Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1);//添加目标
factory.addAdvisor(advisor);//添加切面
factory.setProxyTargetClass(true);
I1 proxy = (I1) factory.getProxy();
proxy.foo();
proxy.bar();
6、切点匹配
先定义一个静态类T1,其中有用 @Transactional
注解的foo方法和一个普通的bar方法
再定义一个使用 @Transactional
注解标注的静态类T2
然后再定义一个用 @Transactional
注解标注的接口,内有方法foo,最后创建一个类实现该接口
static class T1 {
@Transactional
public void foo() { }
public void bar() { }
}
@Transactional
static class T2 {
public void foo() { }
}
@Transactional
interface I3 { void foo(); }
static class T3 implements I3 {
public void foo() {
}
}
1、创建AspectJExpressionPointcut对象,然后调用setExpression方法,在其中填写切点表达式
2、调用matches方法判断是否与切点表达式匹配,第一个参数为对应类的方法名,第二个参数为对应类的class
public static void main(String[] args) throws NoSuchMethodException {
//判断方法是否与切点表达式匹配
AspectJExpressionPointcut pointcut1 = new AspectJExpressionPointcut();
pointcut1.setExpression("execution(* bar())");
System.out.println(pointcut1.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pointcut1.matches(T1.class.getMethod("bar"), T1.class));
//判断方法上的注解类型是否与切点表达式定义的类型匹配
AspectJExpressionPointcut pointcut2 = new AspectJExpressionPointcut();
pointcut2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
// 切点匹配
boolean b1 = pointcut2.matches(T1.class.getMethod("foo"), T1.class)
boolean b2 = pointcut2.matches(T1.class.getMethod("bar"), T1.class)
}
3、为了保证当出现T3类的情况时(实现了被注解标注的接口)@Transactional
注解能够被匹配到,在spring内部并不是采用以上方法匹配@Transactional
注解,而是创建StaticMethodMatcherPointcut对象并重写matches方法,在方法内部添加匹配注解的逻辑
//匹配@Transactional注解
StaticMethodMatcherPointcut pointcut3 = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
//检查方法上是否添加@Transactional注解
MergedAnnotations annotations = MergedAnnotations.from(method);
if (annotations.isPresent(Transactional.class)) {
return true;
}
//检查类上是否添加@Transactional注解,并添加检查策略
annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
if (annotations.isPresent(Transactional.class)){
return true;
}
return false;
}
};
//测试
//true
System.out.println(pointcut3.matches(T1.class.getMethod("foo"), T1.class));
//false
System.out.println(pointcut3.matches(T1.class.getMethod("bar"), T1.class));
//true
System.out.println(pointcut3.matches(T2.class.getMethod("foo"), T2.class));
//true
System.out.println(pointcut3.matches(T3.class.getMethod("foo"), T3.class));
MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
中 SearchStrategy
取值如下
7、注册到容器
@Configuration
public class AdvisorConfig {
//低级切面:由一个切点和一个通知组成
@Bean
public Advisor advisor3(MethodInterceptor advice3){
//定义切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
return advisor;
}
//定义通知
@Bean
public MethodInterceptor advice3() {
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable{
System.out.println("advice3 before");
Object res = invocation.proceed();
System.out.println("advice3 after");
return res;
}
};
}
}
三、自动代理后处理器
自动代理后处理器 AnnotationAwareAspectJAutoProxyCreator
作用:
- 找到所有的 高级切面
@Aspect
和低级切面Advisor
,并将高级切面转化为低级切面 - 根据这些切面创建代理对象
通常代理创建的活在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行
1、findEligibleAdvisors()
来自 AbstractAdvisorAutoProxyCreator
的方法
解析某个类的切面,一部分切面是低级的,如准备代码中的advisor3;另一部分是高级的, 由解析 @Aspect 后获得。解析时会将高级切面解析成多个低级切面
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass,
String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors =
findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}