扑街前言:从上篇可以了解到spring boot自动配置的一部分流程和原理,Spring Boot用各种注解将配置文件中的信息进行了引入和扫描,然后在IOC容器创建后进行了注入,本次我们详细了解一下IOC容器中到底是注入了各个什么Bean。(认识到自己是菜鸟的第二十八天)
Spring boot自动配置的关键——META-INF/spring.factories文件:
从上篇文章中我们可以得知,外部jar中的bean在IOC容器的注入信息来源是META-INF/spring.factories文件中EnableAutoConfiguration类的全限定名对应的值,这些值也全部都是全限定名,那么以Spring Boot为Redis提供的RedisTemplate对象为例,可以解析一下Spring Boot为Redis的连接和创建做了写什么。
从下面截图可以看出,一共有三个Redis的相关连接,RedisAutoConfiguration:Redis的自动配置类、RedisReactiveAutoConfiguration:redis的反应式自动配置、RedisRepositoriesAutoConfiguration:Redis的存储库自动配置。显然后续使用的就是RedisAutoConfiguration:Redis的自动配置类。
RedisAutoConfiguration:
从源码中可以看到RedisAutoConfiguration类的目的就是创建出RedisTemplate对象和StringRedisTemplate对象,它们之前具体的区别简单来说就是:
- StringRedisTemplate对象:是专门对Redis操作String类型字符串的工具类,如果redis数据库里面本来存的是字符串数据或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可;
- RedisTemplate对象:使用的是JdkSerializationRedisSerializer,存入数据会将数据先序列化成字节数组然后在存入Redis数据库,当数据是复杂的对象类型,而取出的时候又不想做任何的数据转换,直接从Redis里面取出一个对象,使用RedisTemplate是更好的选择;
- 两者的数据也是不共通的,也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。其实他们两者之间的区别主要在于他们使用的序列化类。
我们目前就看一个方法就行了,以redisTemplate()方法为例,使用@Bean调动下面的方法,产生的bean注入IOC容器、使用@ConditionalOnMissingBean(name = "redisTemplate") 只有在redisTemplate没有注入的时候,才会执行下面的方法。当注解完成之后,方法中构建一个RedisTemplate对象,并设置对应的RedisConnectionFactory对象属性Redis的连接工厂对象属性。
方法结束,我们再看下类上面的注解,@Configuration 表明这是个配置类,需要扫描、@ConditionalOnClass 表明类路径下有RedisOperations(Redis的操作接口,RedisTemplate也就是实现这个接口)这个类的时候该类才会被加载到容器中、@EnableConfigurationProperties 表示让RedisProperties 配置类生效、@Import 导入了两个类LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class。下面详细说下这些注解的具体作用。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
Condition接口:
说注解之前,spring提供给@Conditional(XXXCondition.class)之类注解的Condition接口,这个接口只有一个方法matches方法,当matches方法返回true,装配bean,返回false,不装配bean。两个参数中的ConditionContext对象,它持有不少有用的对象,可以用来获取很多系统相关的信息,来丰富条件判断,AnnotatedTypeMetadata 注解类型元数据对象,用于获取注解中的数据。
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
@ConditionalOnClass(RedisOperations.class):
该注解的目的,判断类路径下有没有RedisOperations这个类,如果有的话才会将自身进行加载和注入,反正则不会。其底层也就是使用的@Conditional注解,而该注解就是需要传入一个Class数组,并且需要继承Condition接口。
RedisOperations.class这个类就是Redis的操作接口,RedisTemplate也就是RedisOperations接口的实现类。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* The classes that must be present. Since this annotation is parsed by loading class
* bytecode, it is safe to specify classes here that may ultimately not be on the
* classpath, only if this annotation is directly on the affected component and
* <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
* use this annotation as a meta-annotation, only use the {@link #name} attribute.
* @return the classes that must be present
*/
Class<?>[] value() default {};
/**
* The classes names that must be present.
* @return the class names that must be present.
*/
String[] name() default {};
}
@EnableConfigurationProperties(RedisProperties.class):
之前我们就有了解到@Enable开头的注解就是spring boot提供出用于动态注入的注解,底层原理就是@import注解,而这个注解的目的是让RedisProperties配置类生效,从源码看首先是将RedisProperties配置类放入了自身的数组中,并且import了EnableConfigurationPropertiesRegistrar类,而这个类的目的就是更新注解中的bean定义表,我的理解也就是为了将RedisProperties配置类中的所有信息放进注解信息里面。
可以看下RedisProperties配置类里面有什么,其实就是一些Redis的连接配置端口、密码、地址之类的信息,取值这个注解也是之前说过的@ConfigurationProperties 当前意思就是取spring.redis开头的所有信息。这里还有不用的连接方式,比如jedis之类的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
/**
* The bean name of the configuration properties validator.
* @since 2.2.0
*/
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
/**
* Convenient way to quickly register
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans with
* Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@code @ConfigurationProperties} annotated beans to register
*/
Class<?>[] value() default {};
}
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
getTypes(metadata).forEach(beanRegistrar::register);
}
private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
return metadata.getAnnotations().stream(EnableConfigurationProperties.class)
.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
.filter((type) -> void.class != type).collect(Collectors.toSet());
}
@SuppressWarnings("deprecation")
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
ConfigurationPropertiesBindingPostProcessor.register(registry);
BoundConfigurationProperties.register(registry);
ConfigurationBeanFactoryMetadata.register(registry);
}
}
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }):
@import注解不用说,这里说下导入的两个类LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class,从@import注解的四种使用方法分析,这里使用的第二种使用方法:导入配置类,也就是这两个是申明的配置类,是@Configuration注解修饰。
那么首先看LettuceConnectionConfiguration类,这个类继承RedisConnectionConfiguration类,并使用@ConditionalOnClass 判断类路径下有RedisClient Redis的客户端类,才会加载该类。并且可以看到其构造方法,也是调用的父类RedisConnectionConfiguration类的构造方法。RedisConnectionConfiguration类的构造方法也就是三个主要对象,一是RedisProperties 刚刚注解加载的Redis各个信息的配置类,二是RedisSentinelConfiguration的哨兵模式配置对象,最后是RedisClusterConfiguration Redis的集群配置对象。
这样可以看出LettuceConnectionConfiguration类主要是Redis的各个信息配置类。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
}
}
abstract class RedisConnectionConfiguration {
private final RedisProperties properties;
private final RedisSentinelConfiguration sentinelConfiguration;
private final RedisClusterConfiguration clusterConfiguration;
protected RedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
this.properties = properties;
this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
}
}
再说JedisConnectionConfiguration类,说起来跟上面的那个没什么差别,一样是继承RedisConnectionConfiguration类,使用父类的构造方法,其实差一点可以看到就是关于判断类不同,上面的是RedisClient客户端,而这个是Jedis客户端,我的理解这个其实也是用于Redis的各个信息配置,只是两者使用的Redis客户端不一样。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {
JedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
super(properties, sentinelConfiguration, clusterConfiguration);
}
}
上述所有内容就是把spring boot对于RedisTemplate的自动配置流程说了一遍,可以看到我们正常使用的时候,其实只是将RedisTemplate用注解修饰说明,然后配置文件写好相关的Redis的端口、地址之类的信息,这样就能正常使用的,Redis的连接还有相应的操作接口都是spring boot已经封装好了的,只能说spring还是相当强大的。
后续还有spring boot的一系列注解,慢慢写吧,我工作中遇到的相关问题也会在这一系列的文章中提到。