Spring 代理 class 访问时,Kotlin 实例变量为 null

Kotlin instance variable is null when accessed by Spring proxied class

我有一个服务 class 被 Spring 代理,像这样:

@Service
@Transactional
open class MyService { ... }

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

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

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

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

internalVariable 是作为其声明的一部分分配的,没有任何注释(如 @Autowired 等),并且 在我删除 [=30] 时工作正常=] @Transactional 注释和 Spring 代理 class.

的要求

当 Spring 是 proxying/subclassing 我的服务 class 时,为什么这个变量为空?

我遇到了类似的问题,Rafal G 和 Craig Otis 的上述评论帮助了我——所以我想建议接受以下文章作为答案(或评论以上被更改为答案并被接受)。

解决方法:打开 method/field.

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

解释:

为什么这个解决方案更复杂,肯定与 Spring AOP、最终 fields/methods、CGLIB 代理和 如何 Spring+CGLIB 有关尝试处理最终方法(或字段)。

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

因此这些 bean 需要 Proxy/Wrapper,Spring 有 2 个选择——但是当父 class 不是接口时,只有 CGLIB 可用。

When using CGLIB to proxy classes Spring will create a subclass called something like myService$EnhancerByCGLIB. This enhanced class will override some if not all of your business methods to apply cross-cutting concerns around your actual code.

Here comes the real surprise. This extra subclass does not call super methods of the base class. Instead it creates second instance of myService and delegates to it. This means you have two objects now: your real object and CGLIB enhanced object pointing to (wrapping) it.

发件人:spring singleton bean fields are not populated

引用者:

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

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

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