一、Spring Boot整合第三方组件(Redis为例)

  1、加依赖

<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  2、加配置

spring.redis.host=127.0.0.1
spring.redis.password=
spring.redis.port=6379
spring.redis.jedis.pool.max-idle=200
spring.redis.jedis.pool.max-active=1024
spring.redis.jedis.pool.max-wait=1000

  3、加注解(看各自的组件需要,比如整合Mybatis就需要,Redis不需要)

二、Spring Boot自动装配组件原理

  1、@SpringBootApplication注解

  2、AutoConfigurationImportSelector分析

  ① selectImports方法:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
     //获取自动装配的入口
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

  ② getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata)方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
    /**
* 获取候选的配置类,主要是到classpath下面的\META-INF\spring.factories中,
* 取key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置类
*/
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
/**去除重复的配置类,若我们自己写的starter 可能存主重复的*/
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
/**根据maven依赖导入的启动器过滤出需要导入的配置类*/
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

  ③ getCandidateConfigurations(annotationMetadata, attributes)方法:

/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//去spring.factories中去查询EnableAutoConfiguration类
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;
}

  ④ SpringFactoriesLoader.loadFactoryNames方法:

/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
//去spring.factories 中去查询EnableAutoConfiguration类
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

  ⑤ loadSpringFactories(classLoader)方法:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
} try {
/**
* The location to look for factories. Can be present in multiple JAR files.
* FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
*/
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 factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

  spring.factories如下:

  3、RedisAutoConfiguration分析

  导入了三个组件:RedisTemplate,StringRedisTemplate,JedisConnectionConfiguration

  ① RedisTemplate组件(默认采用java序列化,所以一般要自定义该组件):

@Bean
//当没有Spring容器中没有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;
}

  自定义RedisTemplate组件,主要修改序列化方式,如下:

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

  ② StringRedisTemplate(默认采用java序列化,所以一般要自定义该组件):

@Bean
//当没有Spring容器中没有StringRedisTemplate类型的Bean的时候才加载
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

  ③ JedisConnectionConfiguration组件:

/**
* Redis connection configuration using Jedis.
*/
@Configuration
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration { /**
* redis配置
*/
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;
} /**
* Jedis连接工厂
* @return
* @throws UnknownHostException
*/
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
return createJedisConnectionFactory();
} /**
* Jedis连接工厂
* @return
*/
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.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRunsMillis(pool.getTimeBetweenEvictionRuns().toMillis());
}
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));
} }

  redis配置类:

@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; 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 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; 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;
} } /**
* 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; 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;
} } }

三、Spring Boot自动装配流程图

四、总结

  本文以Spring Boot整合Redis为例,把Spring Boot整合第三方组件的自动装配原理进行了解析,对应其他的第三方组件,比如整合Mybatis,套路是一样的。

Spring Boot系列(二):Spring Boot自动装配原理解析的更多相关文章

  1. Spring Boot系列二 Spring @Async异步线程池用法总结

    1. TaskExecutor Spring异步线程池的接口类,其实质是java.util.concurrent.Executor Spring 已经实现的异常线程池: 1. SimpleAsyncT ...

  2. SpringBoot系列二:SpringBoot自动配置原理

    主程序类的注解 @SpringBootApplication 注解,它其实是个组合注解,源码如下: @Target({ElementType.TYPE}) @Retention(RetentionPo ...

  3. SpringBoot自动装配原理解析

    本文包含:SpringBoot的自动配置原理及如何自定义SpringBootStar等 我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序: @SpringBoo ...

  4. Spring Boot系列(二) Spring Boot 之 REST

    Rest (Representational Stat Transer) 是一种软件架构风格. 基础理论 架构特性 性能 可伸缩 简化的统一接口 按需修改 组件通信透明 可移植 可靠性 架构约束 C/ ...

  5. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  6. Spring Boot 自动装配原理

    Spring Boot 自动装配原理 Spring Boot 在启动之前还有一系列的准备工作,比如:推断 web 应用类型,设置初始化器,设置监听器,启动各种监听器,准备环境,创建 applicati ...

  7. Eureka 系列(03)Spring Cloud 自动装配原理

    Eureka 系列(03)Spring Cloud 自动装配原理 [TOC] 0. Spring Cloud 系列目录 - Eureka 篇 本文主要是分析 Spring Cloud 是如何整合 Eu ...

  8. Spring Boot干货系列:(三)启动原理解析

    Spring Boot干货系列:(三)启动原理解析 2017-03-13 嘟嘟MD 嘟爷java超神学堂 前言 前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说 ...

  9. Spring系列7:`autowire`自动装配怎么玩

    回顾 前几篇我们介绍各种依赖依赖注入,都是显式指定的,配置明确但同时也有些繁杂和重复."很多发明的出发点,都是为了偷懒,懒人是推动社会进步的原动力".Spring 提供了自动注入依 ...

随机推荐

  1. Spark入门(第1讲)

    一.Spark是什么 引用官方文档的一句话 Apache Spark is a unified analytics engine for large-scale data processing. Ap ...

  2. Centos 7 静态IP设置

    1.编辑 ifcfg-eth0 文件,vim 最小化安装时没有被安装,需要自行安装不描述. # vim /etc/sysconfig/network-scripts/ifcfg-eth0 2.修改如下 ...

  3. django-rest-framework-源码解析001-整体框架

    简介 Django Rest Framework是一个强大且灵活的工具包,主要用以构建RESTful风格的Web API. Django REST Framework(简称DRF)可以在Django的 ...

  4. Microsoft Cloud App Security 微软的云应用安全

    1.概述 微软2015年收购的一家云安全创业公司 Adallom 正式推出产品,同时更名为微软 Cloud App Security.Adallom 成立于 2012年,是一家 SaaS 云安全公司, ...

  5. 使用types库修改函数

    import types class ppp: pass p = ppp()#p为ppp类实例对象 def run(self): print("run函数") r = types. ...

  6. Python os.statvfs() 方法

    概述 os.statvfs() 方法用于返回包含文件描述符fd的文件的文件系统的信息.高佣联盟 www.cgewang.com 语法 statvfs()方法语法格式如下: os.statvfs([pa ...

  7. PHP strtolower() 函数

    实例 把所有字符转换为小写: <?php高佣联盟 www.cgewang.comecho strtolower("Hello WORLD.");?> 定义和用法 str ...

  8. LVS-DR:搭建HTTP和HTTPS负载均衡集群

    目录 LVS-DR实战:搭建HTTP和HTTPS负载均衡集群 1. 搭建lvs-dr模式的http负载集群 1.1 LVS上配置IP 1.2 RS上配置arp内核参数 1.3 RS上配置VIP 1.4 ...

  9. Win10系统安装MySQL Workbench 8

    系统:Window10 专业版 MySQL Workbench 8.0.19 下载地址:https://dev.mysql.com/downloads/workbench/8.0.html 点击Dow ...

  10. maven中的陌生单词

    有个单词记不住啊: artifact:人工制品,手工艺品,加工品; 石器; 词根:fac,fact,fect,fic,fig=make,do,表示“做,制作”   因此 art i fact 意思很好 ...