一次事务回滚失败引起的思考 | 七日打卡

141 阅读4分钟

    今天主要是分享spring中代理对象的两种方式,一种是CGLIB,一种JDK。

    初衷是前两天一个新项目中,测试事务时发现事务居然没有生效,发生异常时没有回滚数据库表!

    根据网上的各种场景,比如事务方法必须是public, 方法抛出的异常不符合规范、要在头部注解加上Exception等等,排查过后,还是没有发现缘由。

    接着自己新建个项目自己照着网上事务成功的例子,写了一版,可以成功。

    对比失败的事务回滚和成功的事务回滚,发现成功回滚的日志中打印了事务被spring拦截器捕获后进行了回滚动作,而失败事务的日志中并没有这部分的事务日志。一时间似懂非懂,没有事务回滚?这个bean不是都注入成功了吗?

    网上百度才发现有这么个主题,叫做jdk动态代理引起的spring事务不起作用。 本文并非是解决该问题的帖子,主要是自己想趁着周末把动态代理的相关知识温习一下,笔记如下:

动态代理

    原理是利用了Java语言提供的一种基础功能--反射,这种功能帮助我们直接操作类或对象,调用方法等用途。 

    至于动态两个字,查了下资料,大概是指Java本身作为一门静态的强类型语言,但是因为类似反射的机制,也具备动态类型语言的能力,加上反射,就能理解这个动态代理的解释吧。

    那么动态代理触发事务回滚,本身就是spring帮我们代理了个事务对象,来动态地对发生异常的业务进行业务回滚。 CGLib和JDK的区别,网上已有详细的资料,在这里就不再赘余。

    下面主要用代码两种动态代理的代码实现。

贴代码:

public class JDKProxyTest {
    @Test
    public void testCreateProxy() throws Exception {
        IUserDao userDao=new UserDaoImpl();

        JDKProxy jdkProxy=new JDKProxy(userDao);

        IUserDao proxy = jdkProxy.createProxy();

        proxy.addUser(111, "testJDK");
    }
}

============================================================

/**
 * CGLib 代理对象测试
 *
 * @Author: SK-Keith
 * @Date: 2021.01.16 20:28:12
 */
public class CGLibProxyTest {

    @Test
    public void testCreateProxy() throws Exception {
        UserDaoImplC userDaoImplC = new UserDaoImplC();

        CGLibProxy cgLibProxy = new CGLibProxy(userDaoImplC);

        UserDaoImplC proxy = cgLibProxy.createProxy();

        proxy.addUser(222, "testCGLib");
    }
}

如图,左边是JDK的动态代理实现测试,右边是CGLib的。

其中JDKProxy中定义了代理的目标对象,并且提供了创建代理类的方法和调用被代理类(即目标对象)的任意方法"触发器"。CGLibProxy中也同样声明了代理的目标对象,创建代理类的方法,还有个回调方法,作用跟上面的"触发器"相似。如下图。

代码

public class JDKProxy implements InvocationHandler {

    /**
     * 要被代理的目标对象
     */
    private IUserDao userDao;

    public JDKProxy(IUserDao userDao) {
        this.userDao = userDao;
    }

    /**
     * 创建代理类
     *
     * @return
     */
    public IUserDao createProxy() {
        return (IUserDao) Proxy.newProxyInstance(userDao.getClass()
                .getClassLoader(), userDao.getClass().getInterfaces(), this);
    }

    /**
     * 代理实现方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("addUser".equals(method.getName())) {
            // 记录日志:
            System.out.println("jdk 日志记录======");
            //调用目标对象的方法
            Object result = method.invoke(userDao, args);
            return result;
        }
        return method.invoke(userDao, args);
    }
}
====================================================================================
public class CGLibProxy implements MethodInterceptor {
    /**
     * 被代理的目标类
     */
    private UserDaoImplC userDaoImplC;

    public CGLibProxy(UserDaoImplC userDaoImplC) {
        super();
        this.userDaoImplC = userDaoImplC;
    }

    /**
     * 创建代理类
     * @return
     */
    public UserDaoImplC createProxy() {
        // 使用CGLIB生成代理
        // 1. 声明增强类实例,用于生产代理类
        Enhancer enhancer = new Enhancer();
        // 2. 设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
        enhancer.setSuperclass(userDaoImplC.getClass());
        // 3. 设置回调函数
        enhancer.setCallback(this);
        // 4. 创建代理
        return (UserDaoImplC) enhancer.create();
    }

    /**
     * 回调函数
     * @param proxy 代理对象
     * @param method 委托类方法
     * @param args 方法参数
     * @param methodProxy 每个被代理的方法都对应一个MethodProxy对象
     *                    methodProxy.invokeSuper方法最终调用委托类(目标类)的addAdmin方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if("addUser".equals(method.getName())){
            System.out.println("CGLIB,日志记录==============");
            //调用目标类的方法
            Object obj = methodProxy.invokeSuper(proxy, args);
            return obj;
        }
        return methodProxy.invokeSuper(proxy, args);
    }
}

最后接口的实现就写的一样的了,如下图。

结果如下图:

今天就先分享动态代理的两种实现,对于前文中提出的问题,还没有找到解决方法,后续有方法再补充跟进,谢谢大家!