先来看一段代码:
@Service
@Slf4j
public class AopTestService {
public String name = "真的吗";
@Retryable
public void test(){
// 模仿业务操作
log.debug("name:{}", this.name);
// 模仿内部操作,失败重试
}
}
很简略的代码,而后在另一个类中进行调用
public void test(){
testService.test();
log.info("name:{}", testService.name);
}
问题也很简略,以上代码打印输出什么?
如果没能看进去,无妨先来看(笑)看(笑)我是怎么触发一个简略的BUG。
bug之路
以上代码必定是不标准的。
失常应该是类里定义为一个private
公有变量,而后提供getter/setter
办法供内部拜访。
像这种将变量间接为定义public
,在外部类间接拜访的状况,失常状况下我是写不进去。
然而,话说某天,活急了,一个类写了上千行代码,必定得想把公共代码提取进去,将代码依据业务拆分。
原始类中有一个private
的成员变量,在该类外部办法中拜访。因为部份代码拆分到其它类当中,该变量须要在内部被拜访,我一时偷懒,就将该变量的拜访级别由private改为public
。
省略业务代码,大略就变成了下面一结尾的示例代码。 司空见惯的,我认为这样就能拜访了。
但我却被啪啪打脸了。
失常状况下,这样尽管代码不标准,但的确能拜访。 为什么这里确不能拜访了呢?
因为我在办法加了个@Retryable
注解。
retryable是什么? 因为一些网络,内部接口等不可预知的问题,咱们的程序或者接口会失败,这时就须要重试机制,比方延时1S后重试、距离一直减少重试等,本人去实现这些性能的话,显得轻便而不优雅,所以spring官网实现了retryable模块。
这里能够略过它的原理,只需晓得它是应用了动静代理+AOP。
这个注解需给AopTestService
生成代理类。而动静代理是不能代理属性的。所以在另一个类当中,应用AopTestService
的代理类不能间接拜访指标类的成员变量。
严格意义来说,这还不算BUG,因为在调试阶段就立马发现了,但我的确没能一眼看进去。
可能一眼看出问题所在的大佬,请喝茶。
当初咱们晓得,动静代理类只能代理办法而不能代理属性。然而话语是红润的,咱们还是要有间接的证据。 最表象的起因,间接Debug截图能够察看到,aopTestService
由cglib
生成了代理类。在这个代理类里value
值为null
。
再通过反编译动静代理生成的代码,能够看到只有办法的定义,没有父类变量的定义。
为什么spring中的动静代理不能代理属性?
后面说到,spring动静代理只能代理办法,不能代理属性。
cglib都能够,为什么spring不能够呢?
再深刻一点。咱们能够在源码中断点,看看cglib到底如何没有代理属性。
在spring-aop模块中查找类ObjenesisCglibAopProxy
,从名字当中就可以看进去,spring的动静代理全用了Objenesis
+cglib
。
在这个类中的createProxyClassAndInstance
办法断点,在srping boot启动的时候,能够察看到:
能够看到这里应用了Objenesis
实例化了AopTestService
代理对象。如果Objenesis
实例失败,再通过默认构造方法进行实例。
因为没有调用构造方法,所以spring生成动静代理类的时候没能保留父类的属性。
所以Objenesis
是什么?
从以上的代码和正文当中也能够揣测得出,它是一个能够绕过构造方法实例对象的一个工具。
为什么须要绕过构造方法实例对象?
这又分为spring
和非spring
。
非srping下的确有这样的场景,比方
结构器须要参数 结构器有side effects 结构器会抛异样
因而,在类库中常常会有类必须领有一个默认结构器的限度。Objenesis
通过绕开对象实例结构器来克服这个限度。
至于为什么spring要应用Objenesis
绕过构造方法,那就是另一个问题了。
java为什么要有private关键字?
这仿佛是一个无厘头的问题,然而的确有很多初学者有这个疑难。 我想了想,至多在我刚接触java的时候没想过这个问题。创立一个java bean
,private
所有变量,而后主动生成getter/setter
干就完了。
又比方这个知乎问题,看起来看是在钓鱼,也有人认为是好问题,不知道是不是反窜。
我感觉这位大佬说得很好
这位大佬说到最外围的点:
private标记外部代码,内部不应应用,并配合get/set使代码可控。
在一个零碎里,多人合作,从业人员,代码品质参差不齐的状况下,代码可控是如许的重要。
触类旁通
不仅仅是@Retryable
才会导致下面生效的场景,其它只有波及到动静代理和AOP的都会导致生效。
比方最常见的事务,@Transcational
。
常见的面试经,导致spring事务生效的场景有哪些?
这12种场景,除却本身的起因比方不反对事务,未被spring纳入治理等,其它诸如办法拜访权限,final办法,外部调用等等都跟动静治理和AOP无关。
拜访权限和final
- springboot2.0当前动静代理应用cglib。cglib从名字
Code Generation Library
上来看就是一个代码生成的货色,它是要重写该类,而private办法,final办法均无奈被重写。所以事务会生效。
private String value = "hello world";
@Transactional
public void proxy(ApplicationContext applicationContext) {
log.info(this.value);
}
public fianl void noProxy(ApplicationContext applicationContext) {
Object obj = applicationContext.getBean(this.getClass());
proxy(applicationContext);
}
以上示例代码中,通过在启动main办法中设置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "目录");
将生成的动静代理类输入到目录中。
再反编译过后,能够看到final
批改的办法没有在这外面,证实final办法
没有被代理到。
外部调用
- 办法外部调用。如果同类中,一个非事务办法调用另一个事务办法,默认应用的是this对象,非动静代理类的指标对象调用,所以会生效。
留神以上两点,这是考点。
再来一题
在下面的示例代码的根底上简略改一下。两个事务办法,其中一个是final办法。
@Service
@Slf4j
public class AopTestService {
private String value = "hello world";
@Transcational
public void proxy(ApplicationContext applicationContext) {
Object obj = applicationContext.getBean(this.getClass());
boolean bool1 = AopUtils.isAopProxy(obj);
boolean bool2 = AopUtils.isAopProxy(this);
log.info("bool1:{},bool2:{},value:{}", bool1, bool2, value);
}
@Transcational
public final void noProxy(ApplicationContext applicationContext) {
Object obj = applicationContext.getBean(this.getClass());
boolean bool1 = AopUtils.isAopProxy(obj);
boolean bool2 = AopUtils.isAopProxy(this);
log.info("bool1:{},bool2:{},value:{}", bool1, bool2, value);
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
请问下面两个办法别离输入什么?为什么?
咱们来捋一捋。
首先,两个办法都加上了@Transcational注解,所以类AopTestService
和两个办法都应该被代理。
而后noProxy
办法因为被final
批改,无奈被重写,所以最终noProxy
不会被代理。
当办法能够被代理的时候,代理对象应用的是指标对象来调用指标办法,所以’proxy’办法能够拜访value
。 当noProxy
办法没有被代理的时候,同时类AopTestService
却被代理了,所以只能拿代理类来调用指标办法。而代理类是无奈代理属性的。所以这里无法访问value
。
1.当代理类发现调用的办法能够代理的时候,就应用
指标对象
进行调用
这一点从下图能够看出,最终invoke的传入的是target指标对象,而是代理对象。
点击进去能够更显著的看到,应用的是代理对象外部的指标对象
2.当代理类发现调用的办法无奈代理的时候,就应用
代理对象
进行调用
这一点就更好了解了。假如我在controller层调用该service类办法,AopTestService
对象为代理对象,因该noProxy
没有被代理,因而走的就是最一般失常的应用该代理对象间接调用。
所以 proxy
办法输入:
bool1:true,bool2:false,value:hello world
noProxy
办法输入:
bool1:true,bool2:true,value:null
从proxy
办法打印进去第1个布尔值是true
,第2个布尔值是false
,也能够反过来佐证下面的说法。 就是Object obj = applicationContext.getBean(this.getClass())
间接获取spring ioc窗口里的对象是代理的对象(true),
而执行到以后调用的却是指标对象而非代理对象(false)。
然而,又一个问题来了,为什么在本人的类外面拜访外部变量value会获取到null
? 如同有点奇怪是吧?
然而,起初一想,这的确只是spring(非cglib)的一个feature
,而不是bug。
因为既然办法是final的,代表办法事务未然不失效了,在这种状况下,办法外部获取不到类的外部变量属于事务不失效引发的次生问题。 它自身是因为不标准的写法导致的,因而我认为不能算是bug。
其实写到这里,这个不成熟的ussue有了回复,大略看了一下,可能是我渣渣英语,没有表述分明,回复其实就是把我问题的形容反复了一下,大略是就这么设计的意思。
总结
java的private关键字自身是很有意义的,同时也是避免bug的利器。
如果面试官再问到你spring事务生效的起因,除了12个场景以外,你或者还能够联合本文引申进去其它的内容,疏导话题。