spring boot 2.1.4 hibernate二級快取 Hazelcast實現(二)
在(一)中我們配置好了 hibernate二級快取 Hazelcast實現,但是當我們使用spring cache相關注解(@CacheConfig,@Cacheable,@CachePut,@CacheEvict ,@Caching )的時候,並不能自動建立快取配置,需要在hazelcast.xml檔案中配置cache結點,如果使用的比較多的話就比較麻煩,而且大部分情況下,配置項都是一樣的
檢視org.springframework.cache.jcache.JCacheCacheManager原始碼實現,發現針對沒有發快取,沒有自動生成
@Override protected Cache getMissingCache(String name) { CacheManager cacheManager = getCacheManager(); Assert.state(cacheManager != null, "No CacheManager set"); // Check the JCache cache again (in case the cache was added at runtime) javax.cache.Cache<Object, Object> jcache = cacheManager.getCache(name); if (jcache != null) { return new JCacheCache(jcache, isAllowNullValues()); } return null; }
找到自動配置類原始碼org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
@Bean @ConditionalOnMissingBean public CacheManager jCacheCacheManager() throws IOException { CacheManager jCacheCacheManager = createCacheManager(); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (!CollectionUtils.isEmpty(cacheNames)) { for (String cacheName : cacheNames) { jCacheCacheManager.createCache(cacheName, getDefaultCacheConfiguration()); } } customize(jCacheCacheManager); return jCacheCacheManager; }
對於spring.cache.cache-names中配置的會自動生成,但是感覺還是有點多餘
在這一篇中,針對spring cache相關注解,對於沒有配置的cache,也按預設配置項生成
思路是掃描org.springframework.cache.annotation.CacheConfig註解,將cache自動新增到spring.cache.cache-names配置項中,這樣就會自動建立了
1、建立包掃描路徑註解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import({CacheConfigScannerRegistrar.class}) public @interface CacheNameScan { /** * `@GrpcService` 所註解的包掃描路徑 */ String[] packages() default {}; }
2、進行掃描新增到配置項中
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CacheConfigScannerRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
scanner.setResourceLoader(this.resourceLoader);
Set<String> cacheNameSet = new HashSet<>();
scanner.addIncludeFilter(new AnnotationTypeFilter(CacheConfig.class) {
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
boolean isMatch = super.matchSelf(metadataReader);
if (isMatch) {
String[] cacheNames = (String[]) annotationMetadata
.getAnnotationAttributes("org.springframework.cache.annotation.CacheConfig")
.get("cacheNames");
if (cacheNames != null) {
cacheNameSet.addAll(Arrays.asList(cacheNames));
}
}
return isMatch;
}
});
Set<BeanDefinition> beanDefinitions = scanPackages(importingClassMetadata, scanner);
beanDefinitions.forEach(beanDefinition -> {
log.info("配置CacheConfig快取的class:{}", beanDefinition.getBeanClassName());
});
if (environment instanceof StandardEnvironment) {
StandardEnvironment standardEnvironment = (StandardEnvironment) environment;
cacheNameSet.addAll(Arrays
.asList(StringUtils.split(standardEnvironment.getProperty("spring.cache.cache-names", ""), ",")));
String configName = StringUtils.join(cacheNameSet, ",");
Map<String, Object> map = new HashMap<>();
map.put("spring.cache.cache-names", configName);
MapPropertySource propertySource = new MapPropertySource("cache.yml", map);
standardEnvironment.getPropertySources().addFirst(propertySource);
}
}
/**
* 包掃描
*/
private static Set<BeanDefinition> scanPackages(AnnotationMetadata importingClassMetadata,
ClassPathBeanDefinitionScanner scanner) {
List<String> packages = new ArrayList<>();
Map<String, Object> annotationAttributes = importingClassMetadata
.getAnnotationAttributes(CacheNameScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("packages");
if (basePackages.length > 0) {
packages.addAll(Arrays.asList(basePackages));
}
log.info("cache name 包掃描:{}", packages);
}
Set<BeanDefinition> beanDefinitions = new HashSet<>();
if (CollectionUtils.isEmpty(packages)) {
return beanDefinitions;
}
packages.forEach(pack -> beanDefinitions.addAll(scanner.findCandidateComponents(pack)));
return beanDefinitions;
}
}
這樣就自動新增進去了,而不用作其他多餘配置,使用的時候,使用@CacheConfig(cacheNames =“test”)和@Cacheable(key = "#groupName")配合
另外一種更直接的方式,直接修改SimpleCacheResolver的實現,並配置到org.springframework.cache.interceptor.CacheAspectSupport中
@Bean
public HibernatePropertiesCustomizer hibernateSecondLevelCacheCustomizer(JCacheCacheManager jCacheCacheManager,CacheAspectSupport cacheAspectSupport) {
cacheAspectSupport.setCacheResolver(new SimpleCacheResolver(jCacheCacheManager) {
@Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
Collection<String> cacheNames = getCacheNames(context);
if (cacheNames == null) {
return Collections.emptyList();
}
Collection<Cache> result = new ArrayList<>(cacheNames.size());
for (String cacheName : cacheNames) {
Cache cache = getCacheManager().getCache(cacheName);
if (cache == null) {
log.warn("未提前配置快取:{},推薦使用org.springframework.cache.annotation.CacheConfig註解指定快取",
cacheName);
jCacheCacheManager.getCacheManager().createCache(cacheName, defaultCacheConfiguration());
cache = getCacheManager().getCache(cacheName);
}
result.add(cache);
}
return result;
}
});
return (properties) -> properties.put(ConfigSettings.CACHE_MANAGER, jCacheCacheManager.getCacheManager());
}