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个特点
- 继承该接口的ImportSelector会在最后执行
- 如果定义了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入手分析
那我们仔细分析
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";
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组件的,已经讲的很清楚了