当动态代理遇到ioc (四)真正的cglib

当动态代理遇到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确实不能少

posted on 2021-01-05 18:01  silyvin  阅读(255)  评论(0编辑  收藏  举报