1. 程式人生 > >spring boot 2.1.4 hibernate二級快取 Hazelcast實現(二)

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());
	}