如何实现自定义多数据源Redis自动注入

265 阅读2分钟

背景:由于项目需要引入多个Redis提供数据访问,手写RedisTemplate导致代码冗余且维护效率低下,无法统一管理,基于此,实现Redis starter,开箱即用。

一、实现思路:

  1. 读取Redis连接信息
  2. 构建连接池
  3. 注册bean到Springboot工厂
  4. 扫描自定义注解并注入对应Redis Bean对象

二、实现过程:

1)读取Redis连接信息

  1. Redis Properties配置属性如下:
Spring:
    multi:
        redisMaster:
            host: *.*.*.*
            port: 6379
            database: 1
            password: xxx
        redisSlave:
            host: *.*.*.*
            port: 6379
            database: 1
            password: xxx
  1. 实现类属性
private int database;
private String host;
private String password;
private int port;
  1. 由于actuator对redis健康性检查,自定义配置后,导致redis无法通过actuator检查,导致启动报错,需要手动关闭,这里采用以代码方式关闭
@Primary
@Bean
@ConditionalOnEnabledHealthIndicator("redis")
public HealthIndicator redisHealthIndicator() 
    // 返回健康状态
    return () -> Health.up().build();
}
  1. 使用EnvironmentAware读取Redis连接属性
//1.读取以spring.multi-redis为前缀的配置属性
//2.形成Map数据
Map<String, RedisProperties> redisPropertiesMap = Binder.get(environment).bind("spring.multi", Bindable.mapOf(String.class, RedisProperties.class)).get();

2)构建连接池

public class RedisBeanBuilder {

    public static RedisBeanBuilder builder() {
        return new RedisBeanBuilder();
    }
    
    public RedisTemplate buildRedisTemplate(RedisProperties redisProperties) {
        GenericObjectPoolConfig poolConfig = RedisConfig.buildPoolConfig();
        RedisStandaloneConfiguration configuration = RedisConfig.buildRedisConfiguration(redisProperties.getHost(),redisProperties.getDatabase(), redisProperties.getPassword());
        LettuceConnectionFactory connectionFactory = RedisConfig.getConnectionFactory(poolConfig, configuration);
        RedisTemplate redisTemplate = RedisConfig.getRedisTemplate(connectionFactory);
        return redisTemplate;
    }
    
    public BeanDefinition buildRedisBean(RedisProperties redisProperties) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RedisTemplate.class, () -> buildRedisTemplate(redisProperties));
        return beanDefinitionBuilder.getBeanDefini
    }
}

3)注册bean到Springboot工厂

//遍历redisPropertiesMap,注册Redis Bean对象
public class DynamicRegisterRedisBean implements BeanDefinitionRegistryPostProcessor, , EnvironmentAware {

    //动态注册成bean对象
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        for (Map.Entry<String, RedisProperties> entry : redisPropertiesMap.entrySet()) {
            BeanDefinition beanDefinition = RedisBeanBuilder.builder().buildRedisBean(redisSecurity.get(entry.getKey()));
            beanDefinitionRegistry.registerBeanDefinition(entry.getKey(), beanDefinition);
        }
    }
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    
    }
    
    @Override
    public void setEnvironment(Environment eenvironment) {
        redisPropertiesMap = Binder.get(environment).bind("spring.multi", Bindable.mapOf(String.class,RedisProperties.class)).get();
    }
 }

4)自定义注解注入Redis

通过使用AutoRedis注解注入Redis属性被调用,类似@Autowired注解,用法如下

@AutoRedis
private RedisService redisService;
@Component
public class AutoInjectRedisServiceBean implements BeanPostProcessor, ApplicationContextAware {

    private GenericApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = (GenericApplicationContext) applicationContext;
    }
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        List<Field> allFields = ReflectUtils.getAllFields(bean);
        for (Field field : allFields) {
            AutoRedis annotation = field.getAnnotation(AutoRedis.class);
            if (StringUtils.isNull(annotation)) {
                 continue;
            }
            String redisName = annotation.value().getName();
            // 如果Bean不存在,且不允许使用默认Bean就抛出异常
            if (!this.applicationContext.containsBean(redisName)) {
                throw new CustomException(annotation.value().name());
            }
            try {
                Object redisServiceBean;
                // 如果指定bean不存在,使用默认bean
                redisServiceBean = !this.applicationContext..containsBean(redisName) ? this.applicationContext.getBean(RedisType.REDIS.getName(), RedisService.class) : this.applicationContext.getBean(redisName, 
            RedisService.class);
                // 注入bean
                ReflectUtils.setFieldValue(bean, field.getName(), redisServiceBean);
                } catch (Exception e) {
                throw new CustomException("注入Redis Bean异常", e);
            }
         }
        return bean;
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}