当动态代理遇到ioc (四)真正的cglib
0 当动态代理遇到ioc (二)cglib 中的代理仍然不能切子函数,所以有本篇
1 此前已经解决cglib与asm冲突:当动态代理遇到ioc (三)cglib与asm jar包冲突
2 事务的try catch模型由exception扩叨叨throwable
3 抽象层
由于我们反射调用方法时,不再是method.invoke(origin object, args[]),所以抽象层要修改一下
protected Object invokeHasTransactional(SCEF_DB_TRANSACTIONAL scef_db_transactional, Method method, Object[] args, MethodProxy methodProxy, Object oCglib) throws Throwable { protected Object invokeNoTransactional(Method method, Object[] args, MethodProxy methodProxy, Object oCglib) throws Throwable {
Object returnValue = aopInvoke(method, args, methodProxy, oCglib);
jdk
protected Object aopInvoke(Method method, Object [] args, MethodProxy methodProxy, Object oCglib) throws Throwable { Object returnValue = method.invoke(target, args); return returnValue; }
if(scef_db_transactional != null) { res = invokeHasTransactional(scef_db_transactional, method, args, null, null); } else { res = invokeNoTransactional(method, args, null, null); }
cglib
@Override protected Object aopInvoke(Method method, Object[] args, MethodProxy methodProxy, Object oCglib) throws Throwable {
////////return method.invoke(target, args); return methodProxy.invokeSuper(oCglib, args); }
if (scef_db_transactional != null) { res = invokeHasTransactional(scef_db_transactional, null, objects, methodProxy, o); } else { res = invokeNoTransactional(null, objects, methodProxy, o); }
4 ioc反哺
解决了3形成了子函数切面后,报错:
首次处理 class sun.myproxybean.MyServiceImpl的ioc 处理 class sun.myproxybean.MyServiceImpl的myMapper1 处理 class sun.myproxybean.MyServiceImpl的myMapper2 处理 class sun.myproxybean.MyServiceImpl的myDao public void sun.myproxybean.MyServiceImpl.multi() @sun.myproxy.MY_TRANSACTIONAL() public final void sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.multi() null public abstract void sun.myproxybean.MyService.multi() null Exception in thread "main" java.lang.RuntimeException: java.lang.NullPointerException at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:112) at sun.myproxy.OdsTransactionCglibFactory.intercept(OdsTransactionCglibFactory.java:65) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.multi(<generated>) at sun.mybatis.Main.main(Main.java:58) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) Caused by: java.lang.NullPointerException at sun.myproxybean.MyServiceImpl.multi(MyServiceImpl.java:29) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.CGLIB$multi$0(<generated>) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d$$FastClassByCGLIB$$8fca1f76.invoke(<generated>) at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at sun.myproxy.OdsTransactionCglibFactory.aopInvoke(OdsTransactionCglibFactory.java:73) at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:100) ... 8 more
显示ioc null了,故我们回顾一下IOC()中,反哺是反到原始对象
为了切子函数,不再使用method.invoke(origin object, args[])原对象,而是使用代理对象(子类对象)methodProxy.invokeSuper(oCglib, args);,因此反哺应在代理对象(子类对象)上的private成员变量上进行,注意子类没有继承父类的private field,是在子类对象上用反射操作内存对象中父类那一部分的内存
protected Object getTarget() {
return this.target;
}
@SuppressWarnings("squid:S3011")
protected void IOC() throws IllegalAccessException {
if (!alreadyIOC) {
Class clProxy = target.getClass();
Field[] fields = clProxy.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (!field.isAnnotationPresent(getBeanInjectAnnotationGuice()))
continue;
String name = field.getName();
Class fieldType = field.getType();
Object obj = getBeanFromFactoryGuice(fieldType);
if (obj != null)
field.set(getTarget(), obj);
}
alreadyIOC = true;
}
}
public class OdsTransactionCglibFactory extends OdsTransactionProxyFactory implements MethodInterceptor {
public OdsTransactionCglibFactory(Object target) {
super(target);
}
private Object targetCglib;
@Override
protected Object getTarget() {
return targetCglib;
}
@Override
public Object getProxyInstance() {
Enhancer en = new Enhancer();
en.setSuperclass(target.getClass());
en.setCallback(this);
Object oCglib = en.create();
this.targetCglib = oCglib;
return oCglib;
}
5 发现一个bug
@Inject
private MyDao myDao; // https://www.cnblogs.com/silyvin/p/14237270.html 第5点
// private MyDaoImpl myDao; // 导致myDao不是注入代理类对象,而是new MyDaoImpl,guice会自己newInstance一个
6 对于接口MyService中没有,仅在MyServiceImpl中有的子函数,处理“迎合某些人习惯将注解加到接口上”的时候去取接口的getMethod时报错
首次处理 class sun.myproxybean.MyServiceImpl的ioc 处理 class sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d的myMapper1 处理 class sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d的myMapper2 处理 class sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d的myDao public void sun.myproxybean.MyServiceImpl.multi() @sun.myproxy.MY_TRANSACTIONAL() public final void sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.multi() null public abstract void sun.myproxybean.MyService.multi() null Exception in thread "main" java.lang.RuntimeException: java.lang.NoSuchMethodException: sun.myproxybean.MyService.subFunction() at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:117) at sun.myproxy.OdsTransactionCglibFactory.intercept(OdsTransactionCglibFactory.java:74) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.multi(<generated>) at sun.mybatis.Main.main(Main.java:58) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) Caused by: java.lang.NoSuchMethodException: sun.myproxybean.MyService.subFunction() at java.lang.Class.getMethod(Class.java:1786) at sun.myproxy.OdsTransactionCglibFactory.intercept(OdsTransactionCglibFactory.java:42) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.subFunction(<generated>) at sun.myproxybean.MyServiceImpl.multi(MyServiceImpl.java:30) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.CGLIB$multi$0(<generated>) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d$$FastClassByCGLIB$$8fca1f76.invoke(<generated>) at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at sun.myproxy.OdsTransactionCglibFactory.aopInvoke(OdsTransactionCglibFactory.java:83) at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:105) ... 8 more
jdk动态代理的子函数没有办法切,被切到的一定是接口中有的方法,所以天然无此问题;而cglib会切到大概率没有事务注解的子函数,所以就暴露了这个问题
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
IOC();
// ewirweod
MY_TRANSACTIONAL scef_db_transactional = method.getAnnotation(MY_TRANSACTIONAL.class);
// 迎合有些人习惯将注解加到接口上
Class[] interfaces = target.getClass().getInterfaces();
if(scef_db_transactional == null) {
for(Class inter : interfaces) {
try {
Method classMethod = inter.getMethod(method.getName(), method.getParameterTypes());
scef_db_transactional = classMethod.getAnnotation(MY_TRANSACTIONAL.class);
} catch (NoSuchMethodException e1) {
;
} catch (Throwable e) {
e.printStackTrace();
}
break;
}
}
6.5 又引出了一个知识:
// MyServiceImpl&&CGLIB.method uewoqruwiiii { Method classMethod = o.getClass().getMethod(method.getName(), method.getParameterTypes()); System.out.println(classMethod); MY_TRANSACTIONAL my_transactional = classMethod.getAnnotation(MY_TRANSACTIONAL.class); System.out.println(my_transactional); }
这个地方居然也报错了
public class MyServiceImpl implements MyService { 。。。。。 /** * public o.getClass().getMethod 可以找到 * protected 不行 */ public void subFunction() { System.out.println(this); }
原因为:
getDeclaredMethod*()获取的是类自身声明的所有方法,包含public、protected和private方法。getMethod*()获取的是类的所有共有方法,这就包括自身的所有public方法,和从基类继承的、从接口实现的所有public方法。
anyway,所有getMethod方法都加上try catch throwable吧,以免影响主体进程
7 最终的日志
首次处理 class sun.myproxybean.MyServiceImpl的ioc 处理 class sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d的myMapper1 处理 class sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d的myMapper2 处理 class sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d的myDao public void sun.myproxybean.MyServiceImpl.multi() @sun.myproxy.MY_TRANSACTIONAL() public final void sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.multi() null public abstract void sun.myproxybean.MyService.multi() null protected void sun.myproxybean.MyServiceImpl.subFunction() null Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary. 首次处理 class sun.myproxybean.MyDaoImpl的ioc 处理 class sun.myproxybean.MyDaoImpl$$EnhancerByCGLIB$$4b5f7f6c的myMapper2 public void sun.myproxybean.MyDaoImpl.multi() @sun.myproxy.MY_TRANSACTIONAL() public final void sun.myproxybean.MyDaoImpl$$EnhancerByCGLIB$$4b5f7f6c.multi() null public abstract void sun.myproxybean.MyDao.multi() null Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ArithmeticException: / by zero at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:117) at sun.myproxy.OdsTransactionCglibFactory.intercept(OdsTransactionCglibFactory.java:74) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.multi(<generated>) at sun.mybatis.Main.main(Main.java:58) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) Caused by: java.lang.RuntimeException: java.lang.ArithmeticException: / by zero at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:117) at sun.myproxy.OdsTransactionCglibFactory.intercept(OdsTransactionCglibFactory.java:74) at sun.myproxybean.MyDaoImpl$$EnhancerByCGLIB$$4b5f7f6c.multi(<generated>) at sun.myproxybean.MyServiceImpl.multi(MyServiceImpl.java:33) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d.CGLIB$multi$1(<generated>) at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$5b3ba48d$$FastClassByCGLIB$$8fca1f76.invoke(<generated>) at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at sun.myproxy.OdsTransactionCglibFactory.aopInvoke(OdsTransactionCglibFactory.java:83) at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:105) ... 8 more Caused by: java.lang.ArithmeticException: / by zero at sun.myproxybean.MyDaoImpl.multi(MyDaoImpl.java:18) at sun.myproxybean.MyDaoImpl$$EnhancerByCGLIB$$4b5f7f6c.CGLIB$multi$0(<generated>) at sun.myproxybean.MyDaoImpl$$EnhancerByCGLIB$$4b5f7f6c$$FastClassByCGLIB$$e94f8239.invoke(<generated>) at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) at sun.myproxy.OdsTransactionCglibFactory.aopInvoke(OdsTransactionCglibFactory.java:83) at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:105) ... 16 more
8
2021.4.3 发现一个大bug,延迟rollback
/Users/joyce/work/MyTest/MyModbus/src/main/java/sun/myproxy/OdsTransactionProxyFactory.java
protected Object invokeHasTransactional(MY_TRANSACTIONAL scef_db_transactional, Method method, Object[] args, MethodProxy methodProxy, Object oCglib) throws Throwable {
try {
startTransaction();
// Object returnValue = method.invoke(target, args);
Object returnValue = aopInvoke(method, args, methodProxy, oCglib);
commit();
return returnValue;
} catch (InvocationTargetException ie) {
Throwable throwable = ie.getTargetException();
rollback();
// RuntimeException will popup the default error popup view of ecore
throw throwable;
} catch (Throwable e) {
/**
* 上面的InvocationTargetException是为了jdk动态代理
* 调整为cglib时忘了在这里也rollback
* 也就是说rollback被延迟到endTransaction
* 最终依托DefaultSqlSession.BaseExecutor.close回滚
* 在这期间,其它session若处于RU隔离级别则可见这些未回滚的数据
* 顺便说一句,resetAutoCommit也在这条线上,reset后归连接给连接池
*/
rollback();
throw e;
} finally {
endTransaction();
}
}
最初发现这个问题,是由于发现异常居然没有走InvocationTargetException,这才意识到这会用的是cglib,异常直接走Exception/Throwable,不会再像jdk那样抛InvocationTargetException
进而又发现exception/throwable里面居然没有rollback
我们做个实验印证这个延迟rollback的想法
在OdsTransactionProxyProvider中确认使用的是cglib代理,去掉我们此次加的rollback
java | another db client |
打开一个mysql 5.7默认会话,show variables like "tx_isolation"; 显示REPEATABLE-READ | |
set session transaction isolation level read uncommitted; 再次执行查询,READ-UNCOMMITTED | |
在endTransaction处打断点,run | |
到断点(注意可能有多次,因为链路上service和dao的multi方法都有事务注解 而我们要查看的回滚数据是service层插入的,故在第2个endTransactional打断点观察) 此时入参method:public void sun.myproxybean.MyServiceImpl.multi() |
查询数据,显示100 db,即没有回滚 |
放开断点,结束 | |
发现回滚 |
*
//设置read uncommitted级别:
set session transaction isolation level read uncommitted;
//设置read committed级别:
set session transaction isolation level read committed;
//设置repeatable read级别:
set session transaction isolation level repeatable read;
//设置serializable级别:
set session transaction isolation level serializable;
加上我们此次的rollback
java | another db client |
打开一个mysql 5.7默认会话,show variables like "tx_isolation"; 显示REPEATABLE-READ | |
set session transaction isolation level read uncommitted; 再次执行查询,READ-UNCOMMITTED | |
在endTransaction处打断点,run | |
到断点(注意可能有多次,因为链路上service和dao的multi方法都有事务注解 而我们要查看的回滚数据是service层插入的,故在第2个endTransactional打断点观察) 此时入参method:public void sun.myproxybean.MyServiceImpl.multi() |
查询数据,第1次有,第2次无,证明此次加入rollback有效 |
放开断点,结束 | |
8.5
2020.4.23
这个问题不仅会导致事务delay回滚,也会导致norollbackfor 失效,因为它在InnovationTargetException(jdk)里面处理,调整为cglib就忘记了 ,以下代码不入仓库
9
2021.4.28
关于第4点 ioc反哺,一直有莫名其妙的疑问,后来发现,是这个知识点的薄弱所致疑问:
子类虽然没继承父类private属性,但是子类对象空间里是有这些字段的,通过反射存取
简单的说,子类虽然没有,但子类对象有
而且反射要拿父类去getDeclaredFields,然后field.set(cglib子类对象, val)
这里又引出另一个知识点:cglib切面模型
cglibobj.mother
aopfront
origin.mother
this.sun
this->cglibobj
cglibobj.sun (protected, public)
aopfront
origin.sun
set/get this.field1
aopend
aopend
cglib的代理对象,在cglib类方法中是不操作的,而且也无法操作(针对父类private属性),也不需要操作
用一段代码建模:(不进仓库)
// 此类代表原始类,里面有有很多@Inject或@Autowired public class TestTemp { //@Inject or @Autowired // 由于这个类的对象没有注入(注入的是cglib子类对象),故自动装配失败,需要我们手动反哺 private String guiceService; protected String getGuiceService() { return guiceService; } }
// 此类代表cglib public class SubTestTemp extends TestTemp { public static void main(String []f) throws IllegalAccessException { SubTestTemp subTestTemp = new SubTestTemp(); Field [] fields = TestTemp.class.getDeclaredFields(); for(Field field : fields) { field.setAccessible(true); field.set(subTestTemp, "反哺的ioc对象"); } System.out.println(subTestTemp.getGuiceService()); } }
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" "。。。。。。。
反哺的ioc对象
Process finished with exit code 0
10
2021.4.30
事物中控支持按名称getBean。此部分代码不入git仓库:/Users/mac/work/MyTest/MyModbus/src/main/java/sun/myproxy/OdsTransactionProxyFactory.java
后来还是在本地写了一段测试代码:
如果不支持allowNamed时,报错:
首次处理 class sun.myproxybean.MyDaoImpl的ioc
处理 class sun.myproxybean.MyDaoImpl$$EnhancerByCGLIB$$aa3d8422的myMapper2
Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:
1) No implementation for sun.guicename.IGuiceName was bound.
Did you mean?
* sun.guicename.IGuiceName annotated with @com.google.inject.name.Named(value=guiceName1)
* sun.guicename.IGuiceName annotated with @com.google.inject.name.Named(value=guiceName2)
while locating sun.guicename.IGuiceName
1 error
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1075)
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1034)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1086)
at sun.myproxy.OdsTransactionProxyFactory.getBeanFromFactoryGuice(OdsTransactionProxyFactory.java:35)
at sun.myproxy.OdsTransactionProxyFactory.getBeanFromFactoryGuice(OdsTransactionProxyFactory.java:44)
at sun.myproxy.OdsTransactionProxyFactory.IOC(OdsTransactionProxyFactory.java:73)
at sun.myproxy.OdsTransactionCglibFactory.intercept(OdsTransactionCglibFactory.java:32)
at sun.myproxybean.MyDaoImpl$$EnhancerByCGLIB$$aa3d8422.multi(<generated>)
at sun.myproxybean.MyServiceImpl.multi(MyServiceImpl.java:33)
at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$ba19a943.CGLIB$multi$0(<generated>)
at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$ba19a943$$FastClassByCGLIB$$97cd6499.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at sun.myproxy.OdsTransactionCglibFactory.aopInvoke(OdsTransactionCglibFactory.java:97)
at sun.myproxy.OdsTransactionProxyFactory.invokeHasTransactional(OdsTransactionProxyFactory.java:123)
at sun.myproxy.OdsTransactionCglibFactory.intercept(OdsTransactionCglibFactory.java:88)
at sun.myproxybean.MyServiceImpl$$EnhancerByCGLIB$$ba19a943.multi(<generated>)
at sun.mybatis.Main.main(Main.java:64)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
11 2022.7.16 支持一层继承
此代码不进仓库,完整代码在transaction 继承备案
注入B/C,不注入A
A
@Inject M1
@Inject M2
func() {
M1/M2.xxx 报错 null
}
B/C extends A
@Inject M3
function() {
xxx
super.func();
}
12 2022.7.16
cglib debug的bug
ioc这段代码放着debug 没进断点,但事情做了,日志尽然也打了
去掉这段代码,报null,证明ioc是有用的,虽然debug没进去
对于本地demo,Run时,不会打“异常”(重复set),debug时有;对于线上,run时也有“异常”,但ioc确实不能少