如何让Spring缓存注入到单例Bean中的懒加载Prototype Bean?
如何让Spring的懒加载代理缓存Prototype Bean实例
当将懒加载的Prototype Bean注入到Singleton Bean中时,Spring会生成代理对象,每次调用代理方法都会创建新的Prototype实例。如果Prototype Bean的创建开销较高,连续调用会造成性能浪费。现有自定义注解@CacheAnnotation结合@Lazy和@Value注入,但每次访问都会新建实例,以下是几种更简单的解决方案:
方案一:利用@Lookup方法+本地缓存
这是最直接的原生Spring方案,无需额外组件:
- 在Singleton Bean中定义
@Lookup方法,用于获取Prototype实例 - 增加本地缓存字段,确保只创建一次实例
@RestController @RequestMapping(path = "/api/cache") public class NullController { private Cache cache; // Spring会自动实现该方法,返回新的Prototype实例 @Lookup protected Cache createCache() { return null; } // 双重检查锁保证线程安全,缓存唯一实例 private Cache getCachedCache() { if (cache == null) { synchronized (this) { if (cache == null) { cache = createCache(); } } } return cache; } @GetMapping public String getCache() { getCachedCache().toString(); getCachedCache().toString(); // 后续调用复用同一个实例,只会打印一次创建日志 return ""; } }
方案二:自定义CachedLazyTargetSource替换默认实现
如果希望保留原有的注解注入方式,只需替换懒加载代理的TargetSource实现,缓存第一次创建的实例:
- 实现缓存版的LazyTargetSource:
public class CachedLazyTargetSource extends LazyDependencyTargetSource { private Object cachedTarget; private final Object lock = new Object(); public CachedLazyTargetSource(LazyDependencyTargetSource original) { // 复制原TargetSource的核心属性 setBeanFactory(original.getBeanFactory()); setTargetBeanName(original.getTargetBeanName()); setTargetClass(original.getTargetClass()); } @Override public Object getTarget() throws Exception { if (cachedTarget == null) { synchronized (lock) { if (cachedTarget == null) { cachedTarget = super.getTarget(); } } } return cachedTarget; } }
- 注册BeanPostProcessor替换代理的TargetSource:
@Component public class CachedLazyProxyProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 检查是否为Spring代理对象 if (bean instanceof Advised advised) { TargetSource targetSource = advised.getTargetSource(); // 替换默认的LazyDependencyTargetSource if (targetSource instanceof LazyDependencyTargetSource) { advised.setTargetSource(new CachedLazyTargetSource((LazyDependencyTargetSource) targetSource)); } } return bean; } }
添加该处理器后,所有通过@Lazy注入的Prototype Bean代理都会自动缓存实例,无需修改原有注解或Bean定义。
方案三:自定义“单例绑定”Scope
如果需要多个Singleton Bean各自持有独立的Prototype实例(每个Singleton对应一个专属Prototype),可以自定义Scope实现:
- 实现自定义Scope:
public class SingletonBoundPrototypeScope implements Scope { // 用ThreadLocal缓存当前创建周期的实例,确保每个Singleton Bean初始化时获取唯一实例 private final ThreadLocal<Map<String, Object>> instanceCache = ThreadLocal.withInitial(HashMap::new); @Override public Object get(String name, ObjectFactory<?> objectFactory) { return instanceCache.get().computeIfAbsent(name, key -> objectFactory.getObject()); } @Override public Object remove(String name) { return instanceCache.get().remove(name); } @Override public void registerDestructionCallback(String name, Runnable callback) { // 实例随Singleton Bean生命周期结束,无需额外销毁回调 } @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return null; } // 配合Bean销毁回调清理缓存 @PreDestroy public void clearCache() { instanceCache.remove(); } }
- 注册自定义Scope:
@Configuration public class CustomScopeConfig implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerScope("singleton-bound-prototype", new SingletonBoundPrototypeScope()); } }
- 修改Prototype Bean的Scope:
@Bean @Primary @Scope("singleton-bound-prototype") public Cache getCache() { System.out.println("Creating a new bean..."); return new Cache(); }
此方案下,每个Singleton Bean第一次获取Cache时会创建专属实例,后续复用,不同Singleton Bean的实例相互独立。
内容的提问来源于stack exchange,提问作者vasac
相关产品推荐
相关产品推荐

