我有一个由Spring代理的服务类,如下所示:
@Service
@Transactional
open class MyService { ... }
如果删除open
修饰符,Spring会抱怨它需要代理该类以应用@Transactional
注释调整。
但是,这会在代理服务上调用函数时导致问题,代理服务会尝试访问变量:
@Service
@Transactional
open class MyService {
protected val internalVariable = ...
fun doWork() {
internalVariable.execute() // NullPointerException
}
}
internalVariable
被指定为其声明的一部分,没有任何注释(如@Autowired
等),在我移除 {{ 1}}注释和Spring代理类的要求。
当Spring代理/子类化我的服务类时,为什么这个变量为null?
答案 0 :(得分:9)
我遇到了类似的问题以及Rafal G& S的上述评论。克雷格奥蒂斯帮助了我 - 所以我想建议接受以下写作作为答案(或者将上述评论改为答案并接受)。
解决方案:打开方法/字段。
(我遇到一个类似的情况,它是一个关闭的方法引起了问题。但是它是一个字段/方法解决方案是否相同,我认为一般原因是相同的...)
<强>解释强>
为什么这个解决方案更复杂,肯定与Spring AOP,最终字段/方法,CGLIB代理以及 Spring + CGLIB如何处理最终方法(或字段)有关。
Spring使用代理来表示某些对象来处理面向方面编程所处理的某些问题。服务&amp; amp;控制器(特别是在@Transactional或其他需要AOP解决方案的建议时)。
因此,这些bean需要一个代理/包装器,而Spring有2个选择 - 但只有当父类不是接口时,CGLIB才可用。
当使用CGLIB代理类时,Spring将创建一个名为的子类 像myService $ EnhancerByCGLIB。这个增强的课程将 覆盖一些(如果不是全部)要应用的业务方法 关于实际代码的跨领域问题。
这真是一个惊喜。这个额外的子类不会调用super 基类的方法。相反,它创建了第二个实例 myService和委托给它。这意味着您现在有两个对象: 你的真实对象和CGLIB增强对象指向(包装)它。
来自:spring singleton bean fields are not populated
引用者:Spring AOP CGLIB proxy's field is null
在Kotlin,课程&amp;方法是最终的,除非明确打开。
Spring / CGLib何时出现的神奇之处如何选择使用目标委托将Bean包装在EnhancerByCGLIB中(以便它可以使用最终的方法/字段)我不知道。对于我的情况,然而调试器向我展示了2种不同的结构。当父方法打开时,它不会创建委托(使用子类化)并且不使用NPE。但是,当某个特定方法关闭时,然后对于该封闭方法 Spring / CGLIB使用一个包装对象,并将其委托给正确初始化的目标委托。由于某种原因,方法的实际调用是在上下文是包装器及其未初始化的字段值(NULL)的情况下完成的,从而导致NPE。 (如果调用了实际目标/委托上的方法,则应该没有问题。)
Craig能够通过打开属性(而不是方法)来解决问题 - 我怀疑它有类似的效果,允许Spring / CGLib不使用委托,或以某种方式正确使用委托。