构造函数初始化CacheBuilder无法读取Spring注解值的问题及替代方案咨询
这个问题我之前也碰到过!核心原因是Spring的@Value注入时机晚于构造函数执行——当你在构造函数里初始化attemptsCache时,recaptchaAttemptExpire还没被注入,还是int类型的默认值0,所以你的expireAfterWrite才会一直是0。
下面给你两种靠谱的解决方式:
解决方案1:用@PostConstruct延迟初始化缓存
把缓存的初始化逻辑移到一个标注了@PostConstruct的方法里,这个方法会在Spring完成所有依赖注入(包括@Value的属性)之后执行。改动很小,适合快速修复:
public class RecaptchaService { private final Logger log = LoggerFactory.getLogger(this.getClass()); private final RestTemplate restTemplate; @Value("${recaptcha.url:}") private String recaptchaUrl; @Value("${recaptcha.secret:#{null}}") private String recaptchaSecret; @Value("${recaptcha.fail.silent:false}") private boolean recaptchaFailSilent; @Value("${recaptcha.failAttempts.expire:14400}") private int recaptchaAttemptExpire; @Value("${recaptcha.failAttempts.max:4}") private int maxAttempts; private LoadingCache<String, Integer> attemptsCache; private int attempts; public RecaptchaService(final RestTemplate restTemplate) { this.restTemplate = restTemplate; // 构造函数里不再初始化缓存 } @PostConstruct public void initCache() { attemptsCache = CacheBuilder.newBuilder() .expireAfterWrite(recaptchaAttemptExpire, TimeUnit.SECONDS) .build(new CacheLoader<String, Integer>() { @Override public Integer load(final String key) { return 0; } }); } }
解决方案2:构造函数注入所有依赖(推荐)
既然你已经用了构造函数注入RestTemplate,不如把所有需要的配置参数也通过构造函数注入,这样能确保在初始化缓存时拿到正确的值,同时也更符合Spring依赖注入的最佳实践(便于单元测试,依赖关系更清晰):
public class RecaptchaService { private final Logger log = LoggerFactory.getLogger(this.getClass()); private final RestTemplate restTemplate; private final String recaptchaUrl; private final String recaptchaSecret; private final boolean recaptchaFailSilent; private final int recaptchaAttemptExpire; private final int maxAttempts; private LoadingCache<String, Integer> attemptsCache; private int attempts; // 构造函数注入所有参数,包括配置值 public RecaptchaService(final RestTemplate restTemplate, @Value("${recaptcha.url:}") String recaptchaUrl, @Value("${recaptcha.secret:#{null}}") String recaptchaSecret, @Value("${recaptcha.fail.silent:false}") boolean recaptchaFailSilent, @Value("${recaptcha.failAttempts.expire:14400}") int recaptchaAttemptExpire, @Value("${recaptcha.failAttempts.max:4}") int maxAttempts) { this.restTemplate = restTemplate; this.recaptchaUrl = recaptchaUrl; this.recaptchaSecret = recaptchaSecret; this.recaptchaFailSilent = recaptchaFailSilent; this.recaptchaAttemptExpire = recaptchaAttemptExpire; this.maxAttempts = maxAttempts; // 现在这里的recaptchaAttemptExpire已经是正确的配置值了 attemptsCache = CacheBuilder.newBuilder() .expireAfterWrite(recaptchaAttemptExpire, TimeUnit.SECONDS) .build(new CacheLoader<String, Integer>() { @Override public Integer load(final String key) { return 0; } }); } }
这种方式的优势很明显:
- 所有依赖都在构造函数中明确声明,代码可读性和可测试性更好
- 彻底避免了@Value注入时机的问题,初始化逻辑更安全
- 符合Spring官方推荐的依赖注入方式(尤其是Spring Boot项目)
内容的提问来源于stack exchange,提问作者Kuba
相关产品推荐
相关产品推荐

