一、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); } ......
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; ...... }
三、Spring Boot自動裝配流程圖

四、自定義starter啟動器
1、創建一個工程toby-spring-boot-autoconfigure,用來編寫啟動器的核心邏輯
① 在工程的pom.xml中添加依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure-processor</artifactId> <optional>true</optional> </dependency> </dependencies>
② 創建一個業務相關類:
/** * @desc: 具體業務bean * @author: toby */ public class TobyBean { /** * 是否開啟 */ private Boolean enabled; public TobyBean(Boolean enabled){ this.enabled = enabled; } /** * 檢測是否開啟toby功能 * @return */ public String checkTobyFunction(){ if(enabled){ return "open toby function"; } return "not open toby function"; } }
③ 創建一個Toby的屬性類:
/** * @desc: Configuration properties for Toby. * @author: toby */ @ConfigurationProperties(prefix = TobyProperties.TOBY_PREFIX) @Data public class TobyProperties { public static final String TOBY_PREFIX = "toby"; /** * 是否開啟 */ private Boolean enabled = true; }
④ 創建一個Toby自動裝配類:
/** * @desc: Toby自動裝配 * @author: toby */ @Configuration @EnableConfigurationProperties(TobyProperties.class) public class TobyAutoConfiguration implements InitializingBean { private final TobyProperties properties; public TobyAutoConfiguration(TobyProperties properties){ this.properties = properties; } @Bean public TobyBean tobyBean(){ TobyBean tobyBean = new TobyBean(properties.getEnabled()); return tobyBean; } @Override public void afterPropertiesSet() throws Exception { //TODO 初始化工作 } }
⑤在resources/META-INF下面新建一個spring.factories:

⑥ spring.factories內容如下:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.toby.spring.boot.autoconfigure.TobyAutoConfiguration
2、創建一個工程toby-spring-boot-starter,里面沒有邏輯,就依賴上面的toby-spring-boot-autoconfigure

3、測試自定義的starter啟動器
① 在需要用到該starter的工程的pom.xml中引入該starter依賴:
<!--自定義starter--> <dependency> <groupId>com.toby</groupId> <artifactId>toby-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
② 需要用到該starter的工程的配置文件中,根據需要設置開啟屬性值:

③ 使用該starter的業務bean(TobyBean):
@RestController public class WebController { @Autowired private TobyBean tobyBean; /** * 測試toby * @return */ @RequestMapping("/toby") public String toby() { return tobyBean.checkTobyFunction(); } /** * 測試web * @return */ @RequestMapping("/web") public String web() { return "this is spring boot web"; } }
④ 啟動工程,訪問http://localhost:8080/toby,顯示如下:

自此自定義Spring Boot Starter啟動器完成!!!
五、總結
本文以Spring Boot整合Redis為例,把Spring Boot整合第三方組件的自動裝配原理進行了解析,對應其他的第三方組件,比如整合Mybatis,套路是一樣的,根據自動裝配原理自定義了一個Spring Boot Starter啟動器。
