今天主要是分享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);
}
}
最后接口的实现就写的一样的了,如下图。
结果如下图:
今天就先分享动态代理的两种实现,对于前文中提出的问题,还没有找到解决方法,后续有方法再补充跟进,谢谢大家!