扫描下方二维码或者微信搜索公众号
菜鸟飞呀飞
,即可关注微信公众号,阅读更多Spring源码分析文章。
1. 前言
在笔者的上一篇文章中(点击此处跳转查看)介绍了@Import注解的使用场景和原理,以及@EnableXXX注解的实现原理,这一篇文章将通过一个自定义的@Enable注解来实现一个Redis和Spring整合的插件。
- 以前我们在Spring中整合Redis时(非SpringBoot和redis整合,看完本文,
spring-boot-starter-data-redis
这个包的原理也基本明白了),通常第一步是先引入redis客户端的jar包,第二步通过XML方式或者@Bean方式配置一个Jedis或者JedisCluster对象,第三步就是在代码中注入Jedis或者JedisCluter。这三步中,最为麻烦的是第二步。那么有没有一种方法能像@EnableXXX那样满足我们的需求呢?
2. 思路
要想完成Spring和redis的整合,我们就需要向Spring容器中添加一个Jedis或者JedisCluster这样的bean,然后还需要为redis的客户端设置属性,如连接地址,端口号等。那么我们可以自定义一个@EnableJedisClient或者@EnableJedisClusterClient注解,让这两个注解来达到我们的目的。
- 自定义@EnableJedisClient和@EnableJedisClusterClient注解,让这两个注解来向容器中注册Jedis和JedisCluster。
- 由于@Enable注解通常是结合@Import注解使用的,而@Import注解能帮助我们向容器中注册Bean,但是没办法为Bean的属性赋值,因为Import注解的处理只能干预BeanFactory的建造过程,不能参与Bean的创建过程,例如不能参与为Bean的属性赋值等操作。
- 既然想要参与Bean的创建过程,为Bean的属性赋值,那么我们可以通过BeanPostProcessor来参与Bean的创建过程,创建JedisClientBeanPostProcessor和JedisClusterClientBeanPostProcessor类分别为Jedis和JedisCluster来设置属性,这两个类均实现了BeanPostProcessor接口。
3. 代码实现
先实现Jedis的整合,再实现JedisCluster的整合。前者是针对单机版的redis,后者是针对集群版的redis。
- pom依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.8.RELEASE</version>
<!-- 如果该项目是准备作为一个第三方插件的话,这里对spring的依赖范围最好指定为provided-->
<!--<scope>provided</scope>-->
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
3.1 @EnableJedisClient
- 首先定义一个EnableJedisClient注解,在注解中通过Import注解导入了JedisClientImportRegistrar类。并且为EnableJedisClient添加了一个属性:namespace,添加该属性的目的是为了让项目中能同时引入多个redis。例如:在项目中需要同时连接两个不同的redis机器,那么这个时候就可以通过namespace来区分。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(JedisClientImportRegistrar.class)
public @interface EnableJedisClient {
String namespace() default "default";
}
- JedisClientImportRegistrar类实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions()方法,在方法中向Spring容器中注册了两个Bean,一个是Jedis,一个是JedisClientBeanPostProcessor后置处理器,注册该后置处理器是为了在后面Jedis初始化的过程中,为jedis设置连接地址,端口号等属性。
- Jedis在容器中的beanName是 namespace + “Jedis”,namespace的值是从EnableJedisClient注解中获取到的。例如如下示例使用:那么此时的namespce的值为demo,如果不指定,则为default。
@EnableJedisClient(namespace = "demo")
public class AppConfig {
}
- 源码如下
public class JedisClientImportRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(EnableJedisClient.class.getName());
AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationAttributes);
String namespace = attributes.getString("namespace");
// 创建jedis的BeanDefinition,然后注册进容器中,beanName为namespace + "Jedis"
BeanDefinitionBuilder jedisBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Jedis.class);
AbstractBeanDefinition jedisBeanDefinition = jedisBeanDefinitionBuilder.getBeanDefinition();
beanDefinitionRegistry.registerBeanDefinition(namespace+Jedis.class.getSimpleName(),jedisBeanDefinition);
// 向容器注册一个Jedis的后置处理器,这是为了让后置处理器为Jedis的属性赋值
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(JedisClientBeanPostProcessor.class);
beanDefinitionRegistry.registerBeanDefinition(JedisClientBeanPostProcessor.class.getSimpleName(),beanDefinitionBuilder.getBeanDefinition());
}
}
- JedisClientBeanPostProcessor实现了BeanPostProcessor和EnvironmentAware接口,实现EnvironmentAware接口是为了获取到配置文件中相关配置。重写了postProcessBeforeInitialization(),在该方法中,先读取了配置文件中的redis配置,然后为Jedis对象赋值。源码如下:
public class JedisClientBeanPostProcessor implements BeanPostProcessor,EnvironmentAware {
private static String JEDIS_ADDRESS_PREFIX = "jedis.url"