【问题标题】:Spring boot cache not working in @PostConstructSpring Boot 缓存在 @PostConstruct 中不起作用
【发布时间】:2018-04-11 16:32:17
【问题描述】:

我正在构建一个“类缓存”,其中包含我想稍后调用的类。

主要目标是我不想每次需要类实例时都扫描上下文。

# Model / Repository classes

@Getter
@RequiredArgsConstructor
public class Block implements Serializable {
    private final String className;
    private final Set<String> classCandidates = new HashSet<>();
    public boolean addCandidate(final String classCandidate) {
        return this.classCandidates.add(classCandidate);
    }
}

@Slf4j
@Component
@CacheConfig(cacheNames = ConstantsCache.CACHE_BLOCK)
public class BlockRepository {

    @Cacheable(key = "#className")
    public Block findByInputClass(final String className) {
        log.info("---> Loading classes for class '{}'", className);
        val block = new Block(className);
        findCandidates(block);
        return block;
    }
}

首先要评估缓存,我将缓存方法@Autowired 放在@RestController 中,效果很好。当我调用 rest 方法时,缓存会被填充。

@RestController
public class Controller {

    @Autowired
    BlockRepository blockRepository;

    @RequestMapping("/findByInputClass")
    public Block findByInputClass(@RequestParam("className") final String className) {
        return blockRepository.findByInputClass(className);
    }
}

之后,我将 @Autowired 对象移动到了一个 @Service 中,创建了一个自行填充缓存的方法。但这不起作用。调用 @PostConstructor 方法时不会填充缓存。

@Slf4j
@Component
public class BlockCacheService {

    @Autowired
    BlockRepository blockRepository;

    @PostConstruct
    private void postConstruct() {
        log.info("*** {} PostConstruct called.", this.getClass().getTypeName());

        val block = blockRepository.findByInputClass(ConstantsGenerics.BLOCK_PARENT_CLASS);

        final Set<String> inputClasses = getInputFromCandidates(block.getClassCandidates());
        appendClassesToCache(inputClasses);
    }

    private void appendClassesToCache(final Set<String> inputClasses) {
        for (val inputClass : inputClasses) {
            blockRepository.findByInputClass(inputClass);
        }
    }
}

如何使用必须从应用程序开始的服务或组件正确填充缓存。

提前致谢。

编辑:

我在这里找到了可能的解决方案:https://*.com/a/28311225/1703546

我已经更改了 @Service 代码以手动放置缓存,而不是使用 @Cacheable 魔术抽象。

现在的课是这样的。

@Slf4j
@Component
public class BlockCacheService {

    @Autowired
    CacheManager cacheManager;

    @Autowired
    BlockRepository blockRepository;

    @PostConstruct
    private void postConstruct() {
        log.info("*** {} PostConstruct called.", this.getClass().getTypeName());

        val block = blockRepository.findByInputClass(ConstantsGenerics.BLOCK_PARENT_CLASS);

        final Set<String> inputClasses = getInputFromCandidates(block.getClassCandidates());
        appendClassesToCache(inputClasses);
    }

    private void appendClassesToCache(final Set<String> inputClasses) {
        for (val inputClass : inputClasses) {
            val block = blockRepository.findByInputClass(inputClass);
            cacheManager.getCache(ConstantsCache.CACHE_BLOCK).put(block.getClassName(), block);
        }
    }
}

现在缓存已正确填充,但问题是,这是最好的解决方案吗?

谢谢。

【问题讨论】:

    标签: spring-boot concurrenthashmap spring-cache spring-boot-starter


    【解决方案1】:

    您不能在@PostConstruct 中使用方面,因为它可能尚未创建(以及is documented by the way)。

    实现这项工作的一种可能方法是实现SmartInitializingBean,因为它会在所有单例已完全初始化(包括它们的方面)时提供回调。在您的原始服务上更改它应该可以工作。

    话虽如此,您的这段代码对启动时间有影响。你为什么不让你的缓存被懒惰地填充呢?

    【讨论】: