Spring Boot中Service类使用@Scope(proxyMode=TARGET_CLASS)是否合理?
问题本质说明
你遇到的缓存失效属于Spring AOP典型的自调用问题:Spring的@Cacheable缓存能力基于动态代理实现,外部从Controller层注入Service时拿到的是Spring生成的代理对象,代理会拦截带切面注解的方法,先执行缓存校验/写入逻辑,再调用原始对象的对应方法。但你在test1()中直接写test2(10)等价于this.test2(10),这里的this指向原始的目标对象,完全绕过了代理层的切面逻辑,因此缓存不会生效。
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)生效原理 这个配置的原生设计目的是解决不同生命周期Bean的注入兼容问题,它的生效逻辑如下:
- 配置该属性后,Spring不会直接将原始的Service实例注入到其他依赖位置,而是额外生成一个基于CGLIB的代理子类作为容器对外暴露的Bean对象。
- 这个代理子类会重写类中所有非私有、非final的方法,拦截所有方法调用:和普通单例Bean的AOP代理仅拦截外部传入的方法调用不同,该作用域代理会将类内部的方法调用也纳入拦截范围,调用链路不会直接走原始对象的
this引用,因此test1内部调用test2时也会触发缓存切面逻辑,表现为缓存正常生效。 - 每次调用代理方法时,代理都会先从当前Bean对应的作用域(默认是单例作用域)中获取真实的目标实例,再转发方法调用。
普通单例Service上使用该配置的合理性判断
不推荐用这种方式解决缓存自调用问题,属于注解误用,存在明确的弊端:
- 不必要的性能开销:作用域代理每次方法调用都要执行作用域实例查找、路由转发的额外逻辑,比普通AOP代理的调用链路更长,高并发场景下会产生无意义的性能损耗。
- 兼容风险:CGLIB通过生成子类实现代理,无法重写
final方法、私有方法、静态方法,如果Service中存在这类方法,会出现逻辑失效、调用报错的问题;如果类存在带参数的构造函数,也可能出现代理初始化异常。 - 维护成本高:该注解的设计初衷不是解决AOP自调用问题,后续维护代码的开发者看到这个注解会默认当前Bean是request/session这类短作用域Bean,提升代码理解成本。
自调用缓存失效的推荐解决方案
- 方案1:拆分方法到独立类
将需要走缓存逻辑的test2方法抽离到单独的Service类中,在当前ServiceCache中注入该Service后调用,从根源上避免自调用,这是生产环境最推荐的做法,职责也更清晰。 - 方案2:注入自身代理对象
在类中注入自身的代理实例,通过代理实例调用内部方法,注意加@Lazy避免循环注入报错:@Service public class ServiceCache { Logger logger = LoggerFactory.getLogger(ServiceCache.class); @Lazy @Autowired private ServiceCache selfProxy; public void test1(){ logger.info("test1"); selfProxy.test2(10); } @Cacheable(value="cache", key="#id") public void test2(Integer id){ logger.info("test2"); } } - 方案3:手动从容器获取代理对象
注入ApplicationContext,调用时手动从容器中获取当前类的代理Bean再调用方法,适合少量场景使用。
内容的提问来源于stack exchange,提问作者DK93
相关产品推荐
相关产品推荐

