戳蓝字“CSDN云计算”关注我们哦!
“中国最好面试官”
记一次“带套路”的面试
//接口
interface Service {
void doNeedTx();
void doNotneedTx();
}
//目标类,实现接口
class ServiceImpl implements Service {
public void doNeedTx() {
System.out.println("execute doNeedTx in ServiceImpl");
}
//no annotation here
public void doNotneedTx() {
this.doNeedTx();
}
}
//代理类,也要实现相同的接口
class ProxyByJdkDynamic implements Service {
//包含目标对象
private Service target;
public ProxyByJdkDynamic(Service target) {
this.target = target;
}
//目标类中此方法带注解,进行特殊处理
public void doNeedTx() {
//开启事务
System.out.println("-> create Tx here in Proxy");
//调用目标对象的方法,该方法已在事务中了
target.doNeedTx();
//提交事务
System.out.println("<- commit Tx here in Proxy");
}
//目标类中此方法没有注解,只做简单的调用
public void doNotneedTx() {
//直接调用目标对象方法
target.doNotneedTx();
}
}
我:目标类是我们自己写的,肯定是没有事务的。代理类是系统生成的,对带注解的方法进行事务增强,没有注解的方法原样调用,所以事务是代理类加上去的。
那回到一开始的问题,我们调用的方法不带注解,因此代理类不开事务,而是直接调用目标对象的方法。当进入目标对象的方法后,执行的上下文已经变成目标对象本身了,因为目标对象的代码是我们自己写的,和事务没有半毛钱关系,此时你再调用带注解的方法,照样没有事务,只是一个普通的方法调用而已。
他:所以这个问题的答案就是没有事务。
我:这是我们分析推理的结果,究竟对不对呢,还需要验证一下。验证过程如下:
找一个正常可用的Spring项目,把一个@Service的接口注入到一个@Controller类里面,进行检测,请仔细看下代码:
//是否是JDK动态代理
System.out.println("isJdkDynamicProxy => " + AopUtils.isJdkDynamicProxy(exampleService));
//是否是CGLIB代理
System.out.println("isCglibProxy => " + AopUtils.isCglibProxy(exampleService));
//代理类的类型
System.out.println("proxyClass => " + exampleService.getClass());
//代理类的父类的类型
System.out.println("parentClass => " + exampleService.getClass().getSuperclass());
//代理类的父类实现的接口
System.out.println("parentClass's interfaces => " + Arrays.asList(exampleService.getClass().getSuperclass().getInterfaces()));
//代理类实现的接口
System.out.println("proxyClass's interfaces => " + Arrays.asList(exampleService.getClass().getInterfaces()));
//代理对象
System.out.println("proxy => " + exampleService);
//目标对象
System.out.println("target => " + AopProxyUtils.getSingletonTarget(exampleService));
//代理对象和目标对象是不是同一个
System.out.println("proxy == target => " + (exampleService == AopProxyUtils.getSingletonTarget(exampleService)));
//目标类的类型
System.out.println("targetClass => " + AopProxyUtils.getSingletonTarget(exampleService).getClass());
//目标类实现的接口
System.out.println("targetClass's interfaces => " + Arrays.asList(AopProxyUtils.getSingletonTarget(exampleService).getClass().getInterfaces()));
System.out.println("----------------------------------------------------");
//自己模拟的动态代理的测试
Service target = new ServiceImpl();
ProxyByJdkDynamic proxy = new ProxyByJdkDynamic(target);
proxy.doNeedTx();
System.out.println("-------");
proxy.doNotneedTx();
System.out.println("-------");
以下是输出结果:
//是JDK动态代理
isJdkDynamicProxy => true
//不是CGLIB代理
isCglibProxy => false
//代理类的类型,带$的
proxyClass => class com.sun.proxy.$Proxy82
//代理类的父类
parentClass => class java.lang.reflect.Proxy
代理类的父类实现的接口
parentClass's interfaces => [interface java.io.Serializable]
//代理类实现的接口,包含了目标类的接口IExampleService,还有其它的
proxyClass's interfaces => [interface org.eop.sb.example.service.IExampleService,
interface org.springframework.aop.SpringProxy,
interface org.springframework.aop.framework.Advised,
interface org.springframework.core.DecoratingProxy]
//代理对象
proxy => org.eop.sb.example.service.impl.ExampleServiceImpl@54561bc9
//目标对象
target => org.eop.sb.example.service.impl.ExampleServiceImpl@54561bc9
//代理对象和目标对象输出的都是@54561bc9,还真有点懵逼
//进行测试后发现,其实不是同一个,只是toString()的问题
proxy == target => false
//目标类,我们自己写的
targetClass => class org.eop.sb.example.service.impl.ExampleServiceImpl
//目标类实现的接口,我们自己写的
targetClass's interfaces => [interface org.eop.sb.example.service.IExampleService]
----------------------------------------------------
//带注解的方法调用,有事务的开启和提交
-> create Tx here in Proxy
execute doNeedTx in ServiceImpl
<- commit Tx here in Proxy
-------
//没有注解的方法调用,是没有事务的
execute doNeedTx in ServiceImpl
-------
经过测试后,发现和我们推断的一模一样。
他:你真是打破砂锅问到底,把这个事情彻底弄明白了。
我:对于没有实现接口的类,只能使用CGLIB来生成代理。(开始套路),假设有这样一个类,它里面包含public方法,protected方法,private方法,package方法,final方法,static方法,我都给它们加上事务注解,哪些方法会有事务呢?
他:那我就现学现卖,事务是由代理加进去的,所以关键就是代理如何生成。按照上面所说的代理应该具备的特点来看,只能通过继承的方式生成一个子类来充当代理,看起来就是这样的:
class Target {
public void doNeedTx() {
System.out.println("execute doNeedTx in Target");
}
//no annotation here
public void doNotneedTx() {
this.doNeedTx();
}
}
class ProxyByCGLIB extends Target {
private Target target;
public ProxyByCGLIB(Target target) {
this.target = target;
}
public void doNeedTx() {
System.out.println("-> create Tx in Proxy");
target.doNeedTx();
System.out.println("<- commit Tx in Proxy");
}
public void doNotneedTx() {
target.doNotneedTx();
}
}
而且,必须在代理类里重写带注解方法以添加开启事务、提交事务的代码。从这个角度来说,private方法不能被继承,final方法不能被重写,static方法和继承不相干,所以它们3个的事务不起作用。
public方法,protected方法可以被重写以添加事务代码,对于package方法来说,如果生成的子类位于同一个包里,就可以被重写以添加事务代码。所以public方法事务肯定起作用,剩下那2个就不确定了,只能说它们有这个可能性。
我:你分析的很好,CGLIB确实是按照这种方式生成了子类作为代理,而且和父类在同一个包下。不过Spring选择让protected方法和package方法不支持事务,所以只有public方法支持事务。
使用和上面一样的方法进行了测试,结果如下:
//不是JDK动态代理
isJdkDynamicProxy => false
//是CGLIB代理
isCglibProxy => true
//生成的代理类的类型,带$$的
proxyClass => class org.eop.sb.example.service.impl.ExampleServiceImpl$$EnhancerBySpringCGLIB$$5320b86e
//代理类的父类,就是目标类
parentClass => class org.eop.sb.example.service.impl.ExampleServiceImpl
//父类实现的接口,就是我们自己写的接口
parentClass's interfaces => [interface org.eop.sb.example.service.IExampleService]
/**代理类实现的接口,并不包含目标类的接口*/
proxyClass's interfaces => [interface org.springframework.aop.SpringProxy,
interface org.springframework.aop.framework.Advised,
interface org.springframework.cglib.proxy.Factory]
//代理对象
proxy => org.eop.sb.example.service.impl.ExampleServiceImpl@1b2702b1
//目标对象
target => org.eop.sb.example.service.impl.ExampleServiceImpl@1b2702b1
//代理对象和目标对象不是同一个
proxy == target => false
//目标类,我们自己写的类
targetClass => class org.eop.sb.example.service.impl.ExampleServiceImpl
//目标类实现的接口
targetClass's interfaces => [interface org.eop.sb.example.service.IExampleService]
由于采用的是相同的测试代码,所以目标类是实现了接口的,不过这并不影响使用CGLIB来生成代理。可见,代理类确实继承了目标类以保持和目标类的类型兼容,对外接口相同。
注:只要是以代理方式实现的声明式事务,无论是JDK动态代理,还是CGLIB直接写字节码生成代理,都只有public方法上的事务注解才起作用。而且必须在代理类外部调用才行,如果直接在目标类里面调用,事务照样不起作用。
他:以前在网上也看到过有人说事务不生效的情况,我想,这个问题不会发生在我身上了。
重磅 | 华为发布绝杀计算战略!投15亿美元打造开放生态,全球最快AI训练集群Atlas 900,绝了!
你需要知道的那些 redis 数据结构(前篇)
DeepMind悄咪咪开源三大新框架,深度强化学习落地希望再现
Python Web:Flask异步执行任务
程序员为什么要懂物联网?
鸿蒙 OS 的到来,能为我们改变什么?
倒计时3天!dfuse,慢雾,MYKEY技术负责人齐聚, 一站掌握区块链数据架构的秘密!