原理参考ImportBeanDefinitionRegistrar+SPI简化Spring开发
本文描述通过注解方式在spring中自动配置RedisTemplate,并支持启用注解方式的redis cache
首先约定redis的配置文件如下:
redis:
host: 127.0.0.1
port: 6379
password: passwd
db: 0
timeout: 5000
pool:
max_active: 60
max_idle: 30
max_wait: 30
test_on_borrow: true
test_on_return: true
其次我们定义集成Redis的注解EnableRedisIntegration
/**
* EnableRedisIntegration redis集成配置,所需redis配置参考RedisIntegrationConfiguration
* 是否开启Redis缓存
* 是否开启RedisMessage
*
* @version 1.0
* Date: 2020-03-20
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@EnableAutoRegistrar
@Import(RedisIntegrationConfiguration.class)
public @interface EnableRedisIntegration {
/**
* 是否启用RedisCache
*/
boolean enableRedisCache() default true;
/**
* RedisCache Key前缀,仅在启用RedisCache时生效
*/
String redisCacheKeyPrefix() default "RE:CH";
/**
* RedisCache 缓存默认过期时间,仅在启用RedisCache时生效
*/
long defaultRedisCacheRxpire() default 60;
/**
* 本地缓存CacheManager的beanName,仅在启用RedisCache时生效
*/
String localCacheManager() default "";
/**
* 是否启用RedisMessageListener
*/
boolean enableRedisMessageListener() default false;
}
集成Redis注入相关bean主要在RedisIntegrationConfiguration中:
@Configuration
public class RedisIntegrationConfiguration implements EnvironmentAware {
@Setter
private Environment environment;
@Lazy
@Bean("defaultJedisPoolConfig")
public JedisPoolConfig jedisPoolConfig() {
final JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(environment.getProperty("redis.pool.max_active", Integer.class));
config.setMaxIdle(environment.getProperty("redis.pool.max_idle", Integer.class));
config.setMaxWaitMillis(environment.getProperty("redis.pool.max_wait", Integer.class));
config.setTestOnBorrow(environment.getProperty("redis.pool.test_on_borrow", Boolean.class));
config.setTestOnReturn(environment.getProperty("redis.pool.test_on_return", Boolean.class));
return config;
}
@Lazy
@Bean("defaultRedisStandaloneConfiguration")
public RedisStandaloneConfiguration redisStandaloneConfiguration() {
final RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(environment.getProperty("redis.host"));
configuration.setPort(environment.getProperty("redis.port", Integer.class));
configuration.setDatabase(environment.getProperty("redis.db", Integer.class));
configuration.setPassword(RedisPassword.of(environment.getProperty("redis.password")));
return configuration;
}
@Lazy
@Bean("defaultJedisConnectionFactory")
public JedisConnectionFactory jedisConnectionFactory() {
JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration
.builder()
.usePooling()
.poolConfig(jedisPoolConfig())
.and()
.connectTimeout(Duration.ofMillis(environment.getProperty("redis.timeout", Long.class)))
.readTimeout(Duration.ofMillis(environment.getProperty("redis.timeout", Long.class)))
.build();
return new JedisConnectionFactory(redisStandaloneConfiguration(), jedisClientConfiguration);
}
@Lazy
@Bean("stringRedisTemplate")
public RedisTemplate stringRedisTemplate() {
return new StringRedisTemplate(jedisConnectionFactory());
}
@Lazy
@Bean("defaultJedisPool")
public JedisPool jedisPool() {
return new JedisPool(jedisPoolConfig(),
environment.getProperty("redis.host"),
environment.getProperty("redis.port", Integer.class),
environment.getProperty("redis.timeout", Integer.class),
environment.getProperty("redis.password"));
}
@Lazy
@Bean("defaultRedisBean")
public RedisBean redisBean() {
final RedisBean redisBean = new RedisBean();
redisBean.setPool(jedisPool());
redisBean.setDb(environment.getProperty("redis.db", Integer.class));
return redisBean;
}
}
对是否启用redis缓存的处理以及是否开启redis message的处理是通过Handler实现:
@Slf4j
public class EnableRedisIntegrationHandler implements ConfigurationRegisterHandler {
@Override
public void registerBeanDefinitions(RegisterBeanDefinitionContext context) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(context.getImportingClassMetadata()
.getAnnotationAttributes(EnableRedisIntegration.class.getName()));
if (attributes == null || attributes.isEmpty()) {
return;
}
handleRedisCache(attributes, context);
handleRedisMessageListener(attributes, context);
}
@Override
public int getOrder() {
return 0;
}
private void handleRedisCache(AnnotationAttributes attributes, RegisterBeanDefinitionContext context) {
if (!attributes.getBoolean("enableRedisCache")) {
return;
}
// 注册CacheConfig
BeanDefinitionBuilder cacheConfigDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(CacheConfig.class);
cacheConfigDefinitionBuilder.addPropertyValue("redisTemplateName", "stringRedisTemplate");
cacheConfigDefinitionBuilder.addPropertyValue("expire",
attributes.getNumber("defaultRedisCacheRxpire").longValue());
cacheConfigDefinitionBuilder.addPropertyValue("name",
attributes.getString("redisCacheKeyPrefix"));
context.getRegistry().registerBeanDefinition("defaultRedisCacheConfig",
cacheConfigDefinitionBuilder.getBeanDefinition());
// 注册RedisCacheAspect
BeanDefinitionBuilder annotationRedisCacheAspectBuilder =
BeanDefinitionBuilder.genericBeanDefinition(RedisCacheAspect.class);
annotationRedisCacheAspectBuilder.addPropertyReference("cacheConfig", "defaultRedisCacheConfig");
if (StringUtils.isNotBlank(attributes.getString("localCacheManager"))) {
annotationRedisCacheAspectBuilder.addPropertyReference("localCacheManager",
attributes.getString("localCacheManager"));
} else {
try {
final CacheManager cacheManager = context.getBeanFactory().getBean(CacheManager.class);
annotationRedisCacheAspectBuilder.addPropertyValue("localCacheManager", cacheManager);
} catch (BeansException e) {
log.warn("can not wire CacheManager for localCache");
}
}
context.getRegistry().registerBeanDefinition("annotationRedisCacheAspect",
annotationRedisCacheAspectBuilder.getBeanDefinition());
// 注册AOP
String pointcutTemplate = "@within(%s)\n"
+ " || @annotation(%s)\n"
+ " || @within(%s)\n"
+ " || @annotation(%s)\n"
+ " || @within(%s)\n"
+ " || @annotation(%s)";
final AspectJParams params = new AspectJParams()
.setProxyTargetClass(true)
.setAopRefBean("annotationRedisCacheAspect")
.setAopOrder(1)
.setAopAdvice("around")
.setAopAdviceMethod("doCacheAspect")
.setAopPointcut(String.format(pointcutTemplate,
RedisCacheable.class.getName(), RedisCacheable.class.getName(),
RedisCachePut.class.getName(), RedisCachePut.class.getName(),
RedisCacheEvict.class.getName(), RedisCacheEvict.class.getName()));
try {
AopBeanDefinitionRegistry.loadBeanDefinitions(context.getRegistry(), params);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void handleRedisMessageListener(AnnotationAttributes attributes, RegisterBeanDefinitionContext context) {
if (!attributes.getBoolean("enableRedisMessageListener")) {
return;
}
// 注册RedisMessageListenerContainer
BeanDefinitionBuilder containerDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(RedisMessageListenerContainer.class);
containerDefinitionBuilder.addPropertyReference("connectionFactory",
"defaultJedisConnectionFactory");
final int availableProcessors = Runtime.getRuntime().availableProcessors();
containerDefinitionBuilder.addPropertyValue("taskExecutor",
MDCThreadPoolTaskExecutor.builder()
.corePoolSize(availableProcessors * 2)
.maxPoolSize(availableProcessors * 2)
.keepAliveSeconds(300)
.namedThreadFactory("RedisMsgTask")
.build());
context.getRegistry().registerBeanDefinition("redisMessageListenerContainer",
containerDefinitionBuilder.getBeanDefinition());
// 注册RedisMessageListenerAutoConfig
BeanDefinitionBuilder autoConfigDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(RedisMessageListenerAutoConfig.class);
context.getRegistry().registerBeanDefinition("redisMessageListenerAutoConfig",
autoConfigDefinitionBuilder.getBeanDefinition());
}
}
其中对是否开启Redis Cache的处理,原理参考Spring自定义注解定义AOP配置去xml,通过xml模板方式动态生成xml配置的AOP配置并注入到容器。
而对是否开启Redis Message的处理,则是定义了两个bean:RedisMessageListenerContainer、RedisMessageListenerAutoConfig来实现。也定义了注解来简化Redis Message的使用,有兴趣的也可以参考。
在@Configuration的class上引入@EnableRedisIntegration注解来启用自动集成:
@Configuration
@EnableRedisIntegration(redisCacheKeyPrefix = "RDS:CH", enableRedisMessageListener = true)
@PropertySource(value = {"classpath:redis.yml"}, factory = AutoPropertySourceFactory.class)
public class EnableRedisIntegrationConfiguration {
}