Spring CGLib代理问题:类成员变量为空的技术求助
你遇到的这个问题,核心在于Spring CGLIB代理的工作机制,以及private方法无法被代理的特性,结合Spring处理自注入的方式共同导致的:
1. CGLIB代理的成员变量初始化逻辑
Spring的CGLIB代理是通过生成目标类的子类实现的,但这里有个关键细节:为了高效创建代理对象(尤其是处理循环依赖场景),Spring会使用Objenesis库绕过父类的构造器来创建代理实例。这意味着代理对象的父类成员变量(比如你的enumFilters、itemsRepository等)不会被初始化,全部保持默认值null。
正常情况下,调用代理对象的public/protected方法时,CGLIB的方法拦截器会把调用转发给原始的FilterService实例(这个实例的成员变量是通过构造器正常初始化的),所以不会有问题。但initNonSpecificFilters是private方法,情况就不一样了。
2. Private方法无法被CGLIB代理
Java的private方法对子类是不可见的,CGLIB无法为private方法生成代理逻辑。当你通过self(代理对象)调用initNonSpecificFilters时,这个调用不会触发代理的方法拦截,而是直接在代理对象本身上执行父类的private方法——而代理对象的成员变量都是未初始化的null,自然就抛出空指针异常了。
3. 为什么filterItems里的this是原始实例?
当控制器调用filterItems时,调用的是代理对象的filterItems方法(因为它是public方法,被代理了)。代理的方法拦截器会把请求转发给原始的FilterService实例,所以在filterItems内部的this指向的是成员变量都初始化好的原始对象。
针对这个问题,有几个可行的修复方案,按推荐程度排序:
方案1:将private方法改为public/protected
把initNonSpecificFilters的访问修饰符改成public或者protected,这样CGLIB就能为它生成代理逻辑了。当你通过self调用这个方法时,代理会把请求转发给原始实例,成员变量就能正常访问:
@Profileable protected List<Filter> initNonSpecificFilters(@NotNull Lang lang, @NotNull FilterInfo filterInfo) { final List<NumericFilter> allNumericNonSpecific = NumericFilter.getAllNonSpecific(lang, filterInfo); final List<EnumOptionFilter> allEnumNonSpecific = enumFilters.values().stream() .flatMap(x -> x.getAllOptions(lang, filterInfo).stream()) .collect(Collectors.toList()); // 剩余逻辑... }
方案2:使用Spring AopContext获取代理对象(替代自注入)
如果你不想修改方法的访问权限,也可以通过AopContext.currentProxy()获取当前代理对象,但需要确保方法是非private的,同时在启动类上添加@EnableAspectJAutoProxy(exposeProxy = true):
// 启动类添加注解 @SpringBootApplication @EnableAspectJAutoProxy(exposeProxy = true) public class YourApplication { /* ... */ } // 在FilterService中修改调用方式 @Profileable public ItemsViewShared filterItems(/* 参数 */) { try { this.readLock.lock(); final ItemsViewShared itemsViewResponse = new ItemsViewShared(); FilterService selfProxy = (FilterService) AopContext.currentProxy(); List<Filter> allFilters = selfProxy.initNonSpecificFilters(lang, filterInfo); // 剩余逻辑... } } // 同时修改initNonSpecificFilters为protected/public @Profileable protected List<Filter> initNonSpecificFilters(/* 参数 */) { /* ... */ }
方案3:拆分方法到独立服务类
如果initNonSpecificFilters的逻辑相对独立,可以把它提取到一个新的服务类(比如FilterInitializer)中,通过构造器注入这个服务类,然后直接调用它的方法——这样既避免了自注入的问题,也能让切面正常生效。
内容的提问来源于stack exchange,提问作者Peter Kozlovsky

