Spring代理类访问Kotlin实例变量为null

Cra*_*tis 8 proxy spring visibility nullpointerexception kotlin

我有一个Spring代理的服务类,如下所示:

@Service
@Transactional
open class MyService { ... }
Run Code Online (Sandbox Code Playgroud)

如果删除open修饰符,Spring会抱怨它需要代理该类以应用@Transactional注释调整。

但是,在代理服务上尝试访问变量的函数时,这会引起问题:

@Service
@Transactional
open class MyService { 
    protected val internalVariable = ...

    fun doWork() {
        internalVariable.execute() // NullPointerException
    }
}
Run Code Online (Sandbox Code Playgroud)

internalVariable被指定为它声明的一部分,没有任何注解(如@Autowired,等),和正常工作时,我删除@Transactional注释和要求,为春季代理类。

当Spring代理/子类化我的服务类时,为什么此变量为null?

小智 13

我遇到了类似的问题,Rafal G和Craig Otis的上述评论对我有所帮助-所以我建议将以下文章作为答案接受(或将以上评论改为答案,然后将其接受) )。

解决方案: 打开方法/字段。

(我遇到过类似的情况,原因是封闭的方法导致了问题。但是,无论是字段/方法,解决方案都是相同的,而且我认为一般原因是相同的...)

说明:

为什么这是解决方案更复杂,并且肯定与Spring AOP,最终字段/方法,CGLIB代理以及Spring + CGLIB如何尝试处理最终方法(或字段)有关。

Spring使用代理来表示某些对象来处理面向方面的编程所处理的某些问题。服务和控制器会发生这种情况(尤其是在提供@Transactional或其他需要AOP解决方案的建议时)。

因此,这些bean需要一个Proxy / Wrapper,而Spring有两个选择-但是当父类不是接口时,只有CGLIB可用。

当使用CGLIB代理类时,Spring将创建一个名为myService $ EnhancerByCGLIB的子类。这个增强的类将覆盖某些(如果不是全部)业务方法,以在您的实际代码周围应用横切关注点。

真正的惊喜来了。这个额外的子类不会调用基类的超级方法。相反,它创建myService的第二个实例并委托给它。这意味着您现在有两个对象:实际对象和指向(包装)它的CGLIB增强对象。

来自:未填充春季的单例豆字段

引用人:Spring AOP CGLIB代理的字段为null

在Kotlin中,除非明确打开,否则类和方法是最终的。

我不知道Spring / CGLib何时以及如何选择使用目标委托将Bean封装在EnhancerByCGLIB中的魔力(以便它可以使用最终方法/字段)。就我而言,调试器向我展示了2种不同的结构。当父方法打开时,它不会创建委托(而是使用子类),并且在没有NPE的情况下可以工作。但是,当特定方法关闭时 Spring / CGLIB将使用该包装对象并将其委派给正确初始化的目标委托。由于某种原因,该方法的实际调用是在上下文是包装器的情况下完成的带有未初始化的字段值(NULL),从而导致NPE。(已经调用了实际目标/委托上的方法,应该没有问题)。

Craig可以通过打开属性(而不是方法)来解决问题-我怀疑这具有类似的效果,即允许Spring / CGLib不使用委托,或以某种方式正确使用委托。