引言:
本人之前对于AOP的理解知之甚少,但是却比较好奇其中的原理,所以就花时间学习了一下,接下里就给大家介绍一下。
本篇会详细介绍AOP增强的各个功能实现原理,但并不涉及源码解读,只是对每个功能实现原理进行讲解,内容有点长,耐心看完一定会有收获。
加油!!!
AOP 增强
proxy 代理增强
jdk动态代理
jdk 动态代理的主要原理是根据接口来创建代理。创建出来的代理类和目标类是实现了同一个接口的,然后对接口的方法,相较于目标类来说进行了增强。所以这里也可以总结出来:
- 目标类和代理类是实现了相同的接口(两个的关系比作兄弟)
- 不用管目标类的方法和类是否被
final
修饰,代理类都可以实现代理(这跟后面的 cglib 实现是不一样的)
接下来调用一下 jdk 的动态代理:
interface Foo{
void foo();
}
static class Target implements Foo{
@Override
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] args) {
Target target = new Target();
ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();
Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(target, args);
System.out.println("after");
return result;
}
});
proxy.foo();
}
jdk 的动态代理主要通过newProxyInstance
方法用来创建代理:
参数一ClassLoader
:类加载器,因为不管是 jdk 还是 cglib 实现的代理都是在类加载的阶段创建出来的代理(没有源码),所以需要一个类加载器。
参数二Class[]
:实现的接口集合,因为 jdk 是通过接口来实现的代理,所以需要几口集合。
参数三InvocationHandler
:代表对代理类的约束,规定了代理类需要怎么执行方法,所以创建一个匿名内被类让开发者自己写如何实现代理。
- invoke 方法的三个参数:
- proxy:代理对象
- method:目标方法
- args:方法参数
想要执行目标方法,就可以在InvocationHandler
中调用method
反射调用目标方法,我们外部需要创建一个目标类,进行反射。
最后返回的对象,由于代理也实现了接口所以可以直接强转成接口类。
原理
这里我进行模拟jdk是怎么实现动态代理的,是怎么设计的。首先准备一个接口和类
interface Foo{
void foo();
}
static class Target implements Foo{
@Override
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] args) {
}
然后我们模仿jdk动态代理的样子创建一个代理类$Proxy0
,实现接口。
public class $Proxy0 implements Foo {
@Override
public void foo() {
// 方法增强
// 调用目标方法
}
}
我们实现的接口目的就只有两个:方法增强和调用目标方法。
所以我们可以简单的实现一下:
public class $Proxy0 implements Foo {
@Override
public void foo() {
// 方法增强
System.out.println("before...");
// 调用目标方法
new Target().foo();
}
}
进行调用查看一下:
public static void main(String[] args) {
Foo proxy = new $Proxy0();
proxy.foo();
}
因为实现了Foo
接口,所以直接转化为接口类型了
before...
target foo
我们发现就实现了代理,但是这里面就有个问题:
这个增强的逻辑被我们写死了,只能这样增强,但是事实上增强的逻辑千变万化,无穷无尽我们就这样写死了就只对应的一种方法,而且有的时候我们不一定会调用目标,情况很多变。
所以这里我们应该把这部分抽象出来,不应该直接在方法内部写死。
我们在创建一个接口invocationHandler
:
interface InvocationHandler{
void invoke();
}
然后让代理类在内部创建这个对象
并且通过构造函数进行初始化,然后让代理方法进行调用,而具体实现什么样的增强逻辑,在创建对象时让开发者自己填入进来。
代理对象变成这样:
public class $Proxy0 implements Foo {
private InvocationHandler handler;
public $Proxy0(InvocationHandler handler) {
this.handler = handler;
}
@Override
public void foo() {
handler.invoke();
}
}
然后我们创建对象时,new
一个匿名内部类,然后填写自己的逻辑。
public static void main(String[] args) {
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public void invoke() {
// 方法增强
System.out.println("before...");
// 调用目标方法
new Target().foo();
}
});
proxy.foo();
}
最后调用一下:
被增强了
before...
target foo
这样就成功把要处理的增强逻辑抽出来让对应的开发者去写,而代理类就不需要考虑那么多情况了,只需要创建代理即可。
但是这样并没有完,还是会存在问题:
这时如果我们的Foo
接口再创建一个bar
方法看看会出现什么样的情况:
// 接口
interface Foo{
void foo();
void bar();
}
interface InvocationHandler{
void invoke();
}
// 目标类
static class Target implements Foo{
@Override
public void foo() {
System.out.println("target foo");
}
@Override
public void bar() {
System.out.println("target bar");
}
}
// 代理类
public class $Proxy0 implements Foo {
private InvocationHandler handler;
public $Proxy0(InvocationHandler handler) {
this.handler = handler;
}
@Override
public void foo() {
handler.invoke();
}
@Override
public void bar() {
handler.invoke();
}
}
我们调用一下bar
方法:
// 调用方法
proxy.bar();
// 结果
before...
target foo
before...
target foo
我们发现虽然被增强了,但是被增强的还是foo
方法,这是因为我们在invoke
方法里面写的逻辑就是只有增强foo
方法,这也是写死的。所以我们为了解决这个问题,我们应该通过参数把方法和方法参数给传进来,这样invoke
的方法就不需要考虑具体怎么区分和调用方法了,只需要对方法的内容增强即可。
我们给invoke
方法添加两个参数:
然后代码修改为这样:
// 抽象方法
interface InvocationHandler{
void invoke(Method method, Object[] args) throws Throwable;
}
// 代理类
public class $Proxy0 implements Foo {
private InvocationHandler handler;
public $Proxy0(InvocationHandler handler) {
this.handler = handler;
}
@Override
public void foo() {
try {
Method foo = Foo.class.getMethod("foo");
handler.invoke(foo, new Object[0]);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public void bar() {
try {
Method bar = Foo.class.getMethod("bar");
handler.invoke(bar, new Object[0]);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
// 创建代理
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
// 方法增强
System.out.println("before...");
// 调用目标方法
method.invoke(new Target(), args);
}
});
最后我们在调用一下:
// 调用方法
proxy.foo();
proxy.bar();
// 结果
before...
target foo
before...
target bar
我们可以发现,两个方法都被增强了。
接下来就是处理返回值,有可能我们的方法还存在着返回值,所以我们需要进行处理。
例如我们给bar
方法增加一个返回值:
// 接口
interface Foo{
void foo();
int bar();
}
// 目标类
static class Target implements Foo{
@Override
public void foo() {
System.out.println("target foo");
}
@Override
public int bar() {
System.out.println("target bar");
return 100;
}
}
这时代理类也需要添加返回值。
但是代理类调用的invoke
方法,所以应该给invoke
方法添加返回值,但是每个方法都是调用的invoke
方法,所以我们应该给invoke
方法添加一个Object
方法的返回值,这样每个方法都可以使用这个。
// InvocationHandler
interface InvocationHandler{
Object invoke(Method method, Object[] args) throws Throwable;
}
// 生成代理
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public Object invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
// 方法增强
System.out.println("before...");
// 调用目标方法
return method.invoke(new Target(), args);
}
});
// 代理类的方法
public int bar() {
try {
Method bar = Foo.class.getMethod("bar");
return (int)handler.invoke(bar, new Object[0]);
} catch (Throwable e) {
e.printStackTrace();
}
}
这样就完成了返回值的处理。
但是bar
方法正常情况下有返回值,异常情况下却没有返回值,所以我们需要处理一下异常。
jdk动态代理在Spring
底层分为异常进行处理,一种是运行时异常,一种是检查时异常。
@Override
public void foo() {
try {
Method foo = Foo.class.getMethod("foo");
handler.invoke(foo, new Object[0]);
} catch (RuntimeException | Error e){
throw e;
}catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public int bar() {
try {
Method bar = Foo.class.getMethod("bar");
return (int)handler.invoke(bar, new Object[0]);
}catch (RuntimeException | Error e){
throw e;
}catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
还有问题,就是我们现在每次获取方法都是在调用时才进行获取的,每次调用都获取一次,我们可以先获取好然后每次调用的时候直接用就好了。
static Method bar;
static Method foo;
static {
try {
bar = Foo.class.getMethod("bar");
foo = Foo.class.getMethod("foo");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
其实这里还缺一些东西,在源码中还有一个代理类的参数proxy
,我们也来加一下,然后再调用的时候,因为就是在代理类中调用,所以直接传this
即可。
interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
@Override
public void foo() {
try {
handler.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e){
throw e;
}catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public int bar() {
try {
return (int)handler.invoke(this, bar, new Object[0]);
}catch (RuntimeException | Error e){
throw e;
}catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
最后在源码中其实代理类继承了一个Proxy
父类,然后父类里面维护了一个InvocationHandler
和构造方法,所以我们直接继承Proxy
即可。
public class $Proxy0 extends Proxy implements Foo {
public $Proxy0(InvocationHandler handler) {
super(handler);
}
static Method bar;
static Method foo;
static {
try {
bar = Foo.class.getMethod("bar");
foo = Foo.class.getMethod("foo");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
@Override
public void foo() {
try {
this.h.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e){
throw e;
}catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public int bar() {
try {
return (int)this.h.invoke(this, bar, new Object[0]);
}catch (RuntimeException | Error e){
throw e;
}catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
最后调用:
before...
target foo
before...
target bar
正常调用。
以上就是模拟源码写的代码。
cglib动态代理
cglib 的动态代理,底层是通过继承父类来实现的,所以代理类相当于是目标类的子类,然后代理类通过重写父类的方法进行对方法的增强。所以从这里我们可以总结出来:
- cglib 代理是通过继承父类实现的(两者的关系可以比作父子)
- 当目标类或目标方法被
final
修饰之后,就无法进行增强。
接下来我们调用一下cglib
动态代理
static class Target{
public void foo(){
System.out.println("target foo");
}
}
public static void main(String[] args) {
Target target = new Target();
Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object result = method.invoke(target, args);
// Object o = methodProxy.invokeSuper(p, args);
System.out.println("after");
return result;
}
});
proxy.foo();
}
cglib 的动态代理是通过Enhancer
的create
方法进行创建的:
第一个参数target
:目标类的class
对象
第二个参数MethodInterceptor
:与InvocationHandler
的作用相近,可以理解为就是来开发者自己限制方法是如何被调用(增强)。
intercept
的方法参数p
:代理类method
:目标方法args
:方法参数MethodProxy
:后面进行介绍(可以用来执行目标方法)
想要执行目标方法这里一共有三个方法可以使用:
- 通过反射调用(效率比较低)
- 通过
MethodProxy
的invoke
方法直接正向调用目标类的方法(需要目标类参数) - 通过
MethodProxy
的invokeSuper
方法直接正向调用目标类的方法(需要代理类参数)
最后的返回类,由于是子类所以也可以直接强转成父类进行调用。
原理
接下来我们也像jdk一样模拟一下源码的调用。这次我们就直接一点,其实cglib和jdk差不多,只不过cglib里面维护了一个MethodInterceptor
。然后通过继承父类重写方法即可。
目标类
public class Target {
public void save(){
System.out.println("save()");
}
public void save(int i){
System.out.println("save(int i)");
}
public void save(long i){
System.out.println("save(long i)");
}
}
代理类
public class Proxy extends Target{
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static Method save0;
static Method save1;
static Method save2;
static{
try {
save0 = Target.class.getMethod("save");
save1 = Target.class.getMethod("save", int.class);
save2 = Target.class.getMethod("save", long.class);
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
@Override
public void save() {
try {
methodInterceptor.intercept(this, save0, new Object[0], null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, save1, new Object[]{i}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(long i) {
try {
methodInterceptor.intercept(this, save2, new Object[]{i}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
测试类
public static void main(String[] args) {
Target target = new Target();
Proxy proxy = new Proxy();
proxy.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
return method.invoke(target, args);
}
});
proxy.save();
proxy.save(1);
proxy.save(2L);
}
输出结果:
before
save()
before
save(int i)
before
save(long i)
Process finished with exit code 0
我们发现这里面interceptor
方法的最后一个参数MthodProxy
我们填的是null
,这个参数主要的作用就是避免反射调用,而是直接调用方法。
接下来我们就来讲讲这个方法的原理:
MthodProxy原理
首先我们来看一下`MethodProxy`怎么使用。它有两个方法分别为:
invoke
配合目标使用,直接调用方法无需反射。
invokeSuper
配合代理使用,也不许反射直接调用方法。
proxy.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
methodProxy.invoke(target, args);
methodProxy.invokeSuper(proxy, args);
return method.invoke(target, args);
}
});
其实底层原理也是通过创建两个代理对象,一个处理invoke
方法,一个处理invokeSuper
方法,接下来我们称这两个代理对象为FastClass,因为这两个代理类都是FastClass的子类。
我们可以查看源码:
这是一个抽象类,我们查看一下它的抽象方法:
其中最重要的两个方法就是这个invoke
和getIndex
接下来我们先看一下这个MethodProxy
怎么使用。
首先我们需要在代理类中创建三个方法用来直接调用父类的方法:
public void saveSuper(){
super.save();
}
public void saveSuper(int i){
super.save(i);
}
public void saveSuper(long i){
super.save(i);
}
然后我们创建MethodProxy
static Method save0;
static Method save1;
static Method save2;
static MethodProxy save0Super;
static MethodProxy save1Super;
static MethodProxy save2Super;
static{
try {
save0 = Target.class.getMethod("save");
save1 = Target.class.getMethod("save", int.class);
save2 = Target.class.getMethod("save", long.class);
save0Super = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
save1Super = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
save2Super = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
我们通过create
方法创建,里面的参数
参数一target
:目标类的class
对象。
参数二proxy
:代理类的class
对象
参数三desc
:方法表述,其中前面的括号用来表示参数,什么都没有就是无参,I
代表int
,J
代表long
。
参数四name
:表示代理方法名称。
参数五name
:调用原方法的名称。
这样我们把创建好的MethodProxy
传入到参数中,进行调用即可。
接下来我们来模拟一下其中的原理。
之前说了MethodProxy
内部会创建两个代理类,一个用于invoke
调用目标类的使用,一个用于invokeSuper
调用代理类的使用。
接下来我们就一起模拟一下:
首先创建一个类TargetClass
用来模拟调用目标类:
这里我们就不继承FastClass
了,因为那样的话需要重写很多方法,我们就直接模拟实现。
我们需要上面说的那两个重要的方法。
public class TargetClass {
/**
* 这个方法是通过签名来获取到目标方法的编号
* 这里我们就假设一下:
* Target
* save() 0
* save(int) 1
* save(long) 2
* 其中签名包括:方法名称、参数、返回值
*/
public int getIndex(Signature signature){
}
// 通过 getIndex 获得的编号,直接
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException{
}
}
其中getIndex
这个方法的签名就是在我们调用MethodProxy
的create
方法时创建了FastClass
代理类,然后通过create
的方法参数(其中desc和代理的名称就组成了签名),就能够确定每个MethodProxy
代表的是那个方法签名并且对应了哪个方法,然后会把所有的MethodProxy
都加载到一个代理类中。
这里我们就模拟通过参数直接创建好签名和对应的编号:
static Signature s0 = new Signature("save", "()V");
static Signature s1 = new Signature("save", "(I)V)");
static Signature s2 = new Signature("save", "(J)V");
/**
* 这个方法是通过签名来获取到目标方法的编号
* 这里我们就假设一下:
* Target
* save() 0
* save(int) 1
* save(long) 2
* 其中签名包括:方法名称、参数、返回值
*/
public int getIndex(Signature signature){
if (s0.equals(signature)){
return 0;
}else if(s1.equals(signature)){
return 1;
}else if(s2.equals(signature)){
return 2;
}
return -1;
}
这里给好编号之后,到后门我们调用MethodProxy
因为每个方法传递的是不同的,所以当调用的时候,它就知道自己是哪个编号了,然后通过编号就可以调用相关的目标方法了。
// 通过 getIndex 获得的编号,直接调用目标方法。
public Object invoke(int index, Object target, Object[] args) {
if (index == 0){
((Target) target).save();
return null;
}else if(index == 1){
((Target) target).save((int)args[0]);
return null;
}else if(index == 2){
((Target) target).save((long)args[0]);
return null;
}else{
throw new RuntimeException("无此方法");
}
}
这里我们就假设模拟一下save()
方法,当save()
创建MethodProxy
时,就会通过签名获取到编号0,然后调用的时候知道自己的编号,就可以在invoke
方法中找到正确的方法进行调用。
那么按照上面的TargetClass
,我们就可以快速的写出ProxyClass
,只不过注意的是,这里面调用的是代理类的saveSuper
方法,否则就会循环嗲用。
public class ProxyClass {
static Signature s0 = new Signature("saveSuper", "()V");
static Signature s1 = new Signature("saveSuper", "(I)V)");
static Signature s2 = new Signature("saveSuper", "(J)V");
public int getIndex(Signature signature){
if (s0.equals(signature)){
return 0;
}else if(s1.equals(signature)){
return 1;
}else if(s2.equals(signature)){
return 2;
}
return -1;
}
// 通过 getIndex 获得的编号,直接调用目标方法。
public Object invoke(int index, Object proxy, Object[] args) {
if (index == 0){
((Proxy) proxy).saveSuper();
return null;
}else if(index == 1){
((Proxy) proxy).saveSuper((int)args[0]);
return null;
}else if(index == 2){
((Proxy) proxy).saveSuper((long)args[0]);
return null;
}else{
throw new RuntimeException("无此方法");
}
}
}
Spring 选择代理
上述我们提到了,`aop`底层有 `jdk`和`cglib`两种代理方式,但是 Spring 是怎么选择何时用什么代理的呢?两种切面
在了解这个之前我们需要先了解清楚 Spring 中的两种不同切面的概念:/* aspect =
通知1(advice) + 切点1(pointcut)
通知2(advice) + 切点2(pointcut)
通知3(advice) + 切点3(pointcut)
*/
@Aspect // 切面
class MyAspect{
@Before("execution(* foo())")
public void before(){
System.out.println("前置增强");
}
@After("execution(* foo())")
public void after(){
System.out.println("后置增强");
}
}
aspect 切面:里面可以包含多个通知和切点,通常使用@Aspect
注解表明这是一个切面,然后在里面使用
@Before
注解加上方法体表示这是一个前置通知,然后在@Before
注解里面写上切点表达式表示这是一个切点。
/*
advisor = 更细粒度的切面,里面包含一个通知和一个切点
*/
通常我们都是使用aspect
切点的,但是一般aspect
切点在底层都是会划分为多个advisor
切点,所以说advisor
切点的粒度更细。
选择代理规则:
在这里我们会进行自己手动模型 Spring 的选择代理规则。模型底层切面增强
再此之前我们需要先准备一个接口和类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");
}
}
然后我们需要准备:
- 一个切点
切点底层是一个 PointCut
的接口,所以我们可以先查看一下源码(注意这里有很多个PointCut
我们需要查看的是springframework.aop
包下的)
进来我们可以发现切点就是用来匹配的,通过方法名我们就可以发现。
getClassFilter
进行类过滤的
getMethodMatcher
通过方法进行匹配的(这个方法最重要)
这个接口还提供了很多实现类(在接口上按住ctrl+alt+B
可以查看接口的实现类)
这里我们使用一个AspectJExpressionPointcut
通过切点表达式匹配的一个实现类。
// 1. 一个切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// 设置切点表达式
pointcut.setExpression("execution(* foo())");
- 一个通知
Spring 中通知也有很多接口,这里介绍一个最底层的通知,其它的实现最终都是转换为这个通知。这个通知就是MethodInterceptor
(环绕通知)。
进来查看源码
这个接口就只有一个方法,这个方法就是用来进行增强的,我们可以通过编写来进行增强我们的方法,其中最重要的一点就是,这个方法其实是一个环绕通知。
// 2. 一个通知
MethodInterceptor advice = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before");
Object result = invocation.proceed(); // 调用目标方法
System.out.println("after");
return result;
}
};
// 改写为 lambda 表达式
MethodInterceptor advice = invocation -> {
System.out.println("before");
Object result = invocation.proceed(); // 调用目标方法
System.out.println("after");
return result;
};
- 一个切面
这里切面使用的就是上述说的只有一个切点和一个通知的 Advisor
(因为后面所有的切面都是转化成一个个的 Advisor
切面)。
这个切面也是一个接口:
这里使用一个比较简单的实现:
这里在构造方法上就可以传递一个切点和一个通知
// 3. 一个切面
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
- 创建代理
因为 aop 的增强都是通过代理对象来增强的,所以这里我们也需要生成代理。不过这里我们不需要使用 jdk 或者 cglib 的 api 去创建代理,可以直接使用一个工厂类:ProxyFactory
。(内部会根据不同的情况去选择 jdk 的实现或者 cglib 的实现)。
// 4. 创建代理
Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1); // 设置目标
factory.addAdvisor(advisor); // 设置切面
I1 proxy = (I1) factory.getProxy(); // 获得代理
最后调用代理对象的方法,查看是否增强
proxy.foo();
proxy.bar();
before
target1 foo
after
target1 bar
可以看到 foo 方法是被增强了的。
同时我们还可以查看一下这时ProxyFactory
创建出来的代理是通过什么方式创建出来的,我们可以通过查看类名查看
Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1); // 设置目标
factory.addAdvisor(advisor); // 设置切面
I1 proxy = (I1) factory.getProxy(); // 获得代理
System.out.println(proxy.getClass());
class com.sahuid.a15.A15 T a r g e t 1 Target1 Target1 E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$7864e0bf
可以查看这是通过 CGLIB 创建出来的代理。
但是到底什么时候选择 jdk 什么时候选择 cglib 呢?
下面我们一起学习一下:
代理选择
在创建代理的时候,`ProxyFactory`会读取一个属性变量`proxyTargetClass`这个属性变量是存在它的父类`ProxyConfig`当中。代理的选择就与这个值有关。
代理选择的规则:
**proxyTargetClass = false**
,如果目标实现了接口,就使用 jdk 代理。**proxyTargetClass = false**
,如果目标没有实现接口,就使用 cglib 代理。**proxyTargetClass = true**
,使用 cglib 代理。(不管有没有实现接口)
这时就有一个疑问了:我们上面那个模型也实现了接口,但是为什么还是使用的 cglib 代理呢?
其实这是因为还少设置了一个值,告诉proxyfactory
实现了什么接口。我们上面就只设置了目标是谁,切面是谁,但是如果不设置这个值,就没有办法告诉 proxyfactory
是否这个目标实现了接口,会默认没有实现接口。
我们设置一下:
factory.setInterfaces(target1.getClass().getInterfaces()); // 设置实现的接口
再来观察一下代理对象:
class com.sahuid.a15.$Proxy0
就会发现变成了 jdk 实现
我们再测试一下第三种情况,将proxyTargetClass
设置为 true,即使实现了接口也会调用 cglib 的实现。
factory.setInterfaces(target1.getClass().getInterfaces()); // 设置实现的接口
factory.setProxyTargetClass(true);
class com.sahuid.a15.A15 T a r g e t 1 Target1 Target1 E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$c84c94a9
再模拟一下第二种情况,我们将proxyTargetClass
设置为 false,然后使用 Target2 没有实现接口。
Target2 target2 = new Target2();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target2); // 设置目标
factory.addAdvisor(advisor); // 设置切面
factory.setInterfaces(target2.getClass().getInterfaces()); // 设置实现的接口
factory.setProxyTargetClass(false);
Target2 proxy = (Target2) factory.getProxy(); // 获得代理
System.out.println(proxy.getClass());
proxy.foo();
proxy.bar();
class com.sahuid.a15.A15 T a r g e t 2 Target2 Target2 E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$659f8203
切点匹配
之前我们已经模拟了底层切面的增强,然后使用了 `AspectJExpressionPointcut`作为切点,通过切点表达式去匹配方法增强,这里我们来学习和研究更多种切点匹配的模式。再次之前准备模拟数据
static class T1{
@Transactional
public void foo(){
}
public void bar(){
System.out.println("T1.. bar");
}
}
匹配特定的某个方法
这里我们只匹配 `bar()`方法,而不匹配`foo()`方法。AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* bar())");
MethodInterceptor advice = invocation -> {
System.out.println("before....");
Object result = invocation.proceed();
System.out.println("after....");
return result;
};
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
T1 t1 = new T1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(t1);
factory.addAdvisor(advisor);
factory.setInterfaces(t1.getClass().getInterfaces());
T1 proxy = (T1) factory.getProxy();
proxy.bar();
before…
T1… bar
after…
但是具体是怎么判断是否哪个方法应该增强的呢?
这里具体依赖于AspectJExpressionPointcut
的一个方法matches
。这个方法会返回一个 boolean 值,如果为 true 就说明这个方法与切点匹配,如果返回 false 就说明与方法不匹配。
System.out.println(pointcut.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pointcut.matches(T1.class.getMethod("bar"), T1.class));
false
true
后续代理就会进行这个检查,如果匹配成功的话再进行增强。
匹配某个注解的方法
这里需要换一种写法 `@annotation`来匹配,需要使用包名加注解名称AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class));
true
false
这里只是模拟了一个注解的匹配方式,而Transactional
注解在 Spring 底层中真正的匹配方式并不是这样的。
上述的两种方式只能匹配方法(切点表达式),而不能匹配类。
Transcational 注解的匹配方式(任意匹配方式)
而`Transactional`注解可以使用在:- 方法上生效
- 类上生效
- 同时还可以使用在接口上,使其实现类生效。
所以使用不一样的匹配方式。
接下来我们来模拟一下Transactional
注解的生效方式。
准备类和接口:
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() {
}
}
这里使用一个StaticMethodMatcherPointcut
抽象类,重写它的方法来进行匹配,两个参数分别是方法和目标类。可以分别查询方法和类上是否存在某个接口。
查看某个方法/类是否有某个注解可以使用反射。
不过这里使用 Spring 封装好的一个类MergedAnnotations
,可以传入方法
或者类
的参数。调isPresent
方法查看是否存在某个注解。
StaticMethodMatcherPointcut pt3 = 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);
if (annotations.isPresent(Transactional.class)) {
return true;
}
return false;
}
};
进行测试:
System.out.println(pt3.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class));
System.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class));
true
false
true
不过测试接口的时候我们发现会失败
System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class));
false
查看 from
方法的源码我们可以发现:
这里有个搜索策略是SearchStrategy.DIRECT
意思是只会查询一层,只会查看本类中有没有添加注解,不会查看其实现的接口。(所以 T3 不会匹配成功)
想要匹配成功我们只需要调用from
方法时,改变一下查询策略即可。
这个枚举表示从继承树上进行查找:
annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
if (annotations.isPresent(Transactional.class)) {
return true;
}
true
@Aspect 和 Advisor
这一节主要来讲 @Aspect 高级切面类和 Advisor 低级切面类的关系,并且会模拟怎么最终把高级切面类都转化为低级的切面类。先创建模拟用的类:
static class Target1{
public void foo(){
System.out.println("target1 foo");
}
}
static class Target2{
public void bar(){
System.out.println("target2 bar");
}
}
然后创建两个切面类
@Aspect
static class Aspect1{
@Before("execution(* foo())")
public void before(){
System.out.println("aspect1 before");
}
@After("execution(* foo())")
public void after(){
System.out.println("aspect1 after");
}
}
@Configuration
static class Config{
@Bean
public Advisor advisor3(Advice advice3){
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
return new DefaultPointcutAdvisor(pointcut, advice3);
}
@Bean
public MethodInterceptor advice3(){
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before");
Object result = invocation.proceed();
System.out.println("after");
return result;
}
};
}
}
这里使用 Aspect 注解创建了高级切面,然后使用容器配置类的方式创建了低级切面。
最后将切面交给 Spring 容器进行管理
GenericApplicationContext context = new GenericApplicationContext();
// 注册 bean
context.registerBean("aspect1", Aspect1.class);
context.registerBean("config", Config.class);
// 注册 bean 工厂后处理器,去解析 @Bean 注解
context.registerBean(ConfigurationClassPostProcessor.class);
// 准备好容器
context.refresh();
// 打印 bean 的名称
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
aspect1
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
advisor3
advice3
AnnotationAwareAspectJAutoProxyCreator
接下来进入重点,介绍一个类。它的一个 bean 的后置处理器,作用是:
- 解析所有的切面,包括高级切面和低级切面,如果是高级切面会转化为低级切面。
- 拿到所有的切面,然后为其创建代理。
这个类就是AnnotationAwareAspectJAutoProxyCreator
,我们需要把它也注入到容器当中。
// 注册 bean 后处理器, AnnotationAwareAspectJAutoProxyCreator
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
这个类实现了BeanPostProcessor
接口,所以是一个 bean 的后处理器,它会被使用在 bean 的整个生命周期中。
这个类一般都是在初始化后去调用,但是也有可能会在依赖注入前调用(为了是解决依赖循环创建代理,后面再讲)。
如果我们就直接把这个类注入到容器当中,那么 Spring 会帮我们自动完成整个创建流程,我们就不是很好能够观察到这个类的作用,接下来我们手动调用这个类的方法来模拟。
接下来我们去源码中查看一下最为重要的两个方法:(ctrl + F12 查看内部方法)
我们会发现这两个方法都是protected
修饰的,所以我们想要调用就需要使用反射调用或者创建一个子类去调用,或者是在这个源码相同的包下创建类去使用。
findEligibleAdvisor
这个方法就是去查找**有资格的**切面(Advisor),如果是低级切面就直接保存到集合当中,如果是**高级切面就会将高级切面转化为低级切面**然后再保存到集合当中。这个我们通过反射去获取这个方法:
// 反射获取方法
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
Method findEligibleAdvisors = AbstractAdvisorAutoProxyCreator.class.getDeclaredMethod("findEligibleAdvisors", Class.class, String.class);
findEligibleAdvisors.setAccessible(true);
注意:
这个方式其实是它父类中的方法,所以我们通过反射的时候需要去父类中查找方法。
方法说明:
这个方法一共有两个参数。
第一个是目标对象 class,这个对象主要是用来,当对象传进来之后,方法会去从切面中查找能够匹配上目标方法的切面,如果是低级切面直接加入集合,如果是高级切面先转化为低级切面再加入集合。
第二个参数是,如果被容器管理需要传入 bean 的名字,但是这里并没有被容器管理,所以就随便填一个。
进行调用:
List<Advisor> advisors = (List<Advisor>) findEligibleAdvisors.invoke(creator, Target1.class, "target1");
for (Advisor advisor : advisors) {
System.out.println(advisor);
}
org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [com.sahuid.a17.A17$Config$1@587e5365]
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void com.sahuid.a17.A17$Aspect1.before()]; perClauseKind=SINGLETON
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void com.sahuid.a17.A17$Aspect1.after()]; perClauseKind=SINGLETON
我们可以查看到一共有四个切面。
这和我们想象的并不一样呀,我们一共一个高级切面一个低级切面,怎么会有四个切面呢?
其中第一个切面是一个环绕通知,它是Spring自动加的具体作用后续会说明。
然后第二个切面我们可以看得出DefaultPointcutAdvisor
说明这是我们设置的低级切面。
然后第三第四个切面我们可以看到before
和after
说明这是我们高级切面里面的两个通知。
这里我们就可以知道了,findEligibleAdvisor
这个方法会把高级切面里面的每个切点 + 通知都会转化为一个低级切面。
所以一共有四个。
wrapIfNecessary
这个方法就是先去检查是否满足要求,如果满足要求再去创建代理,否则不去创建代理。如何查看是否满足要求然后创建切面呢?
其实wrapIfNecessary
方法内部调用了findEligibleAdvisor
方法,只有方法返回的集合不为空就说明有匹配的切面,就为其创建代理。
接下来我们也模拟调用一下:
先反射获取方法:
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
Method wrapIfNecessary = AbstractAutoProxyCreator.class.getDeclaredMethod("wrapIfNecessary", Object.class, String.class, Object.class);
wrapIfNecessary.setAccessible(true);
注意:
这个方法是在 AbstractAutoProxyCreator
这个类下
参数说明:
第一个参数就是目标类,当前我们是在环境外部自行模拟调用的,所以需要自己 new 对象,当在容器中后,这个目标类就是通过 Spring 进行调用的。
第二个参数就是容器的名称对当前模拟没有什么作用,第三个参数也是所以就随便填即可。
接下来调用方法:
Object o1 = wrapIfNecessary.invoke(creator, new Target1(), "target1", "target1");
System.out.println(o1.getClass());
Object o2 = wrapIfNecessary.invoke(creator, new Target2(), "target2", "target2");
System.out.println(o2.getClass());
我们会发现方法会返回一个 Object
的对象。然后我们通过输出一下可以查看是否是代理。
不过我们在不输出之前也可以知道,因为 Target1 类中的 foo
方法切点匹配,所以内部调用findEligibleAdvisor
方法返回的集合不为空,所以会被增强,而 Target2 不会被增强。
查看一下结果
class com.sahuid.a17.A17 T a r g e t 1 Target1 Target1 E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$47ce25fe
class com.sahuid.a17.A17$Target2
创建代理的时机
上面我们讲过`AnnotationAwareAspectJAutoProxyCreator`这个类是一个 bean 后处理器,一般会在**依赖注入前**或者**初始化后**来调用,调用的目的就是因为这个类可以创建代理对象。接下来我们就一起来看看到底什么时候创建代理,又为什么在这个时候创建代理?
提前准备好类
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(Config.class);
context.refresh();
context.close();
}
@Configuration
static class Config{
@Bean // 解析 @Aspect 注解 创建代理
public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator(){
return new AnnotationAwareAspectJAutoProxyCreator();
}
@Bean // 解析 @Autowired 注解
public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor(){
return new AutowiredAnnotationBeanPostProcessor();
}
@Bean // 解析 @PostConstruct 注解
public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor(){
return new CommonAnnotationBeanPostProcessor();
}
@Bean
public Advisor advisor(MethodInterceptor advice){
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
return new DefaultPointcutAdvisor(pointcut, advice);
}
@Bean
public MethodInterceptor advice(){
return invocation -> {
System.out.println("before");
Object result = invocation.proceed();
return result;
};
}
@Bean
public Bean1 bean1(){
return new Bean1();
}
@Bean
public Bean2 bean2(){
return new Bean2();
}
}
static class Bean1{
public void foo(){
}
public Bean1(){
System.out.println("Bean1()");
}
@PostConstruct
public void init(){
System.out.println("Bean1 init()");
}
}
static class Bean2{
public Bean2(){
System.out.println("Bean2");
}
@Autowired
public void setBean1(Bean1 bean1){
System.out.println("Bean2 setBean1(bean1) class is" + bean1.getClass());
}
@PostConstruct
public void init(){
System.out.println("Bean2 init()");
}
}
初始化后创建代理
这时候我们发现这个`Bean1`和`Bean2`的依赖关系是单向的,`Bean2`依赖于`Bean1`然后运行程序我们可以发现输出结果为:
Bean1()
Bean1 init()
Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors
Bean2
Bean2 setBean1(bean1) class isclass com.sahuid.a17.A17_1$Bean1$$EnhancerBySpringCGLIB$$f175f1d5
Bean2 init()
从这里我们可以发现
首先是实例化创建了 bean1 ,然后初始化了 bean 1。
通过这段可以发现是初始化之后创建了代理。
Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors
这里就是在初始化之后创建了代理!!
依赖注入前创建代理
这时我们向 bean 1 也依赖注入 bean 2,然后在打印看一下:static class Bean1{
public void foo(){
}
@Autowired
public void setBean2(Bean2 bean2){
System.out.println("Bean1 setBean2(bean2) class is" + bean2.getClass());
}
public Bean1(){
System.out.println("Bean1()");
}
@PostConstruct
public void init(){
System.out.println("Bean1 init()");
}
}
Bean1()
Bean2()
[TRACE] 16:48:00.458 [main] o.s.a.a.a.AnnotationAwareAspectJAutoProxyCreator - Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors
Bean2 setBean1(bean1) class isclass com.sahuid.a17.A17_1$Bean1$$EnhancerBySpringCGLIB$$b5517fb7
Bean2 init()
Bean1 setBean2(bean2) class isclass com.sahuid.a17.A17_1$Bean2
Bean1 init()
这时我们可以发现是在依赖注入前的时候就创建了代理。
这是为什么呢?
因为这里面存在了循环依赖:了解bean
的生命周期就知道首先需要实例化然后依赖注入然后初始化。所以这里出现的情况就是:
一开始实例化然后准备依赖注入,但是发现需要 bean2
,但是 bean 2
并没有注入到容器中,所以开始注入 bean 2
,然后实例化 bean 2
,当要依赖注入的时候发现需要 bean 1
但是 bean 1
还在创建当中。这时如果还是继续走 bean 1
的创建流程就会又从 bean 1
到 bean 2
,从 bean 2
到 bean 1
陷入循环,这种情况就成为依赖循环。
为了解决这个依赖循环的问题,所以就可以在 bean 2
需要依赖注入之前先创建一个 bean 1
的代理对象,然后将其注入这时 bean 2
就完成了创建。然后将其注入到bean 1
,bean 1
也可以完成创建。
整个流程就是
bean1 实例化 -> bean2 实例化 -> 创建 bean1 的代理对象 -> bean 2 依赖注入 ->
bean2 初始化 -> bean1 依赖注入 -> bean 1 初始化。
所以这个代理就是在依赖注入之前进行的,作用就是防止依赖循环。
高级切面转换低级切面
上面我们也讲过,所有的`@Aspect`注解的高级切面最后都会转化为`Advisor`这低级切面,但是具体怎么转化的呢,而且到底转化成什么样的`Advisor`低级切面呢,接下来了解一下:提起准备相关的类:
static class Aspect{
@Before("execution(* foo())")
public void before1(){
System.out.println("before1 foo");
}
@Before("exe")
public void before2(){
System.out.println("before2 foo");
}
public void after(){
System.out.println("after");
}
public void afterReturning(){
System.out.println("afterReturning");
}
public void afterThrowing(){
System.out.println("afterThrowing");
}
public void around(){
System.out.println("around");
}
}
接下来就模拟一下 @Before
注解转化为低级切面。其中@Before
标记的通知最后会转化为原始的AspectJMethodBeforeAdvice
低级切面。
具体操作:
public static void main(String[] args) {
// 创建切面对象实例工厂, 用户后续反射调用切面的方法
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
Method[] methods = Aspect.class.getDeclaredMethods();
List<Advisor> list = new ArrayList<>();
for (Method method : methods) {
if (method.isAnnotationPresent(Before.class)){
// 解析切点
Before before = method.getAnnotation(Before.class);
String expression = before.value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 创建通知类 Before 对应的通知类 ->> AspectJMethodBeforeAdvice
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 创建切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
}
for (Advisor advisor : list) {
System.out.println(advisor);
}
输出:
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.sahuid.a17.A17_2$Aspect.before2()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.sahuid.a17.A17_2$Aspect.before1()]; aspect name '']
这样一个被@Before
注解标注的高级切面就被转化为一个对应的低级初始切面AspectJMethodBeforeAdvice
。
最后介绍一下高级切面和归纳一下所有高级切面对应的切面:
- @Before -> 前置通知
- @AfterReturning -> 最终通知
- @AfterThrowing -> 异常通知
- @After -> 后置通知
- @Around -> 环绕通知
注解 | 对应的原始通知类 |
---|---|
@Before | AspectJMethodBeforeAdvice |
@AfterReturning | AspectJAfterReturningAdvice |
@AfterThrowing | AspectJAfterThrowingAdvice |
@After | AspectJAfterAdvice |
@Around | AspectJAroundAdvice |
静态通知调用
统一转化为环绕通知
上面我们介绍过了各个注解最后都会转化成对应的原始通知切面,但是这其实并不是程序最后调用的。程序最后会将所有的通知都转化成一个**环绕通知**`MethodInterceptor` 。但是具体为什么要这么转化呢,我们一起来了解一下:其实无论ProxyFactory
是基于哪种代理方式,最后干活的(调用 advice )的都是一个MethodInvacation
的对象。
这个MethodInvacation
对象不仅需要知道有哪些advice
,还要知道目标有什么。然后最终的调用次序为:
调用需要保证一个有序性,先从外往里一层层调用,调用到目标之后再从里往外一层层掉。如果不是环绕通知的话就会导致调用的顺序无法保证。
从图可知,只有环绕通知才适合做advice
,其他的 before、afterReturning等需要转化为环绕通知。
而如果本身就是环绕通知的就不需要转化为环绕通知了,例如:
AspectJAroundAdvice
AspectJAfterThrowingAdvice
AspectJAfterAdvice
(想看是否是环绕通知,只需要看是否实现了 MethodInterceptor
接口)
接下来模拟一下:
首先我们还是先转化成低级切面
准备模拟类
static class Aspect{
@Before("execution(* foo())")
public void before1(){
System.out.println("before1 foo");
}
@Before("execution(* foo())")
public void before2(){
System.out.println("before2 foo");
}
public void after(){
System.out.println("after");
}
@AfterReturning("execution(* foo())")
public void afterReturning(){
System.out.println("afterReturning");
}
public void afterThrowing(){
System.out.println("afterThrowing");
}
@Around("execution(* foo())")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
System.out.println("around...before");
return pjp.proceed();
} finally {
System.out.println("around...after");
}
}
}
模拟将@Before
、@AfterReturning
、@Around
转化成低级切面
// 创建切面对象实例工厂, 用户后续反射调用切面的方法
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
Method[] methods = Aspect.class.getDeclaredMethods();
List<Advisor> list = new ArrayList<>();
for (Method method : methods) {
if (method.isAnnotationPresent(Before.class)) {
// 解析切点
Before before = method.getAnnotation(Before.class);
String expression = before.value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 创建通知类 Before 对应的通知类 ->> AspectJMethodBeforeAdvice
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 创建切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
} else if (method.isAnnotationPresent(AfterReturning.class)) {
// 解析切点
AfterReturning before = method.getAnnotation(AfterReturning.class);
String expression = before.value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 创建通知类 AfterReturning 对应的通知类 ->> AspectJAfterReturningAdvice
AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);
// 创建切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
} else if (method.isAnnotationPresent(Around.class)) {
// 解析切点
Around before = method.getAnnotation(Around.class);
String expression = before.value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 创建通知类 Around 对应的通知类 ->> AspectJAfterReturningAdvice
AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);
// 创建切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
}
for (Advisor advisor : list) {
System.out.println(advisor);
}
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.sahuid.a18.A18$Aspect.before1()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJAfterReturningAdvice: advice method [public void com.sahuid.a18.A18$Aspect.afterReturning()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJAroundAdvice: advice method [public void com.sahuid.a18.A18$Aspect.around()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.sahuid.a18.A18$Aspect.before2()]; aspect name '']
转化成环绕通知:
这里需要使用到代理工厂ProxyFactory
,因为通常是创建完代理之后,然后调用代理方法的时候会转化成环绕通知,这里我们之间调用方法模拟一下。这个方法是
getInterceptorsAndDynamicInterceptionAdvice
。
参数1:被增强的方法
参数2:目标类
转化为环绕通知
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Target1());
proxyFactory.addAdvisors(list);
List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target1.class.getMethod("foo"), Target1.class);
for (Object o : methodInterceptorList) {
System.out.println(o);
}
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@26a7b76d
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@4abdb505
org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor@7ce6a65d
org.springframework.aop.aspectj.AspectJAroundAdvice: advice method [public void com.sahuid.a18.A18$Aspect.around()]; aspect name ''
从输出结果可以得知:
像不是环绕通知的 AspectJMethodBeforeAdvice
这种会转化为MethodBeforeAdviceInterceptor
。而像AspectJAroundAdvice
就不换转换,还是本身。
调用链执行
上面我们也说过,这所有的通知最后都会转化为环绕通知,转化完的环绕通知就会通过一个调用链的方式依次调用,这个调用链就有点像拦截器或者过滤器。接下我们在上面代码的基础上继续模拟调用:
这里执行调用的是一个接口MethodInvocation
,我们实现一个它的实现类ReflectiveMethodInvocation
。
注意:
这里的构造方法是protected
的所以可以通过创建反射或者继承父类的方式调用,这里我们就通过继承父类的方法调用,只需要在方法后面打一个大括号即可。
MethodInvocation invocation = new ReflectiveMethodInvocation(null, target, Target1.class.getMethod("foo"), new Object[0], Target1.class, methodInterceptorList){};
invocation.proceed();
第一个参数是一个代理,这个在调用期间基本上用不到就传一个null
第二个参数是一个目标类对象,这里就是new
出来的 target
第三个参数是一个method
,就通过反射拿到foo
方法
第四个参数是一个数组,就是方法的参数,这里没有就创建一个空数组
第五个参数是目标类的一个class
对象
第六个参数就是我们上面获取到的环绕通知的list
当这时我们进行调用时:
发现报错了,具体原因是因为MethodInvocation
没有找到。
这时就有一个疑问了?
明明我们都创建了对象才调用的方法,为什么会没有找到呢。
原因是因为其实我们环绕通知的每一个切面都需要MethodInvocation
然后调用
proceed
方法。这就有点像过滤器,每次过滤最后都需要调用filterChain.doFilter
方法一样。
而怎么给每个切面都添加一个MethodInvocation
呢,这里需要一个环绕通知,是添加到最开始的环绕通知,这个环绕通知是ExposeInvocationInterceptor
。这也解决了我们上面第一次高级切面转化为低级切面时,发现的明明自己定义了三个却有四个,这多出来的就是这个环绕通知切面。
这个环绕通知实现的原理就是向当前线程里面设置一个MethodInvocation
,然后哪个切面需要就像当前线程取即可,底层是根据ThreadLocal
实现的。
接下来我们把它加一下
我们需要在添加所有切面之前加入这个,添加的是一个单例对象。
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);
proxyFactory.addAdvisors(list);
最后再调用执行一下:
before1 foo
before2 foo
around...before
target1 foo
around...after
afterReturning
最后发现调用符合我们的调用链的执行方式
模拟实现调用链
先创建两个环绕通知,直接实现`MethodInterceptor`创建环绕通知。static class Advice1 implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("advice1 before");
Object result = invocation.proceed();
System.out.println("advice1 after");
return result;
}
}
static class Advice2 implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("advice2 before");
Object result = invocation.proceed();
System.out.println("advice2 after");
return result;
}
}
再创建一个目标类:
static class Target{
public void foo(){
System.out.println("target foo");
}
}
再创建一个调用链对象,实现MethodInvocation
接口。
然后创建一个成员变量:
target
:目标类对象,用来反射调用方法时使用
method
:被增强的方法
args
:方法参数
methodInterceptorList
:环绕通知切面集合。
static class MyInvocation implements MethodInvocation{
private Object target;
private Method method;
private Object[] args;
private List<MethodInterceptor> methodInterceptorList;
private int count = 1;
public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {
this.target = target;
this.method = method;
this.args = args;
this.methodInterceptorList = methodInterceptorList;
}
@Override
public Method getMethod() {
return method;
}
@Override
public Object[] getArguments() {
return args;
}
@Override
public Object proceed() throws Throwable {
if (count > methodInterceptorList.size()){
return method.invoke(target,args);
}
MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
return methodInterceptor.invoke(this);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return method;
}
}
其中的proceed
方法最重要,其中运用了递归的原理。
首先我们需要确定什么时候调用目标方法,假设切面有两个,那么两个切面之后就会调用目标方法,所以我们设置一个变量用来计数当前执行的切面数。
然后如果没有到执行目标方法时,我们就先直接切面的方法,从list
中取然后调用,调用完之后count++
。
但是单纯方法看,根本看不出来递归再哪里,所以我们需要结合着方法来看看。
// advice1
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("advice1 before");
Object result = invocation.proceed();
System.out.println("advice1 after");
return result;
}
// advice2
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("advice2 before");
Object result = invocation.proceed();
System.out.println("advice2 after");
return result;
}
// MethodInvocation
public Object proceed() throws Throwable {
if (count > methodInterceptorList.size()){
return method.invoke(target,args);
}
MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
return methodInterceptor.invoke(this);
}
当外面调用的MethodInvocation
的proceed
方法时:
- 进入
MethodInvocation
的proceed
方法- 判断是否执行目标方法 – 不需要
- 获取
advice1
的切面通知 - 调用
advice1
的invoke
方法- 输出
advice1 before
- 调用通过
ExposeInvocationInterceptor
传入的MethodInvocation
的proceed
方法- 判断是否执行目标方法 — 不需要
- 获取
advice2
的切面通知 - 调用
advice2
的invoke
方法- 输出
advice2 before
- 调用
MethodInvocation
的proceed
方法- 判断是否执行目标方法 – 需要
- 执行
target
的foo
方法 - 返回
- 输出
advice2 after
- 返回
- 输出
- 返回
- 输出
advice1 after
- 返回
- 输出
- 返回
- 执行完成
最后整个输出结果为:
advice1 before
advice2 before
target foo
advice2 after
advice1 after