Cglib动态代理
引言
学习AOP之后就对Cglib动态代理感到好奇,尤其是使用了AOP遇到切面失效这种场景后,更对其底层实现有了深入研究的欲望。
初探
我们先写一个入门的使用Cglib对目标类进行增强的代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35public class TargetAdvisor implements MethodInterceptor {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Intercept Beginning......");
Object ret = methodProxy.invokeSuper(proxy, args);
System.out.println("Intercept Ending......");
return ret;
}
}
public class Target {
public void eat(){
System.out.println("Target Eat Method Running......");
}
public void doSomething(){
System.out.println("Target Do Something......");
}
}
public class Main {
public static void main(String[] args) {
// 用于保存生成的动态代理对象的字节码文件
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Target.class);
enhancer.setCallback(new TargetAdvisor(new Target()));
Target t = (Target)enhancer.create();
t.eat();
t.doSomething();
}
}
目标类的两个方法都会会被拦截增强。
Cglib动态代理原理
观察生成的动态代理类的字节码文件,一共生成了三个文件:
- Targetd7654b9.class
- Targetd7654b9e6923f72.class
- Targetc6b7aaff.class
Targetd7654b9这个类是Cglib生成的动态代理类,Targetd7654b9e6923f72这个类是代理类的FastClass,Targetc6b7aaff这个类是目标类的FastClass。Cglib使用FastClass用来快速定位要执行的方法,相比反射要性能更快一点。
通过IDEA的反编译,我们观察代理类的源码。可以看到,动态代理类中为每一个方法都有对应的Method和MethodProxy。CGLIB$CALLBACK_0即我们设置的拦截器对象。
观察eat方法,拦截器对象被赋值给了var1000的变量。我们最终执行的eat就是这个eat方法。如果拦截器不为null,就会在执行拦截器的intercept方法。也就是TargetAdvisor的intercept方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class Target$$EnhancerByCGLIB$$d7654b9 extends Target implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$eat$0$Method;
private static final MethodProxy CGLIB$eat$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$doSomething$1$Method;
private static final MethodProxy CGLIB$doSomething$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
final void CGLIB$eat$0() {
super.eat();
}
public final void eat() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$eat$0$Method, CGLIB$emptyArgs, CGLIB$eat$0$Proxy);
} else {
super.eat();
}
}
final void CGLIB$doSomething$1() {
super.doSomething();
}
public final void doSomething() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$doSomething$1$Method, CGLIB$emptyArgs, CGLIB$doSomething$1$Proxy);
} else {
super.doSomething();
}
}
}
深入
现在,我们引入一个注解,并在eat方法上加上该注解。对于拦截器,也做出改动,要求只能对标有@Eat注解的方法进行增强,对于doSomething则忽略,该如何设计?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 (ElementType.METHOD)
(RetentionPolicy.RUNTIME)
public Eat {
public String value();
}
public class Target {
"eat") (value =
public void eat(){
System.out.println("Target Eat Method Running......");
}
public void doSomething(){
eat();
}
}
经过初步思考,我们可以判断这个方法有没有标注@Eat注解来判断:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class TargetAdvisor implements MethodInterceptor {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Eat annotation = method.getAnnotation(Eat.class);
Object ret;
if (annotation == null){
ret = methodProxy.invokeSuper(proxy, args);
}else{
System.out.println("Intercept Beginning......");
ret = methodProxy.invokeSuper(proxy, args);
System.out.println("Intercept Ending......");
}
return ret;
}
}
当我们调用doSomething方法,会输出什么?eat会被增强吗?答案是会。很奇怪是不是。为了了解这个需要看MethodProxy.invokeSuper这个方法内部到底执行了什么?
可以看到invokeSuper上还有一个invoke方法,另外重点关注内部类FastClassInfo,见名知意,就是保存FastClass信息的类,有两个类型为FastClass属性f1、f2,有两个整型i1、i2。
当执行代理对象的eat方法时,可以在TargetAdvisor中的methodProxy.invokeSuper(proxy, args)、MethodProxy.invokeSuper上打断点,观察fc2是什么,i2是什么。如下图:
很明显,fc1是目标类的FastClass对象信息,fc2是代理类的FastClass对象信息。那i2=18很明显就是代理对象中某个方法的索引。到Targetd7654b9e6923f72的字节码中找到对应索引的方法名:CGLIB$eat$1()V。
也就是说fci.f2.invoke(fci.i2, obj, args)相当于Targetd7654b9#CGLIB$eat$1(),内部仅有一条语句:super.eat(),也就是调用了Target类的eat方法。
i1=1,对应着eat()V方法,即Target中的eat方法。
好了,有了这些基础就可以解释为什么当我们调用doSomething方法时,eat还是会被拦截了,问题就出在拦截器中当索引为null的时候,仍然调用了invokeSuper这个方法。
复习一下多态,这个eat其实是代理类中的eat方法,因此被拦截了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public class MethodProxy {
private Signature sig1;
private Signature sig2;
private CreateInfo createInfo;
private final Object initLock = new Object();
private volatile FastClassInfo fastClassInfo;
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
private void init() {
if (this.fastClassInfo == null) {
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
CreateInfo ci = this.createInfo;
FastClassInfo fci = new FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(this.sig1);
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
private static FastClass helper(CreateInfo ci, Class type) {
FastClass.Generator g = new FastClass.Generator();
g.setType(type);
g.setClassLoader(ci.c2.getClassLoader());
g.setNamingPolicy(ci.namingPolicy);
g.setStrategy(ci.strategy);
g.setAttemptLoad(ci.attemptLoad);
return g.create();
}
private MethodProxy() {
}
public Signature getSignature() {
return this.sig1;
}
public String getSuperName() {
return this.sig2.getName();
}
public int getSuperIndex() {
this.init();
return this.fastClassInfo.i2;
}
FastClass getFastClass() {
this.init();
return this.fastClassInfo.f1;
}
FastClass getSuperFastClass() {
this.init();
return this.fastClassInfo.f2;
}
public static MethodProxy find(Class type, Signature sig) {
try {
Method m = type.getDeclaredMethod("CGLIB$findMethodProxy", MethodInterceptorGenerator.FIND_PROXY_TYPES);
return (MethodProxy)m.invoke((Object)null, sig);
} catch (NoSuchMethodException var3) {
throw new IllegalArgumentException("Class " + type + " does not use a MethodInterceptor");
} catch (IllegalAccessException var4) {
throw new CodeGenerationException(var4);
} catch (InvocationTargetException var5) {
throw new CodeGenerationException(var5);
}
}
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
this.init();
FastClassInfo fci = this.fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
} catch (IllegalArgumentException var5) {
if (this.fastClassInfo.i1 < 0) {
throw new IllegalArgumentException("Protected method: " + this.sig1);
} else {
throw var5;
}
}
}
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init();
FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
private static class CreateInfo {
Class c1;
Class c2;
NamingPolicy namingPolicy;
GeneratorStrategy strategy;
boolean attemptLoad;
public CreateInfo(Class c1, Class c2) {
this.c1 = c1;
this.c2 = c2;
AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
if (fromEnhancer != null) {
this.namingPolicy = fromEnhancer.getNamingPolicy();
this.strategy = fromEnhancer.getStrategy();
this.attemptLoad = fromEnhancer.getAttemptLoad();
}
}
}
private static class FastClassInfo {
FastClass f1;
FastClass f2;
int i1;
int i2;
private FastClassInfo() {
}
}
}
我们可以改进当注解为空的时候,invokeSuper改为invoke。Spring中通过各种工具在拦截器中获取到了要代理的目标对象,我们演示为了简便,直接在拦截器内部定义要代理的对象。改进的TargetAdvisor如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class TargetAdvisor implements MethodInterceptor {
public Target target;
public TargetAdvisor(Target o){
this.target = o;
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Eat annotation = method.getAnnotation(Eat.class);
Object ret;
if (annotation == null){
// 改动点!!!
ret = methodProxy.invoke(target, args);
}else{
System.out.println("Intercept Beginning......");
ret = methodProxy.invokeSuper(proxy, args);
System.out.println("Intercept Ending......");
}
return ret;
}
}
当注解为空,invoke内部调用其实是Target内部的doSomething方法,eat方法自然也是没有被拦截过的。此使这个doSomething方法没有被拦截。
Spring中的切面失效
上面啰嗦了一大堆,其实为了引出AOP切面失效这个关键问题,具体如以下代码所示。1
2
3
4
5
6
7
8
9
10
11public class UserService{
public void func(int id){
updateUserInfo(id);
}
public void updateUserInfo(int id){
// do something
}
}
updateUserInfo这个方法被@Transaction注解修饰,Spring在启动的时候会使用动态代理的方式,为这个对象生成一个增强类注入到容器中。
当调用func方法,这个注解会失效,不会被增强。究其本质,还是Spring对这个注解处理方式所致,原理上和我们上述演示的基本一致。
对于加了事务注解的方法,在执行时,主要经历了以下步骤:
- 开始执行 userService.updateUserInfo(); 这里的 userService 就是代理对象;会被 CglibAopProxy.DynamicAdvisedInterceptor#intercept 方法拦截;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49class DynamicAdvisedInterceptor{
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we
// "own" the target, in case it comes from a pool...
target = getTarget();
if (target != null) {
targetClass = target.getClass();
}
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
// 注意!!!!
retVal = methodProxy.invoke(target, args);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null) {
releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
} - TransactionInterceptor#invoke 被事务拦截器拦截
- TransactionAspectSupport#invokeWithinTransaction 事务处理
- AbstractPlatformTransactionManager#getTransaction 会在这里调用 AbstractPlatformTransactionManager#startTransaction 方法,来开启事务。