在Spring项目当中,我们使用缓存时,往往会直接使用redis,memcache,Guava。你曾经使用过Spring CacheManager吗?
Spring CacheManager实际上是Spring框架对底层缓存接口的一层抽象,也就是说,用户在使用Spring CacheManager时,可以不用关心底层到底使用的是什么缓存,即使该缓存从redis替换成了memcache,缓存接口并不需要作任何改动。
不仅仅如此,笔者在项目当中,曾经再一次对该CacheManager作了封装,实现了网易云NCR缓存集群在集群故障的情况下的动态切换,这里我会写专门的一篇文章来介绍怎么来实现该机制。
所以,如果你的项目有改动底层缓存的可能,或许Spring CacheManager就是你的一个选择。
当然,使用过的人也清楚,Spring CacheManager并没有封装所有的底层接口,比如redis的pipeline,lpush,lpop等。所以,笔者在实际项目中的使用状态是:混合使用Spring CacheManager和jedis。
下面进入正文。
对Spring CacheManager的进一步封装,在Spring容器启动过程中,扫描被@AutowireCache标注的字段或者方法,从而将Spring CacheManager生成的缓存bean注入到service bean中:
package com.wisexplore.base.spec.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
自定义一个标签,用于在service bean中注入缓存bean
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Documented
public @interface AutowireCache {
boolean required() default true; //功能同IOC注入时的required
String value(); //单个缓存名称,针对特定业务可以定义不同的名称
}
package com.wisexplore.base.core.beans;
import com.wisexplore.base.spec.annotation.AutowireCache;
import com.wisexplore.base.spec.api.ExpressionResolver;
import com.wisexplore.base.spec.support.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
**封装的核心逻辑
**扫描所有bean,被@AutowireCache标注的属性或者方法,会将Spring CacheManager生成的bean注入到service bean中
**/
@Component
public class CacheAutowireBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {
private static final Logger logger = LoggerFactory.getLogger(CacheAutowireBeanPostProcessor.class);
private ConfigurableListableBeanFactory beanFactory;
//InjectionMetadata按字面理解就知道是IOC注入时的元数据,
//这里可能存在多个bean同时注入时的并发可能,所以用的是并发集合ConcurrentHashMap
private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
//要求必须是ConfigurableListableBeanFactory
if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
throw new IllegalArgumentException(
"CacheAutowireBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
}
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
@Override
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException
{
//通过Spring标签动态注入缓存bean的逻辑定义在下面的findAutowiringMetadata()方法中
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of AutowireCache dependencies failed", ex);
}
return pvs;
}
//获取@AutowireCache修饰的标签属性
private AnnotationAttributes findAnnotation(AccessibleObject ao) {
return ao.getAnnotations().length > 0
? AnnotatedElementUtils.getMergedAnnotationAttributes(ao, AutowireCache.class)
: null;
}
//该属性是否是required
private boolean determineRequiredStatus(AnnotationAttributes ann) {
String name = "required";
return !ann.containsKey(name) || ann.getBoolean(name);
}
private String determineCacheName(AnnotationAttributes ann) {
String name = "value";
if (!ann.containsKey(name)) {
return null;
}
ExpressionResolver resolver = null;
try {
//缓存名称支持spel表达式
resolver = beanFactory.getBean(ExpressionResolver.class);
} catch (Exception ex) {
ExceptionUtils.swallow(ex, false);
}
return resolver == null ? ann.getString(name) : resolver.resolveAsString(ann.getString(name));
}
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
String cacheKey = StringUtils.hasLength(beanName) ? beanName : clazz.getName();
InjectionMetadata metadata = injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (injectionMetadataCache) { //同步
metadata = injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
try {
metadata = buildAutowiringMetadata(clazz);
injectionMetadataCache.put(cacheKey, metadata);
}
catch (NoClassDefFoundError err) {
throw new IllegalStateException("Failed to introspect bean class [" + clazz.getName() +
"] for AutowireCache metadata: could not find class that it depends on", err);
}
}
}
}
return metadata;
}
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();
Class<?> targetClass = clazz;
do {
final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> {
AnnotationAttributes ann = findAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
logger.warn("@AutowireCache is not supported on static fields: " + field);
return;
}
if (field.getType() != Cache.class) {
logger.warn("@AutowireCache should be used on Cache type");
return;
}
currElements.add(new AutowireCacheFieldElement(field, determineRequiredStatus(ann), determineCacheName(ann)));
}
});
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
AnnotationAttributes ann = findAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
logger.warn("@AutowireCache is not supported on static methods: " + method);
return;
}
if (method.getParameterTypes().length != 1 || method.getParameterTypes()[0] != Cache.class) {
logger.warn("@AutowireCache should only be used on methods with parameters: " + method);
return;
}
boolean required = determineRequiredStatus(ann);
String cacheName = determineCacheName(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowireCacheMethodElement(method, required, cacheName, pd));
}
});
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
//这里的elements实际上就是Spring CacheManager生成的cache bean
return new InjectionMetadata(clazz, elements);
}
//AutowireCacheFieldElement按字面理解,即是用@AutowireCache修饰的bean的属性
private class AutowireCacheFieldElement extends InjectionMetadata.InjectedElement {
private final boolean required;
private final String cacheName;
public AutowireCacheFieldElement(Field field, boolean required, String cacheName) {
super(field, null);
this.required = required;
this.cacheName = cacheName;
}
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Object value = null;
try {
value = beanFactory.getBean(CacheManager.class).getCache(cacheName);
} catch (Exception ex) {
ExceptionUtils.swallow(ex, false);
}
if (value != null) {
Field field = (Field) member;
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
} else if (required) {
throw new IllegalStateException("@AutowireCache injection failed");
}
}
}
//AutowireCacheMethodElement按字面理解,即是用@AutowireCache修饰的bean的方法
private class AutowireCacheMethodElement extends InjectionMetadata.InjectedElement {
private final boolean required;
private final String cacheName;
public AutowireCacheMethodElement(Method method, boolean required, String cacheName, PropertyDescriptor pd) {
super(method, pd);
this.required = required;
this.cacheName = cacheName;
}
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
if (checkPropertySkipping(pvs)) {
return;
}
Object value = null;
try {
//这里是Spring CacheManager生成的cache bean
value = beanFactory.getBean(CacheManager.class).getCache(cacheName);
} catch (Exception ex) {
ExceptionUtils.swallow(ex, false);
}
if (value != null) {
Method method = (Method) member;
try {
ReflectionUtils.makeAccessible(method);
method.invoke(bean, value);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
} else if (required) {
throw new IllegalStateException("@AutowireCache injection failed");
}
}
}
}
第二步,配置Spring configuration:
package com.wisexplore.redis.main.config;
import com.wisexplore.base.spec.annotation.CacheManagerBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class WisexploreRedisMainConfig {
//jedis pool配置
@Bean("mainJedisPoolConfig")
JedisPoolConfig mainJedisPoolConfig(
@Value("${redis.main.jedisPoolConfig.blockWhenExhausted}") boolean blockWhenExhausted,
@Value("${redis.main.jedisPoolConfig.lifo}") boolean lifo,
@Value("${redis.main.jedisPoolConfig.fairness}") boolean fairness,
@Value("${redis.main.jedisPoolConfig.jmxEnabled}") boolean jmxEnabled,
@Value("${redis.main.jedisPoolConfig.maxTotal}") int maxTotal,
@Value("${redis.main.jedisPoolConfig.maxIdle}") int maxIdle,
@Value("${redis.main.jedisPoolConfig.minIdle}") int minIdle,
@Value("${redis.main.jedisPoolConfig.maxWaitMillis}") long maxWaitMillis,
@Value("${redis.main.jedisPoolConfig.minEvictableIdleTimeMillis}") long minEvictableIdleTimeMillis,
@Value("${redis.main.jedisPoolConfig.numTestsPerEvictionRun}") int numTestsPerEvictionRun,
@Value("${redis.main.jedisPoolConfig.softMinEvictableIdleTimeMillis}") long softMinEvictableIdleTimeMillis,
@Value("${redis.main.jedisPoolConfig.timeBetweenEvictionRunsMillis}") long timeBetweenEvictionRunsMillis,
@Value("${redis.main.jedisPoolConfig.testOnBorrow}") boolean testOnBorrow,
@Value("${redis.main.jedisPoolConfig.testOnCreate}") boolean testOnCreate,
@Value("${redis.main.jedisPoolConfig.testOnReturn}") boolean testOnReturn,
@Value("${redis.main.jedisPoolConfig.testWhileIdle}") boolean testWhileIdle) {
JedisPoolConfig config = new JedisPoolConfig();
config.setBlockWhenExhausted(blockWhenExhausted);
config.setLifo(lifo);
config.setFairness(fairness);
config.setJmxEnabled(jmxEnabled);
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setMaxWaitMillis(maxWaitMillis);
config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
config.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnCreate(testOnCreate);
config.setTestOnReturn(testOnReturn);
config.setTestWhileIdle(testWhileIdle);
return config;
}
//配置jedis连接工厂
@Bean("mainJedisConnectionFactory")
JedisConnectionFactory mainJedisConnectionFactory(
@Value("${redis.main.jedisConnectionFactory.usePool}") boolean usePool,
@Value("${redis.main.jedisConnectionFactory.hostname}") String hostname,
@Value("${redis.main.jedisConnectionFactory.port}") int port,
@Value("${redis.main.jedisConnectionFactory.database}") int database,
@Value("${redis.main.jedisConnectionFactory.password}") String password,
@Value("${redis.main.jedisConnectionFactory.timeout}") int timeout,
@Value("${redis.main.jedisConnectionFactory.useSsl}") boolean useSsl,
@Value("${redis.main.jedisConnectionFactory.useSentinel}") boolean useSentinel,
@Value("${redis.main.jedisConnectionFactory.masterName}") String masterName,
@Value("${redis.main.jedisConnectionFactory.sentinelHostPorts}") String sentinelHostPorts,
@Qualifier("mainJedisPoolConfig") JedisPoolConfig config) {
JedisConnectionFactory factory;
if (!useSentinel) {
factory = new JedisConnectionFactory();
factory.setHostName(hostname);
factory.setPort(port);
factory.setDatabase(database);
} else {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration(masterName,
StringUtils.commaDelimitedListToSet(sentinelHostPorts));
factory = new JedisConnectionFactory(sentinelConfig);
}
factory.setUsePool(usePool);
factory.setPassword(password);
factory.setTimeout(timeout);
factory.setPoolConfig(config);
factory.setUseSsl(useSsl);
return factory;
}
//配置redisTemplate
@Bean("mainRedisTemplate")
RedisTemplate<Object, Object> mainRedisTemplate(
@Qualifier("mainJedisConnectionFactory") JedisConnectionFactory connectionFactory,
@Value("${redis.main.redisTemplate.enableTransactionSupport}") boolean enableTransactionSupport) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setEnableTransactionSupport(enableTransactionSupport);
RedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setValueSerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
template.setHashValueSerializer(stringSerializer);
return template;
}
//配置基于redis缓存的CacheManager
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
CacheManager mainRedisCacheManager(
@Qualifier("mainJedisConnectionFactory") JedisConnectionFactory connectionFactory,
@Value("${redis.main.redisCacheManager.defaultExpiration}") long defaultExpiration,
@Value("${redis.main.redisCacheManager.transactionAware}") boolean transactionAware,
@Value("${redis.main.redisCacheManager.loadRemoteCachesOnStartup}") boolean loadRemoteCachesOnStartup,
@Value("${redis.main.redisCacheManager.usePrefix}") boolean usePrefix,
@Value("${redis.main.redisCacheManager.useDefaultCache}") boolean useDefaultCache,
@Value("${redis.main.redisCacheManager.caches}") String caches) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(connectionFactory);
if (StringUtils.hasText(caches)) {
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
RedisCacheConfiguration redisCacheConfiguration;
for (String cache : caches.split("[ ,;\n]")) {
if (!StringUtils.hasText(cache)) {
continue;
}
int split = cache.indexOf(":");
//配置缓存名称和缓存过期时间
if (split < 0) {
redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(0l));
cacheConfigurations.put(cache, redisCacheConfiguration);
} else {
redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(Long.valueOf(cache.substring(split + 1))));
cacheConfigurations.put(cache.substring(0, split), redisCacheConfiguration);
}
}
builder.withInitialCacheConfigurations(cacheConfigurations);
}
return builder.build();
}
}
第三步,缓存配置格式如下:
---
redis:
main:
jedisPoolConfig:
blockWhenExhausted: false
lifo: true
fairness: false
jmxEnabled: true
maxTotal: 50
maxWaitMillis: -1
minEvictableIdleTimeMillis: 120000
minIdle: 0
maxIdle: 50
numTestsPerEvictionRun: -1
softMinEvictableIdleTimeMillis: -1
timeBetweenEvictionRunsMillis: 60000
testOnBorrow: false
testOnCreate: false
testOnReturn: false
testWhileIdle: true
jedisConnectionFactory:
usePool: true
hostname: 127.0.0.1
port: 6379
database: 0
password: xxx
timeout: 15000
useSsl: false
useSentinel: false
masterName: mymaster
sentinelHostPorts: 127.0.0.1:26371,127.0.0.1:26372
redisCacheManager:
defaultExpiration: 0
transactionAware: false
loadRemoteCachesOnStartup: false
usePrefix: true
useDefaultCache: false
caches: >
TOKEN:1800,
NONCE:180,
PASSPORT_KEY:3600,
PASSPORT_CAPTCHA:1200,
PASSPORT_SHORT_TERM:180,
VISIT_CACHE:1200,
PASSPORT_LOCK:1800,
PASSPORT_CODE:3600,
USER_FLOW:3,
COOKIE_TOKEN_FLOW:180,
SMS:1800,
PWD_ERROR:1800,
DEALER_MAPPED:0,
ALIPAY_CACHE:0,
RECOMMEND:1800,
SEARCH_CACHE:600,
SEARCH_FLOW_CACHE:60,
SEARCH_LOCK:1800,
NEURON_AUTO_GEN_CACHE:0,
OAUTH_STATE:1200,
redisTemplate:
enableTransactionSupport: false
最后,您可以在某个service bean中用以下的方式注入并使用它:
@AutowireCache(CacheConstants.TOKEN_CACHE)
private Cache tokenCache;
文章已经收录在以下的知识体系中:分享给您:Spring的深度使用 ,欢迎有意向的同学跟我一起共建该知识体系。在手机浏览器打开该链接,点击上方图片即可下载学识App。