由Spring代理类访问时Kotlin实例变量为null [英] Kotlin instance variable is null when accessed by Spring proxied class

查看:96
本文介绍了由Spring代理类访问时Kotlin实例变量为null的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Spring代理的服务类,就像这样:

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

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

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

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

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

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

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

解决方案

我遇到了类似的问题,而Rafal G&克雷格·奥蒂斯(Craig Otis)为我提供了帮助-因此,我想建议接受以下书面内容作为答案(或者将以上评论更改为答案,然后将其接受).

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

(我遇到过类似的情况,是由封闭的方法引起的.但是,无论是字段/方法,解决方案都是相同的,并且我认为一般原因是相同的...)

说明:

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

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

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

使用CGLIB代理类时,Spring将创建一个名为 类似于myService $ EnhancerByCGLIB.这个增强的课程将 覆盖部分(如果不是全部)要应用的业务方法 围绕您的实际代码的横切关注点.

真正的惊喜在这里.这个额外的子类不会调用super 基类的方法.相反,它创建的第二个实例 myService及其委托.这意味着您现在有两个对象: 您的真实对象和CGLIB增强型对象指向(包装)它.

来自:春天的单例bean字段未填充

引用者: Spring AOP CGLIB代理的字段为空

在科特林,班级&除非显式打开,否则方法是最终方法.

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

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

I have a service class that is being proxied by Spring, like so:

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

If I remove the open modifier, Spring complains that it needs to proxy the class to apply the @Transactional annotation tweaks.

However, this is causing issues when calling a function on the proxied service, which attempts to access a variable:

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

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

The internalVariable is assigned as part of its declaration, does not have any annotations (like @Autowired, etc.), and works fine when I remove the @Transactional annotation and the requirement for Spring to proxy the class.

Why is this variable null when Spring is proxying/subclassing my service class?

解决方案

I hit a similar issue and the above comments by Rafal G & Craig Otis helped me-- so I'd like to propose that the following write up be accepted as an answer (or the comments above be changed to an answer and they be accepted).

The solution: open the method/field.

(I hit a similar case where it was a closed method that caused the problem. But whether it is a field/method the solution is the same, and I think the general cause is the same...)

Explanation:

Why this is the solution is more complicated and definitely has to do with Spring AOP, final fields/methods, CGLIB proxies, and how Spring+CGLIB attempts to deal with final methods (or fields).

Spring uses proxies to represent certain objects to handle certain concerns dealt with by Aspect Oriented Programming. This happens with services & controllers (especially when @Transactional or other advice is given that requires AOP solutions).

So a Proxy/Wrapper is needed with these beans, and Spring has 2 choices-- but only CGLIB is available when the parent class is not an interface.

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.

From: spring singleton bean fields are not populated

Referenced By: Spring AOP CGLIB proxy's field is null

In Kotlin, classes & methods are final unless explicitly opened.

The magic of how Spring/CGLib when & how chooses to wrap a Bean in an EnhancerByCGLIB with a target delegate (so that it can use finalized methods/fields) I don't know. For my case, however the debugger showed me the 2 different structures. When the parent methods are open, it does not create a delegate (using subclassing instead) and works without NPE. However, when a particular methods is closed then for that closed method Spring/CGLIB uses a wrapped object with delegation to a properly initialized target delegate. For some reason, the actual invocation of the method is done with the context being the wrapper with its uninitialized field values (NULLs), causing NPE. (Had the method on the actual target/delegate been called, there should not have been a problem).

Craig was able to solve the problem by opening the property (not the method)-- which I suspect had a similar effect of allowing Spring/CGLib to either not use a delegate, or to somehow use the delegate correctly.

这篇关于由Spring代理类访问时Kotlin实例变量为null的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆