Springboot自动装配原理详解

传统配置

1)传统ssm整合redis的时候 需要在xml的配置文件中 进行大量的配置Bean

第一步:加入依赖

<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
	<version>2.0.9.RELEASE</version>
</dependency>

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>2.9.0</version>
</dependency>

第二步: bean的配置

beans.xml

    //配置连接池
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="minIdle" value="10"></property>
        <property name="maxTotal" value="20"></property>
    </bean>
    
    //配置连接工厂
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="47.104.128.12"></property>
        <property name="password" value="123456"></property>
        <property name="database" value="0"></property>
        <property name="poolConfig" ref="poolConfig"></property>
    </bean>


    //配置 redisTemplate 模版类
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory"  ref="jedisConnectionFactory"/>
        <!--如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!!  -->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
         <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
    </bean>

第三步:导入配置

**@ImportResource(locations = "classpath:beans.xml") 可以导入xml的配置文件

**

@SpringBootApplication
@ImportResource(locations = "classpath:beans.xml")
@RestController
public class OpenAutoconfigPrincipleApplication {

	@Autowired
	private RedisTemplate redisTemplate;

	@RequestMapping("/testRedis")
	public String testRedis() {
		redisTemplate.opsForValue().set("smlz","smlz");
		return "OK";
	}
}

2)综上所述 我们发现,若整合redis的时候通过传统的整合,进行了大量的配置,那么我们来看下通过springboot自动装配整合的对比

Springboot自动装配

导入依赖:

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lettuce pool 缓存连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

修改yml配置文件

spring:
  #redis配置
  redis:
    host: ${redis.host}
    port: ${redis.port}
    password: ${redis.password}
    database: ${redis.database}
    timeout: 50000
    expireTime: 3600000 #单位秒
    lettuce:
      pool:
        max-idle: 50 # 连接池中的最大空闲连接 默认 8
        min-idle: 0 # 连接池中的最小空闲连接 默认 0
        max-active: 100 # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-wait: 1000 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1

直接使用

下述代码可以不要配置,为了解决保存使用jdk的序列方式才配置的

	@Bean
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)  {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

3)传统整合和springboot自动装配 优劣势分析

一目了然,不需要分析了吧

Springboot自动装配原理前置技术

@Import

没有被spring管理的实体类

public class UserService {

    public void testImport() {
        System.out.println("我是通过@Import导入进来的UserService");
    }

    public void testImportSelector() {
        System.out.println("我是通过@Import和ImportSelector导入进来的UserService");
    }

    public void testDeferredImportSelector() {
        System.out.println("我是通过@Import和DeferredImportSelector导入进来的UserService");
    }

    public void testImportBeanDefinitionRegistrar() {
        System.out.println("我是通过@Import和ImportBeanDefinitionRegistrar导入进来的UserService");
    }

}

@Import注解导入

@Configuration
@Import(UserService.class)
public class SpringConfig {

}

直接使用没有被spring管理的实体类UserService:

@RestController
public class TestController {

	//自动注入 userService
	@Autowired
	private UserService userService;

	@RequestMapping("testImport")
	public String testImport() {
		userService.testImport();
		return "testImportOk";
	}	

}

ImportSelector

@Import导入ImportSelector的实现类

配置类

@Configuration
@Import(value = {TestImportSelector.class})
public class SpringConfig {

}

TestImportSelector

public class TestImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.yoocar.service.UserService"};
    }

}

直接使用没有被spring管理的实体类UserService:

@RestController
public class TestController {

   //自动注入 userService
   @Autowired
   private UserService userService;

   @RequestMapping("testImportSelector")
   public String testImportSelector() {
      userService.testImportSelector();
      return "testImportOk";
   }

}

DeferredImportSelector

@Order(100)
public class TestDeferredImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.yoocar.service.UserService"};
    }

}

该接口有2个特点

  1. 继承该接口的ImportSelector会在最后执行
  2. 如果定义了1一个以上的DeferredImportSelector则使用Order接口来进行排序

通过@Import导入ImportBeanDefinitionRegistrar 从而进来导入组件

ImportBeanDefinitionRegistrar

public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //定义一个BeanDefinition
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(UserService.class);
        //把自定义的bean定义导入到容器中
        beanDefinitionRegistry.registerBeanDefinition("userService", rootBeanDefinition);
    }

}

核心代码:

@Conditional和Condition接口

spring底层条件装配的原理@Conditional

标注在类上面,表示该类下面的所有@Bean都会启用配置,也可以标注在方法上面,只是对该方法启用配置。示例如下

应用要求:比如我有二个组件

而TestLog是依赖TestAspect的,只有容器中有存在TestAspect组件才会加载TestLog

public class TestLog {
}

public class TestAspect {
}

①:自定义条件组件条件

public class TestConditional implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //容器中包含Aspect组件才返回Ture
        if(conditionContext.getBeanFactory().containsBean("testAspect")){
            return true;
        }else{
            return false;
        }
    }

}
    
    -------------------------------------该情况下会加载二个组件-------------------------------------------------

    @Bean
    public TestAspect testAspect() {
        System.out.println("Aspect组件自动装配到容器中");
        return new TestAspect();
    }


    @Bean
    @Conditional(value = TestConditional.class)
    public TestLog testLog() {
        System.out.println("Log组件自动装配到容器中");
        return new TestLog();
    }
    
    -------------------------------------二个组件都不会被加载----------------------------------------
    //@Bean
    public TestAspect testAspect() {
        System.out.println("Aspect组件自动装配到容器中");
        return new TestAspect();
    }


    @Bean
    @Conditional(value = TestConditional.class)
    public TestLog testLog() {
        System.out.println("Log组件自动装配到容器中");
        return new TestLog();
    }
    
    

一些衍生的注解

  • @ConditionalOnClass(RedisOperations.class) (Spring工程中引用了Redis的包 才会构建这个bean)
  • @ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
  • @ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
  • @ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
  • @ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
  • @ConditionalOnNotWebApplication(不是web应用)

Springboot自动装配原理

@SpringbootApplication

自动装配原理分析 从@SpringbootApplication入手分析

image

那我们仔细分析

AutoConfigurationImportSelector

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
      ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

   private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

   private static final String[] NO_IMPORTS = {};

   private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);

   private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";

   private ConfigurableListableBeanFactory beanFactory;

   private Environment environment;

   private ClassLoader beanClassLoader;

   private ResourceLoader resourceLoader;

   private ConfigurationClassFilter configurationClassFilter;

   @Override
   public String[] selectImports(AnnotationMetadata annotationMetadata) {
      if (!isEnabled(annotationMetadata)) {
         return NO_IMPORTS;
      }
      //关键代码
      AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
      return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
   }

  
   protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
      if (!isEnabled(annotationMetadata)) {
         return EMPTY_ENTRY;
      }
      AnnotationAttributes attributes = getAttributes(annotationMetadata);
      //关键代码,去META-INF/spring.factories文件中 查询自动装配的信息
      List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
      //去除重复的配置类,可能存在重复的
      configurations = removeDuplicates(configurations);
      //去除exclusion的
      Set<String> exclusions = getExclusions(annotationMetadata, attributes);
      checkExcludedClasses(configurations, exclusions);
      configurations.removeAll(exclusions);
      //这里就是最后返回出去的configurations
      configurations = getConfigurationClassFilter().filter(configurations);
      //根据maven 导入的启动器过滤出 需要导入的配置类
      fireAutoConfigurationImportEvents(configurations, exclusions);
      return new AutoConfigurationEntry(configurations, exclusions);
   }

}

getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  //查询自动装配的信息
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	String factoryTypeName = factoryType.getName();
	//查询自动装配的信息
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   try {
      //查询自动装配的信息
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

可以看下FACTORIES_RESOURCE_LOCATION

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

image

RedisAutoConfiguration分析

导入组件RedisTemplate

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class 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;
	}

    //导入StringRedisTemplate
	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

@Configuration(proxyBeanMethods = false)

proxyBeanMethods = true 或不写,是Full模式

proxyBeanMethods = false 是lite模式

不带@Configuration的类叫Lite配置类,详见spring源码解析

根据注释proxyBeanMethods是为了让使用@Bean注解的方法被代理而实现bean的生命周期的行为。
1.设置为true,那么直接调用方法获取bean,不会创建新的bean,而是会走bean的生命周期的行为。
2.设置为false, 那么直接调用方法获取bean,会创建新的bean,且不会走bean的生命周期的行为。

@ConditionalOnClass(RedisOperations.class)

这里的关键就是它,在Spring工程中引用了Redis的jar包 才会构建这个bean

RedisOperations是spring提供的Redis的操作接口,在此不多介绍

@EnableConfigurationProperties(RedisProperties.class)

@EnableConfigurationProperties
/**
 * Enable support for {@link ConfigurationProperties @ConfigurationProperties} annotated
 * beans. {@code @ConfigurationProperties} beans can be registered in the standard way
 * (for example using {@link Bean @Bean} methods) or, for convenience, can be specified
 * directly on this annotation.
 *
 * @author Dave Syer
 * @since 1.0.0
 */
@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 {};

}

@EnableConfigurationProperties注解的作用是:使使用 @ConfigurationProperties 注解的类生效

如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。说白了 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。

EnableConfigurationPropertiesRegistrar
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      registerInfrastructureBeans(registry);
      //注册ConfigurationPropertiesBeanRegistrar
      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);
   }

}
ConfigurationPropertiesBeanRegistrar

ConfigurationPropertiesBeanRegistrar用于将@ConfigurationProperties标注的bean注册为一个bean定义,源码如下:

final class ConfigurationPropertiesBeanRegistrar {

   //bean定义注册器
   private final BeanDefinitionRegistry registry;

   //IOC容器工厂类
   private final BeanFactory beanFactory;

   ConfigurationPropertiesBeanRegistrar(BeanDefinitionRegistry registry) {
      this.registry = registry;
      this.beanFactory = (BeanFactory) this.registry;
   }

   //将@ConfigurationProperties注解标注的bean注册到IOC容器
   void register(Class<?> type) {
      MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
            .from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
      //核心代码
      register(type, annotation);
   }

   void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
      String name = getName(type, annotation);
      if (!containsBeanDefinition(name)) {
         //核心代码
         registerBeanDefinition(name, type, annotation);
      }
   }

   private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
      String prefix = annotation.isPresent() ? annotation.getString("prefix") : "";
      return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
   }

   private boolean containsBeanDefinition(String name) {
      return containsBeanDefinition(this.beanFactory, name);
   }

   private boolean containsBeanDefinition(BeanFactory beanFactory, String name) {
      if (beanFactory instanceof ListableBeanFactory
            && ((ListableBeanFactory) beanFactory).containsBeanDefinition(name)) {
         return true;
      }
      if (beanFactory instanceof HierarchicalBeanFactory) {
         return containsBeanDefinition(((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(), name);
      }
      return false;
   }

   private void registerBeanDefinition(String beanName, Class<?> type,
         MergedAnnotation<ConfigurationProperties> annotation) {
      Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
            + " annotation found on  '" + type.getName() + "'.");
      //核心代码
      this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
   }

   //根据beanName和class实例创建BeanDefinition
   private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
      if (BindMethod.forType(type) == BindMethod.VALUE_OBJECT) {
         return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
      }
      GenericBeanDefinition definition = new GenericBeanDefinition();
      definition.setBeanClass(type);
      return definition;
   }

}
RedisProperties
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

   /**
    * Database index used by the connection factory.
    */
   private int database = 0;

   /**
    * Connection URL. Overrides host, port, and password. User is ignored. Example:
    * redis://user:password@example.com:6379
    */
   private String url;

   /**
    * Redis server host.
    */
   private String host = "localhost";

   /**
    * Login password of the redis server.
    */
   private String password;

   /**
    * Redis server port.
    */
   private int port = 6379;

   /**
    * Whether to enable SSL support.
    */
   private boolean ssl;

   /**
    * Connection timeout.
    */
   private Duration timeout;

   /**
    * Client name to be set on connections with CLIENT SETNAME.
    */
   private String clientName;

   private Sentinel sentinel;

   private Cluster cluster;

   private final Jedis jedis = new Jedis();

   private final Lettuce lettuce = new Lettuce();

   public int getDatabase() {
      return this.database;
   }

   public void setDatabase(int database) {
      this.database = database;
   }

   public String getUrl() {
      return this.url;
   }

   public void setUrl(String url) {
      this.url = url;
   }

   public String getHost() {
      return this.host;
   }

   public void setHost(String host) {
      this.host = host;
   }

   public String getPassword() {
      return this.password;
   }

   public void setPassword(String password) {
      this.password = password;
   }

   public int getPort() {
      return this.port;
   }

   public void setPort(int port) {
      this.port = port;
   }

   public boolean isSsl() {
      return this.ssl;
   }

   public void setSsl(boolean ssl) {
      this.ssl = ssl;
   }

   public void setTimeout(Duration timeout) {
      this.timeout = timeout;
   }

   public Duration getTimeout() {
      return this.timeout;
   }

   public String getClientName() {
      return this.clientName;
   }

   public void setClientName(String clientName) {
      this.clientName = clientName;
   }

   public Sentinel getSentinel() {
      return this.sentinel;
   }

   public void setSentinel(Sentinel sentinel) {
      this.sentinel = sentinel;
   }

   public Cluster getCluster() {
      return this.cluster;
   }

   public void setCluster(Cluster cluster) {
      this.cluster = cluster;
   }

   public Jedis getJedis() {
      return this.jedis;
   }

   public Lettuce getLettuce() {
      return this.lettuce;
   }

   /**
    * Pool properties.
    */
   public static class Pool {

      /**
       * Maximum number of "idle" connections in the pool. Use a negative value to
       * indicate an unlimited number of idle connections.
       */
      private int maxIdle = 8;

      /**
       * Target for the minimum number of idle connections to maintain in the pool. This
       * setting only has an effect if both it and time between eviction runs are
       * positive.
       */
      private int minIdle = 0;

      /**
       * Maximum number of connections that can be allocated by the pool at a given
       * time. Use a negative value for no limit.
       */
      private int maxActive = 8;

      /**
       * Maximum amount of time a connection allocation should block before throwing an
       * exception when the pool is exhausted. Use a negative value to block
       * indefinitely.
       */
      private Duration maxWait = Duration.ofMillis(-1);

      /**
       * Time between runs of the idle object evictor thread. When positive, the idle
       * object evictor thread starts, otherwise no idle object eviction is performed.
       */
      private Duration timeBetweenEvictionRuns;

      public int getMaxIdle() {
         return this.maxIdle;
      }

      public void setMaxIdle(int maxIdle) {
         this.maxIdle = maxIdle;
      }

      public int getMinIdle() {
         return this.minIdle;
      }

      public void setMinIdle(int minIdle) {
         this.minIdle = minIdle;
      }

      public int getMaxActive() {
         return this.maxActive;
      }

      public void setMaxActive(int maxActive) {
         this.maxActive = maxActive;
      }

      public Duration getMaxWait() {
         return this.maxWait;
      }

      public void setMaxWait(Duration maxWait) {
         this.maxWait = maxWait;
      }

      public Duration getTimeBetweenEvictionRuns() {
         return this.timeBetweenEvictionRuns;
      }

      public void setTimeBetweenEvictionRuns(Duration timeBetweenEvictionRuns) {
         this.timeBetweenEvictionRuns = timeBetweenEvictionRuns;
      }

   }

   /**
    * Cluster properties.
    */
   public static class Cluster {

      /**
       * Comma-separated list of "host:port" pairs to bootstrap from. This represents an
       * "initial" list of cluster nodes and is required to have at least one entry.
       */
      private List<String> nodes;

      /**
       * Maximum number of redirects to follow when executing commands across the
       * cluster.
       */
      private Integer maxRedirects;

      public List<String> getNodes() {
         return this.nodes;
      }

      public void setNodes(List<String> nodes) {
         this.nodes = nodes;
      }

      public Integer getMaxRedirects() {
         return this.maxRedirects;
      }

      public void setMaxRedirects(Integer maxRedirects) {
         this.maxRedirects = maxRedirects;
      }

   }

   /**
    * Redis sentinel properties.
    */
   public static class Sentinel {

      /**
       * Name of the Redis server.
       */
      private String master;

      /**
       * Comma-separated list of "host:port" pairs.
       */
      private List<String> nodes;

      /**
       * Password for authenticating with sentinel(s).
       */
      private String password;

      public String getMaster() {
         return this.master;
      }

      public void setMaster(String master) {
         this.master = master;
      }

      public List<String> getNodes() {
         return this.nodes;
      }

      public void setNodes(List<String> nodes) {
         this.nodes = nodes;
      }

      public String getPassword() {
         return this.password;
      }

      public void setPassword(String password) {
         this.password = password;
      }

   }

   /**
    * Jedis client properties.
    */
   public static class Jedis {

      /**
       * Jedis pool configuration.
       */
      private Pool pool;

      public Pool getPool() {
         return this.pool;
      }

      public void setPool(Pool pool) {
         this.pool = pool;
      }

   }

   /**
    * Lettuce client properties.
    */
   public static class Lettuce {

      /**
       * Shutdown timeout.
       */
      private Duration shutdownTimeout = Duration.ofMillis(100);

      /**
       * Lettuce pool configuration.
       */
      private Pool pool;

      private final Cluster cluster = new Cluster();

      public Duration getShutdownTimeout() {
         return this.shutdownTimeout;
      }

      public void setShutdownTimeout(Duration shutdownTimeout) {
         this.shutdownTimeout = shutdownTimeout;
      }

      public Pool getPool() {
         return this.pool;
      }

      public void setPool(Pool pool) {
         this.pool = pool;
      }

      public Cluster getCluster() {
         return this.cluster;
      }

      public static class Cluster {

         private final Refresh refresh = new Refresh();

         public Refresh getRefresh() {
            return this.refresh;
         }

         public static class Refresh {

            /**
             * Cluster topology refresh period.
             */
            private Duration period;

            /**
             * Whether adaptive topology refreshing using all available refresh
             * triggers should be used.
             */
            private boolean adaptive;

            public Duration getPeriod() {
               return this.period;
            }

            public void setPeriod(Duration period) {
               this.period = period;
            }

            public boolean isAdaptive() {
               return this.adaptive;
            }

            public void setAdaptive(boolean adaptive) {
               this.adaptive = adaptive;
            }

         }

      }

   }

}

@Import({ LettuceConnectionConfiguration.class})

LettuceConnectionConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {

   LettuceConnectionConfiguration(RedisProperties properties,
         ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
         ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
      super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
   }

   @Bean(destroyMethod = "shutdown")
   @ConditionalOnMissingBean(ClientResources.class)
   DefaultClientResources lettuceClientResources() {
      return DefaultClientResources.create();
   }

//这里LettuceConnectionFactory就是RedisConnectionFactory的实现类,在注入RedisTemplate的时候需要
   @Bean
   @ConditionalOnMissingBean(RedisConnectionFactory.class)
   LettuceConnectionFactory redisConnectionFactory(
         ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
         ClientResources clientResources) throws UnknownHostException {
      LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
            getProperties().getLettuce().getPool());
      return createLettuceConnectionFactory(clientConfig);
   }

   private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
      if (getSentinelConfig() != null) {
         return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
      }
      if (getClusterConfiguration() != null) {
         return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
      }
      return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
   }

   private LettuceClientConfiguration getLettuceClientConfiguration(
         ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
         ClientResources clientResources, Pool pool) {
      LettuceClientConfigurationBuilder builder = createBuilder(pool);
      applyProperties(builder);
      if (StringUtils.hasText(getProperties().getUrl())) {
         customizeConfigurationFromUrl(builder);
      }
      builder.clientOptions(initializeClientOptionsBuilder().timeoutOptions(TimeoutOptions.enabled()).build());
      builder.clientResources(clientResources);
      builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
      return builder.build();
   }

   private LettuceClientConfigurationBuilder createBuilder(Pool pool) {
      if (pool == null) {
         return LettuceClientConfiguration.builder();
      }
      return new PoolBuilderFactory().createBuilder(pool);
   }

   private LettuceClientConfigurationBuilder applyProperties(
         LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
      if (getProperties().isSsl()) {
         builder.useSsl();
      }
      if (getProperties().getTimeout() != null) {
         builder.commandTimeout(getProperties().getTimeout());
      }
      if (getProperties().getLettuce() != null) {
         RedisProperties.Lettuce lettuce = getProperties().getLettuce();
         if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
            builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout());
         }
      }
      if (StringUtils.hasText(getProperties().getClientName())) {
         builder.clientName(getProperties().getClientName());
      }
      return builder;
   }

   private ClientOptions.Builder initializeClientOptionsBuilder() {
      if (getProperties().getCluster() != null) {
         ClusterClientOptions.Builder builder = ClusterClientOptions.builder();
         Refresh refreshProperties = getProperties().getLettuce().getCluster().getRefresh();
         Builder refreshBuilder = ClusterTopologyRefreshOptions.builder();
         if (refreshProperties.getPeriod() != null) {
            refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());
         }
         if (refreshProperties.isAdaptive()) {
            refreshBuilder.enableAllAdaptiveRefreshTriggers();
         }
         return builder.topologyRefreshOptions(refreshBuilder.build());
      }
      return ClientOptions.builder();
   }

   private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
      ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
      if (connectionInfo.isUseSsl()) {
         builder.useSsl();
      }
   }

   
   private static class PoolBuilderFactory {

      LettuceClientConfigurationBuilder createBuilder(Pool properties) {
         return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));
      }

      private GenericObjectPoolConfig<?> getPoolConfig(Pool properties) {
         GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
         config.setMaxTotal(properties.getMaxActive());
         config.setMaxIdle(properties.getMaxIdle());
         config.setMinIdle(properties.getMinIdle());
         if (properties.getTimeBetweenEvictionRuns() != null) {
            config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis());
         }
         if (properties.getMaxWait() != null) {
            config.setMaxWaitMillis(properties.getMaxWait().toMillis());
         }
         return config;
      }

   }

}

到这里为止,springboot是如何自动装配redis组件的,已经讲的很清楚了

posted on 2022-03-08 23:33  路仁甲  阅读(109)  评论(0编辑  收藏  举报