当被 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 实例变量为空的主要内容,如果未能解决你的问题,请参考以下文章