当被 Spring 代理类访问时,Kotlin 实例变量为空

Posted

技术标签:

【中文标题】当被 Spring 代理类访问时,Kotlin 实例变量为空【英文标题】:Kotlin instance variable is null when accessed by Spring proxied class 【发布时间】:2018-02-17 18:29:22 【问题描述】:

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

@Service
@Transactional
open class MyService  ... 

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

但是,这会导致在代理服务上调用函数时出现问题,该函数会尝试访问变量:

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

    fun doWork() 
        internalVariable.execute() // NullPointerException
    

internalVariable 被分配为其声明的一部分,没有任何注释(如@Autowired 等),并且在我删除@Transactional 注释和Spring 代理类的要求。

为什么当 Spring 代理/子类化我的服务类时这个变量为空?

【问题讨论】:

你是如何测试MyService的? 我不知道你在拿到 NPE 后对你的班级究竟做了什么,但请尝试打开“doWork”功能。如果你在另一个 bean 中使用这个 bean,那么方法 'doWork' 将不会被代理,因为它是 final 并且 Spring 创建了一个 CGLib 代理。 这很奇怪 - 我实际上可以通过将 property 标记为 open 来解决这个问题。所以open protected val internalVariable = ... 【参考方案1】:

我遇到了类似的问题,Rafal G 和 Craig Otis 的上述 cmets 帮助了我——所以我想建议接受以下文章作为答案(或者将上面的 cmets 更改为一个答案,他们就会被接受)。

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

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

说明:

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

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

因此,这些 bean 需要 Proxy/Wrapper,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 中,类和方法是最终的,除非显式打开。

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

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

【讨论】:

很好的答案,我认为您所描述的正是正在发生的事情。 我在尝试对使用类属性的参数使用默认值时遇到了同样的问题。感谢您很好地解释了如何解决它以及为什么!

以上是关于当被 Spring 代理类访问时,Kotlin 实例变量为空的主要内容,如果未能解决你的问题,请参考以下文章

Spring 代理类和 Kotlin 中的空指针异常

kotlin的哪个功能可以替代通过Java代理进行的方法拦截

浅谈java中内置的观察者模式与动态代理的实现

spring静态代理

Spring基础:代理模式介绍

对比Java学Kotlin代理