You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Spring缓存注入到单例Bean中的懒加载Prototype Bean?

如何让Spring的懒加载代理缓存Prototype Bean实例

当将懒加载的Prototype Bean注入到Singleton Bean中时,Spring会生成代理对象,每次调用代理方法都会创建新的Prototype实例。如果Prototype Bean的创建开销较高,连续调用会造成性能浪费。现有自定义注解@CacheAnnotation结合@Lazy和@Value注入,但每次访问都会新建实例,以下是几种更简单的解决方案:

方案一:利用@Lookup方法+本地缓存

这是最直接的原生Spring方案,无需额外组件:

  1. 在Singleton Bean中定义@Lookup方法,用于获取Prototype实例
  2. 增加本地缓存字段,确保只创建一次实例
@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实现,缓存第一次创建的实例:

  1. 实现缓存版的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;
    }
}
  1. 注册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实现:

  1. 实现自定义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();
    }
}
  1. 注册自定义Scope:
@Configuration
public class CustomScopeConfig implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope("singleton-bound-prototype", new SingletonBoundPrototypeScope());
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 04:37:35