第三方框架整合Spring之FactoryBean(二)

354 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

第三方框架整合Spring之FactoryBean(二)

前言

上一篇文章中我们介绍了Mybaits是如何利用FactoryBean与Spring整合的(第三方框架整合Spring的源码包都是以 xx-spring命名的, 如: mybatis-spring, shiro-spring, dubbo-spring等), 并参考着写了一个小demo进行测试,

下面我们来实现一个实用的小工具

通过读取yaml配置, 将不同定义信息的RedisTemplate注入到Spring容器中, 使用时, 只需要注入不同名称的RedisTemplate即可 区分不同redis 数据库 或者不同的 redis 连接, 我们此次使用Spring提供的另外一个接口BeanDefinitionRegistryPostProcessor, 该接口属于BeanDefinition的后置处理器, Spring允许使用者在所有常规BeanDefinition都已加载,但是还没有被实例化的时候修改内部的BeanDefinition注册表, 在进入下一个处理阶段前, 添加自定义的BeanDefinition

思路

首先先整理一下具体的步骤

  • 定义MyRedisTemplateFactoryBean实现FactoryBean接口
  • 定义MyBeanDefinitionRegistryRedis实现BeanDefinitionRegistryPostProcessor, EnvironmentAware(用于获取环境变量和配置信息)接口
  • 注册Bean的定义信息

代码

相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

MyRedisTemplateFactoryBean具体代码

public class MyRedisTemplateFactoryBean implements FactoryBean {
    private RedisStandaloneConfiguration standaloneConfiguration;
    public MyRedisTemplateFactoryBean(RedisStandaloneConfiguration redisStandaloneConfiguration) {
        this.standaloneConfiguration = redisStandaloneConfiguration;
    }
    @Override
    public Object getObject() {
	// 通过jedisConnectionFactory实例化一个RedisTemplate
        RedisTemplate redisTemplate = new RedisTemplate();
        RedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(this.standaloneConfiguration);
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    @Override
    public Class<?> getObjectType() {
        return RedisTemplate.class;
    }
}

MyBeanDefinitionRegistryRedis具体代码

public class MyBeanDefinitionRegistryRedis implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {
    private Environment environment;
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	// 解析配置文件, 将以multi-redis开头的配置信息封装到RedisConfig
        BindResult<RedisConfig> bindResult = Binder.get(environment).bind("multi-redis", RedisConfig.class);
        bindResult.ifBound(redisTemplateConfig -> registerBeanDefinition(redisTemplateConfig, registry));
    }
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
    /**
     * 注册Bean的定义信息
     */
    private void registerBeanDefinition(RedisConfig redisTemplateConfigs, BeanDefinitionRegistry registry) {
        Map<String, MyRedisStandaloneConfiguration> multiRedisTemplateConfigs = redisTemplateConfigs.getRedisConfigs();
        for (Map.Entry<String, MyRedisStandaloneConfiguration> redisStandaloneConfiguration : multiRedisTemplateConfigs.entrySet()) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RedisTemplate.class);
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
	    // 调用构造函数并传入redisStandaloneConfiguration参数
            definition.getConstructorArgumentValues().addGenericArgumentValue(redisStandaloneConfiguration.getValue());
            definition.setBeanClass(MyRedisTemplateFactoryBean.class);
	    // 注入方式AUTOWIRE_BY_NAME, 通过名称注入
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
	    // 名称为yaml中配置的名称
            registry.registerBeanDefinition(redisStandaloneConfiguration.getKey(), definition);
        }
    }
}

Redis相关配置类

@Data
public class RedisConfig {
    private Map<String, MyRedisStandaloneConfiguration> redisConfigs;
}


/**
 * redis基础信息配置
 *
 */
public class MyRedisStandaloneConfiguration extends RedisStandaloneConfiguration {

    public MyRedisStandaloneConfiguration(String hostName, String password, Integer port, Integer database) {
        Optional.ofNullable(hostName).ifPresent(e -> super.setHostName(hostName));
        Optional.ofNullable(password).ifPresent(e -> super.setPassword(password));
        Optional.ofNullable(port).ifPresent(e -> super.setPort(port));
        Optional.ofNullable(database).ifPresent(e -> super.setDatabase(database));
    }
}

/**
 * redisTemplate配置
 *
 */
@Configuration
public class RedisTemplateConfig {

    @Primary
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

redis yaml

微信图片_20220531105300.png 多redisTemplate实例配置, 根据名称注入, 默认配置 hostName: localhost; password: none; port: 6379; database: 0

EnableMultiRedisTemplate

@Retention(RetentionPolicy.RUNTIME)
@Import(MyBeanDefinitionRegistryRedis.class)
public @interface EnableMultiRedisTemplate {
}

测试

启动类加上@EnableMultiRedisTemplate注解开启多实例RedisTemplate支持

    //================================ 测试注入redisTemplate ======================
    /**
     * 自动注入redisTemplate, @Autowired默认按类型注入, 类型有多个或未找到按名称注入
     * FactoryBean方式注入redisTemplate, @Resource默认按名称注入, 名称未找到按类型注入
     * 但是RedisTemplate类型默认有多个<RedisTemplate, StringRedisTemplate> 会报错
     * 需要指定@Primary
     * 当前指定RedisTemplate为主, 即按类型注入默认使用redisTemplate
     * 注释 multi-redis 配置, 则会使用默认redisTemplate注入
     */
    @Autowired
    private StringRedisTemplate strRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private RedisTemplate redisTemplate0;
    @Resource
    private RedisTemplate redisTemplate1;
    @Resource
    private RedisTemplate redisTemplate2;

    @PostConstruct
    public void testRedisTemplate() {
        strRedisTemplate.opsForValue().set("stringRedisTemplate", "stringRedisTemplate");
        redisTemplate.opsForValue().set("redisTemplate", "redisTemplate");
        redisTemplate0.opsForValue().set("test0", "test0");
        redisTemplate1.opsForValue().set("test1", "test1");
        redisTemplate2.opsForValue().set("test2", "test2");
    }

可以看到测试数据已经根据不同配置set到不同的redis database中了

微信图片_20220531105343.png

至此我们已经完成了整合Spring实现多redisTemplate实例配置, 根据名称注入的功能!!!