一、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. 在 Docker 搭建 Maven 私有库

    在 Docker 搭建 Maven 私有库 小引 If you are developing software without a repository manager you are likely ...

  2. django-rest-framework-源码解析004-三大验证(认证/权限/限流)

    三大验证模块概述 在DRF的APIView重写的dispatch方法中,  self.initial(request, *args, **kwargs) 这句话就是执行三大验证的逻辑, 点进去可以看到 ...

  3. 00_01_使用Parallels Desktop创建WindosXP虚拟机

    打开paralles软件,选择文件->新建 继续 选择手动选择,之后勾选没有指定源也继续 选择要创建的操作系统(这里以XP为例,其他的windows系统安装基本都差不多) 根据需要选择,这里选择 ...

  4. nginx静态资源防盗链

    含义: 用于阻止 Referer 头字段为无效值的请求访问站点.需记住的是,使用适当的 Referer 字段值来伪造请求非常容易,因此本模块的预期目的不是要彻底阻止此类请求,而是阻止常规浏览器发送的大 ...

  5. C#后台实现在Grid标签中动态新增CheckBox标签(WPF中)

    页面代码 <Grid Margin="45,0,10,0" > <Grid.RowDefinitions> <RowDefinition Height ...

  6. luogu P2607 [ZJOI2008]骑士 tarjan dp

    LINK:骑士 本来是不打算写的 发现这道题在tarjan的时候有一个坑点 所以写出来记录一下. 可以发现图可能是不连通的 且一个连通块中是一个奇环树. 做法:类似tarjan找割点 然后把环给拉出来 ...

  7. Vue通过Blob对象实现导出Excel功能

    不同的项目有不同的导出需求,有些只导出当前所显示结果页面的表格进入excel,这个时候就有很多插件,比如vue-json-excel或者是Blob.js+Export2Excel.js来实现导出Exc ...

  8. async和await的使用总结 ~ 竟然一直用错了c#中的async和await的使用。。

    对于c#中的async和await的使用,没想到我一直竟然都有一个错误.. ..还是总结太少,这里记录下. 这里以做早餐为例 流程如下: 倒一杯咖啡. 加热平底锅,然后煎两个鸡蛋. 煎三片培根. 烤两 ...

  9. xml schema杂谈

    有一些场景,我们需要写xml,又需要对内容进行约束,比如智能输入某个值范围,只能写入固定值 这个时候我们就需要xml schema 这个,百度解释为 XML Schema 的作用是定义 XML 文档的 ...

  10. C++类、函数、指针

    1.初始化所有指针. 2. (1)指向常量的指针: (2)常量指针:指针本身为常量: 3.若循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环. 4.字符数组要注意字符串字面值 ...