spring代理问题
问题还是起于老掉牙的spring @Transactional 同类调用不生效的问题。
以下的场景都基于method1调用method2,@Transactional 是加载method2上,外部调用调用的是method1接口。
大致结构如下
|
|
疑问
对于jdk动态代理,其实问题一直很明白,因为jdk动态代理生成的proxy是继承自java.lang.reflect.Proxy,反射到目标类去执行,而在目标类的内部调用中是无法再回到代理类的,所以 @Transactional 在同类调用之间无效。
但是cglib生成的是目标类的子类,猜想的结构大概是这样的。
这样理论上应该是能够调用到代理类的。
但是事实是并不能调用成功。
找了一下Spring文档也发现了相关的结论。
Note: In proxy mode (which is the default), only ‘external’ method calls coming in through the proxy will be intercepted. This means that ‘self-invocation’, i.e. a method within the target object calling some other method of the target object, won’t lead to an actual transaction at runtime even if the invoked method is marked with @Transactional!
Consider the use of AspectJ mode (see below) if you expect self-invocations to be wrapped with transactions as well. In this case, there won’t be a proxy in the first place; instead, the target class will be ‘weaved’ (i.e. its byte code will be modified) in order to turn @Transactional into runtime behavior on any kind of method.
重新梳理一下知识点。那么究竟问题在哪。
jdk动态代理
jdk动态代理调用栈
jdk动态代理demo代码
先看下简易的动态代理的代码
Jdk动态代理的生成类
- 由于动态代理的class继承了proxy,所以jdk动态代理必须要有接口。
- super.h.invoke 其实调用的是InvocationHandler的方法,自己实现,一般是method.invoke(target,args),所以在target内部调用,target.method1 直接调用 target.method2,调用栈在之前有显示,并不会经过proxy.method2,故@Transactional如果处于method2上会失效。
更加深入的了解一下jdk动态代理
先看下newProxyInstance方法
整个逻辑其实很简单,获得proxy的class,然后构造proxy实例。
其中关键节点是getProxyClass0方法,下面是一整条调用链。
通过接口可以发现,proxy class 仅仅与 classloader 和 interface 有关
所以在继承InvocationHandler的时候,impl的实例是不存在的。仅仅只有proxy的实例。
supplier might be a Factory or a CacheValue
关于这句注释,当时看代码的时候有点奇怪,当时以为是简单的get,现在看来get都不是简单的get。 TAT
Factory相关
这里同步get的理由应该是要防止并发,并降低锁的粒度,否则锁的粒度应该构建整个map的时候就开始了。
proxy生成的代码比较复杂就不看了
在构建类发现一行这个代码,也就是说只要将saveGeneratedFiles设为true就会生成proxy.class在硬盘上。
|
|
默认在项目根目录下有个com.sun.xxx下就会出现proxy.class文件。不用单独的去写utils类了,以下是开启方法。
关于 weak reference 简单的理解 //todo 详细了解weak reference
- 对象被创建之后,如果没有任何引用指向他,且发生gc,则对象则会被消失。
- 如果引用的该对象的是一个集合,我们经常会在 代码中看到 node = null;//help gc 这样的代码来帮助gc。
- 但是cache是例外的,因为cache不知道他的生命周期是怎么样的,比如我们在使用redis的时候我们并不知道这个key应该存在多久,什么时候会被停止使用,是否够热。
- 那么cache的普通做法是lru或者lfu,来保证cache中的数据是最有效的。而内存中的cache一般不会那么做(redis有内存大小的限制),因为我们无法估算大小,且在系统允许的情况下存的越多越好。
- 而在上述proxy的使用场景下,一个interface下大概率也只用proxy.class一次(比如单例的service),而我们项目假设有1000个需要动态代理的class,那么在cache中的也有1000个对象,可能大部分是无效的,占用了大部分内存。
- 因为我们无法知晓cache的生命周期,所以 node = null; //help gc 这样的代码显然不适合我们。
- 所以weak reference的意义就在这里,当且仅当一个对象被weak reference指向时,gc时就被回收。
- 因为weak reference 是随着gc被回收,所以他的回收是具有不确定性的,一般用于易构建但是占用内存较大的对象。
- 代码中会看到清空ReferenceQueue,这里保存的是已经被回收的weak reference,因为weak reference本身是没有用的。
- 下面代码是关于weak reference的回收
|
|
cglib动态代理
cglib调用栈
以前虽然用的多但是从来没有思考过相关的实现(主要是懒)。
图中红圈所示的两个地方是cglib两个不同的生成类。分别是enhanceClass 和 fastClass。
enhanceClass是调用了method1方法,可以认为是重写了父类接口。
fastClass调用的是invoke接口反射到了目标类的method1,和之前jdk动态代理是一模一样的,所以一旦用fastClass进入了目标类的方法,@Transactional也依旧会失效。
enhanceClass的理解大概和之前猜测的增强类实现应该一致,而fastClass是干嘛用的呢。
cglib demo代码
|
|
其实代码和java动态代理差不多,就是InvocationHandler和cglib callback接口的差别还有构建的差别。
分为以下几点
之前看到jdk动态代理的时候classloader是个绕不开的东西,invocationHandler创建的时候需要传入来做WeakCache的缓存的key,而enhancer只需要setCallback,new Enhancer中并没有要求传入classloader,代码里会自己去找上下文的ClassLoader。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556// 和jdk动态代理的keyFactory类似public static KeyFactory create(Class keyInterface, KeyFactoryCustomizer first, List<KeyFactoryCustomizer> next) {return create(keyInterface.getClassLoader(), keyInterface, first, next);}public static KeyFactory create(ClassLoader loader, Class keyInterface, KeyFactoryCustomizer customizer,List<KeyFactoryCustomizer> next) {Generator gen = new Generator();gen.setInterface(keyInterface);if (customizer != null) {gen.addCustomizer(customizer);}if (next != null && !next.isEmpty()) {for (KeyFactoryCustomizer keyFactoryCustomizer : next) {gen.addCustomizer(keyFactoryCustomizer);}}gen.setClassLoader(loader);return gen.create();}protected Object create(Object key) {try {ClassLoader loader = getClassLoader();Map<ClassLoader, ClassLoaderData> cache = CACHE;// 同样是通过classloader来进行一级缓存ClassLoaderData data = cache.get(loader);if (data == null) {// jdk动态代理1.8的情况下用putIfAbsent来解决了并发的效率问题// cglib更加直观直接用同步锁来解决synchronized (AbstractClassGenerator.class) {cache = CACHE;data = cache.get(loader);if (data == null) {Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);data = new ClassLoaderData(loader);newCache.put(loader, data);CACHE = newCache;}}}this.key = key;Object obj = data.get(this, getUseCache());if (obj instanceof Class) {return firstInstance((Class) obj);}return nextInstance(obj);} catch (RuntimeException e) {throw e;} catch (Error e) {throw e;} catch (Exception e) {throw new CodeGenerationException(e);}}cglib intercept 方法中参数多了一个methodProxy
- 如果在代码中把interceptor的methodPrxoy替换成method,和jdk动态代理没有区别,代码调用栈如下1Object invoke = method.invoke(new SimpleCglibService(),objects);
- cglib的method proxy 中有两个方法,invoke 和 invokeSuper123456789101112131415161718192021222324252627282930313233343536373839404142434445/*** Invoke the original method, on a different object of the same type.* @param obj the compatible object; recursion will result if you use the object passed as the first* argument to the MethodInterceptor (usually not what you want)* @param args the arguments passed to the intercepted method; you may substitute a different* argument array as long as the types are compatible* @see MethodInterceptor#intercept* @throws Throwable the bare exceptions thrown by the called method are passed through* without wrapping in an <code>InvocationTargetException</code>*/public Object invoke(Object obj, Object[] args) throws Throwable {try {init();FastClassInfo fci = fastClassInfo;//代码差异点return fci.f1.invoke(fci.i1, obj, args);} catch (InvocationTargetException e) {throw e.getTargetException();} catch (IllegalArgumentException e) {if (fastClassInfo.i1 < 0)throw new IllegalArgumentException("Protected method: " + sig1);throw e;}}/*** Invoke the original (super) method on the specified object.* @param obj the enhanced object, must be the object passed as the first* argument to the MethodInterceptor* @param args the arguments passed to the intercepted method; you may substitute a different* argument array as long as the types are compatible* @see MethodInterceptor#intercept* @throws Throwable the bare exceptions thrown by the called method are passed through* without wrapping in an <code>InvocationTargetException</code>*/public Object invokeSuper(Object obj, Object[] args) throws Throwable {try {init();FastClassInfo fci = fastClassInfo;//代码差异点return fci.f2.invoke(fci.i2, obj, args);} catch (InvocationTargetException e) {throw e.getTargetException();}}
代码差异主要在fci.f1 fci.f2,这两个是不同的两个fastClassInfo。
f1主要是原始类的子类很好理解
f2则是一个fastClass1234567891011121314151617181920212223242526272829public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {//原始类SimpleCglibService var10000 = (SimpleCglibService)var2;int var10001 = var1;try {switch(var10001) {case 0:SimpleCglibService.main((String[])var3[0]);return null;case 1:var10000.method1();return null;case 2:var10000.method2();return null;case 3:return new Boolean(var10000.equals(var3[0]));case 4:return var10000.toString();case 5:return new Integer(var10000.hashCode());}} catch (Throwable var4) {throw new InvocationTargetException(var4);}throw new IllegalArgumentException("Cannot find matching method/constructor");}逻辑其实很简单,通过index来确定调用哪个方法。
关于fastclass
Jdk动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。
- 如果在代码中把interceptor的methodPrxoy替换成method,和jdk动态代理没有区别,代码调用栈如下