相信大家都知道springboot的好处,约定大约配置,开箱即用,内置tomcat等等
本人也是一直知其然不知其所以然
本次以RedisTemplate为例,结合其他博文,了解下RedisTemplate到底是如何被作为bean自动注册到spring容器中的
日常开发中,如下引入pom依赖,按照固定格式配置好yml或者properties文件即可在代码中注入使用RedisTemplate,这一切到底是因为springBoot道德的沦丧,还是因spring人性的扭曲,就让本期走进科学,还原故事的真相
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>x.x.x</version>
</dependency>
redis:
host: 1.3.1.4
password: 520
port: 777
timeOut: 5000
maxIdle: 50
maxWaitMillis: 5000
maxTotal: 500
正式开始我的表演
1 RedisAutoConfiguration
代码中点击进入RedisTemplate类,再点击类名找到进入RedisAutoConfiguration类
@Configuration
@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;
}
}
-
@Configuration常见的配置注解,内部含有一个以上的@Bean,让Spring能扫描到内部的@Bean,当然在Spring Boot中,默认只会扫描到启动类所在包或其下级包的类,所以还会通过其他的设置来让这个类被扫描到,这个后面会详细说明。
@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法, 这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描, 并用于构建bean定义,初始化Spring容器。
-
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })通过@Import注解方式生成类实例并注入Spring容器。
-
@EnableConfigurationProperties(RedisProperties.class)让RedisProperties 类被扫描到的关键。这时,如果RedisAutoConfiguration被扫描到,则同时也会去扫描RedisProperties类。
-
@ConditionalOnClass(RedisOperations.class),当存在RedisOperations类时才会进行扫描,这个类什么时候被引入classpath的之后会提到。
2 点击进入JedisConnectionConfiguration类中
@Configuration
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {
private final RedisProperties properties;
private final ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers;
JedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration,
ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
super(properties, sentinelConfiguration, clusterConfiguration);
this.properties = properties;
this.builderCustomizers = builderCustomizers;
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
return createJedisConnectionFactory();
}
private JedisConnectionFactory createJedisConnectionFactory() {
JedisClientConfiguration clientConfiguration = getJedisClientConfiguration();
if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
}
if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(),
clientConfiguration);
}
return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
private JedisClientConfiguration getJedisClientConfiguration() {
JedisClientConfigurationBuilder builder = applyProperties(
JedisClientConfiguration.builder());
RedisProperties.Pool pool = this.properties.getJedis().getPool();
if (pool != null) {
applyPooling(pool, builder);
}
if (StringUtils.hasText(this.properties.getUrl())) {
customizeConfigurationFromUrl(builder);
}
customize(builder);
return builder.build();
}
private JedisClientConfigurationBuilder applyProperties(
JedisClientConfigurationBuilder builder) {
if (this.properties.isSsl()) {
builder.useSsl();
}
if (this.properties.getTimeout() != null) {
Duration timeout = this.properties.getTimeout();
builder.readTimeout(timeout).connectTimeout(timeout);
}
return builder;
}
private void applyPooling(RedisProperties.Pool pool,
JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
builder.usePooling().poolConfig(jedisPoolConfig(pool));
}
private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(pool.getMaxActive());
config.setMaxIdle(pool.getMaxIdle());
config.setMinIdle(pool.getMinIdle());
if (pool.getMaxWait() != null) {
config.setMaxWaitMillis(pool.getMaxWait().toMillis());
}
return config;
}
private void customizeConfigurationFromUrl(
JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
if (connectionInfo.isUseSsl()) {
builder.useSsl();
}
}
private void customize(
JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
this.builderCustomizers.orderedStream()
.forEach((customizer) -> customizer.customize(builder));
}
}
- @Import注解会通过JedisConnectionConfiguration构造方法将JedisConnectionConfiguration的实例注入到Spring容器中,这里有一个RedisProperties参数,实际上就是在(3)中注入的RedisProperties,这样JedisConnectionConfiguration就获得了RedisProperties,也就获得了之前我们在application.propertie中配置的redis服务器连接属性。
- 通过@Configuration和@Bean的定义可知,会扫描到redisConnectionFactory()方法并返回实体,并注入到Spring容器,对应的类为RedisConnectionFactory。(JedisConnectionFactory实现了RedisConnectionFactory接口,所以可以这样)
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
return createJedisConnectionFactory();
}
private JedisConnectionFactory createJedisConnectionFactory() {
JedisClientConfiguration clientConfiguration = getJedisClientConfiguration();
if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
}
if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(),
clientConfiguration);
}
return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
private JedisClientConfiguration getJedisClientConfiguration() {
JedisClientConfigurationBuilder builder = applyProperties(
JedisClientConfiguration.builder());
RedisProperties.Pool pool = this.properties.getJedis().getPool();
if (pool != null) {
applyPooling(pool, builder);
}
if (StringUtils.hasText(this.properties.getUrl())) {
customizeConfigurationFromUrl(builder);
}
customize(builder);
return builder.build();
}
① getJedisClientConfiguration()方法,该方法从之前注入的RedisProperties中获取了 Jedis客户端连接池。
②createJedisConnectionFactory会根据配置的redis参数判断用单机/哨兵/集群模式来创建JedisConnectionFactory实例。
总结:创建并注入了JedisConnectionFactory实例,
JedisConnectionFactory实例中包含有Jedis的客户端连接池,之后就能用其创建连接了。
3 redisTemplate方法
以RedisAutoConfiguration中的redisTemplate为例
@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注解的方法,因此会被Spring扫描并注入。
@ConditionalOnMissingBean(name = “redisTemplate”)当Spring容器中不存在RedisTemplate实例时才会进行扫描注入,很明显是为了防止重复注入。
该方法有一个RedisConnectionFactory参数。
前面2.2中我们知道redisConnectionFactory方法最后会注入一个JedisConnectionFactory实例,而JedisConnectionFactory又是继承于RedisConnectionFactory。同志们,你们懂我的意思了吧∠( ᐛ 」∠)_。
so:该方法会将先前注入的redisConnectionFactory赋给新建的redisTemplate实例,
然后将redisTemplate实例注入Spring容器,我们就可以使用redisTemplate实例了。
总结
- 入口类-各自动装载bean的对应的AutoConfiguration类
redis为例 RedisTemplate->RedisAutoConfiguration - AutoConfiguration类上的关键注解@Import
redis为例 @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) - 导入@Import注解中的实例加到spring容器
相关注解说明
-
@Import
只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中
-
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
这个注解通俗的说就是Spring工程中引用了括号中相关的包 才会构建这个bean
-
@ConditionalOnBean
仅仅在当前上下文中存在某个对象时,才会实例化一个Bean
-
@ConditionalOnClass
某个class位于类路径上,才会实例化一个Bean
-
@ConditionalOnMissingBean
仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean,防止重复注入
-
@ConditionalOnMissingClass
某个class类路径上不存在的时候,才会实例化一个Bean