在微服務概念興起的今天,很多公司轉型使用微服務作為架構。在技術選型上Spring Cloud 是非常好的選擇,它提供了一站式的分布式系統解決方案,而Spring Cloud中的每個組件都是基於Spring Boot構建的,Spring Boot提供J2EE一站式解決方案,具有以下優點:
- 快速創建獨立運行的Spring項目以及與主流框架集成
- 使用嵌入式的Servlet容器,應用無需打成WAR包
- starters自動依賴與版本控制
- 大量的自動配置,簡化開發,也可修改默認值
- 無需配置XML,無代碼生成,開箱即用
- 准生產環境的運行時應用監控
- 與雲計算的天然集成
一、Spring Boot的核心組件模塊
模塊 | java文件數 |
---|---|
spring-boot | 551 |
spring-boot-actuator | 423 |
spring-boot-autoconfigure | 783 |
spring-boot-devtools | 169 |
spring-boot-cli | 180 |
spring-boot-tools | 355 |
從上面的java文件數量大致可以看出,SpringBoot技術框架的核心組成部分:
spring-boot-autoconfigure spring-boot spring-boot-tools
我們把SpringBoot源碼導入IntelliJ IDEA,查看artifact的全部依賴關系。
IDEA有個Maven Projects窗口,一般在右側能夠找到,如果沒有可以從菜單欄打開:View>Tool Windows>Maven Projects;
選擇要分析的maven module(idea的module相當於eclipse的project),右擊show dependencies,會出來該module的全部依賴關系圖,非常清晰細致。
例如,spring-boot-starter-freemarker的依賴圖分析如下:
在spring-boot-build 的pom中,我們可以看到:
<modules> <module>spring-boot-dependencies</module> <module>spring-boot-parent</module> <module>spring-boot-tools</module> <module>spring-boot</module> <module>spring-boot-test</module> <module>spring-boot-autoconfigure</module> <module>spring-boot-test-autoconfigure</module> <module>spring-boot-actuator</module> <module>spring-boot-devtools</module> <module>spring-boot-docs</module> <module>spring-boot-starters</module> <module>spring-boot-actuator-docs</module> <module>spring-boot-cli</module> </modules>
其中,在spring-boot-dependencies中,SpringBoot項目維護了一份龐大依賴。這些依賴的版本都是經過實踐,測試通過,不會發生依賴沖突的。就這樣一個事情,就大大減少了Spring開發過程中,出現jar包沖突的概率。spring-boot-parent依賴spring-boot-dependencies。
下面我們簡要介紹一下SpringBoot子modules。
- spring-boot:SpringBoot核心工程。
- spring-boot-starters:是SpringBoot的啟動服務工程。
- spring-boot-autoconfigure:是SpringBoot實現自動配置的核心工程。
- spring-boot-actuator:提供SpringBoot應用的外圍支撐性功能。
比如:
- Endpoints,SpringBoot應用狀態監控管理
- HealthIndicator,SpringBoot應用健康指示表
- 提供metrics支持
- 提供遠程shell支持
- spring-boot-tools:提供了SpringBoot開發者的常用工具集。諸如,spring-boot-gradle-plugin,spring-boot-maven-plugin就是這個工程里面的。
- spring-boot-cli:是Spring Boot命令行交互工具,可用於使用Spring進行快速原型搭建。你可以用它直接運行Groovy腳本。如果你不喜歡Maven或Gradle,Spring提供了CLI(Command Line Interface)來開發運行Spring應用程序。你可以使用它來運行Groovy腳本,甚至編寫自定義命令。
二、Spring Boot Starters
Spring boot中的starter概念是非常重要的機制,能夠拋棄以前繁雜的配置,統一集成進starter,應用者只需要引入starter jar包,spring boot就能自動掃描到要加載的信息。
starter讓我們擺脫了各種依賴庫的處理,需要配置各種信息的困擾。Spring Boot會自動通過classpath路徑下的類發現需要的Bean,並織入bean。
例如,如果你想使用Spring和用JPA訪問數據庫,你只要依賴 spring-boot-starter-data-jpa 即可。
目前,github上spring-boot項目的最新的starter列表spring-boot/spring-boot-starters如下:
spring-boot-starter spring-boot-starter-activemq spring-boot-starter-actuator spring-boot-starter-amqp spring-boot-starter-aop spring-boot-starter-artemis spring-boot-starter-batch spring-boot-starter-cache spring-boot-starter-cloud-connectors spring-boot-starter-data-cassandra spring-boot-starter-data-couchbase spring-boot-starter-data-elasticsearch spring-boot-starter-data-jpa spring-boot-starter-data-ldap spring-boot-starter-data-mongodb spring-boot-starter-data-mongodb-reactive spring-boot-starter-data-neo4j spring-boot-starter-data-redis spring-boot-starter-data-rest spring-boot-starter-data-solr spring-boot-starter-freemarker spring-boot-starter-groovy-templates spring-boot-starter-hateoas spring-boot-starter-integration spring-boot-starter-jdbc spring-boot-starter-jersey spring-boot-starter-jetty spring-boot-starter-jooq spring-boot-starter-jta-atomikos spring-boot-starter-jta-bitronix spring-boot-starter-jta-narayana spring-boot-starter-log4j2 spring-boot-starter-logging spring-boot-starter-mail spring-boot-starter-mobile spring-boot-starter-mustache spring-boot-starter-parent spring-boot-starter-reactor-netty spring-boot-starter-security spring-boot-starter-social-facebook spring-boot-starter-social-linkedin spring-boot-starter-social-twitter spring-boot-starter-test spring-boot-starter-thymeleaf spring-boot-starter-tomcat spring-boot-starter-undertow spring-boot-starter-validation spring-boot-starter-web spring-boot-starter-web-services spring-boot-starter-webflux spring-boot-starter-websocket
這是Spring Boot的核心啟動器,包含了自動配置、日志和YAML。它的項目依賴圖如下:

可以看出,這些starter只是配置,真正做自動化配置的代碼的是在spring-boot-autoconfigure里面。同時spring-boot-autoconfigure依賴spring-boot工程,這個spring-boot工程是SpringBoot的核心。
SpringBoot會基於你的classpath中的jar包,試圖猜測和配置您可能需要的bean。
例如,如果你的classpath中有tomcat-embedded.jar,你可能會想要一個TomcatEmbeddedServletContainerFactory Bean (SpringBoot通過獲取EmbeddedServletContainerFactory來啟動對應的web服務器。常用的兩個實現類是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory)。
其他的所有基於Spring Boot的starter都依賴這個spring-boot-starter。比如說spring-boot-starter-actuator的依賴樹,如下圖:
三、SpringBoot 自動配置原理
SpringBoot 自動配置主要通過 @EnableAutoConfiguration, @Conditional, @EnableConfigurationProperties 或者 @ConfigurationProperties 等幾個注解來進行自動配置完成的。
@EnableAutoConfiguration 開啟自動配置,主要作用就是調用 Spring-Core 包里的 loadFactoryNames(),將 autoconfig 包里的已經寫好的自動配置加載進來。
@Conditional 條件注解,通過判斷類路徑下有沒有相應配置的 jar 包來確定是否加載和自動配置這個類。
@EnableConfigurationProperties的作用就是,給自動配置提供具體的配置參數,只需要寫在application.properties 中,就可以通過映射寫入配置類的 POJO 屬性中。
1、SpringBoot啟動主程序類
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
每次我們直接直接啟動這個啟動類,SpringBoot就啟動成功了,並且幫我們配置了好多自動配置類。其中最重要是 @SpringBootApplication 這個注解,我們點進去看一下。
2、@SpringBootApplication注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {
@SpringBootApplication聲明時,引用了三個重要的注解:
- @SpringBootConfiguration : SpringBoot的配置類,標注在某個類上,表示這是一個Spring Boot的配置類
- @EnableAutoConfiguration: 開啟自動配置類,SpringBoot的精華所在。
- @ComponentScan包掃描
以前我們需要配置的東西,Spring Boot幫我們自動配置;@EnableAutoConfiguration告訴SpringBoot開啟自動配置功能;這樣自動配置才能生效;
-
@EnableAutoConfiguration注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
@EnableAutoConfiguration聲明時,引用了兩個重要的注解:
- @AutoConfigurationPackage:自動配置包
- @Import: 導入自動配置的組件
-
@AutoConfigurationPackage注解
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); }
它其實是注冊了一個Bean的定義。
new PackageImport(metadata).getPackageName(),它其實返回了當前主程序類的同級以及子級的包組件。
以上圖為例,DemoApplication是和demo包同級,但是demo2這個類是DemoApplication的父級,和example包同級
也就是說,DemoApplication啟動加載的Bean中,並不會加載demo2,這也就是為什么,我們要把DemoApplication放在項目的最高級中。
- @Import(AutoConfigurationImportSelector.class)注解
可以從圖中看出AutoConfigurationImportSelector 繼承了 DeferredImportSelector繼承了ImportSelector,ImportSelector有一個方法為:selectImports。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); }
可以看到第九行,它其實是去加載public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。這個外部文件,有很多自動配置的
類。如下:
3、如何自定義自己的Bean
我們以RedisTemplate為例:
@Configuration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
我們每次在Spring中使用Redis,都會使用到RedisTemplate這個工具類,但是他默認給我們返回的這個工具類,可能不是很符合我們的要求。比如:我們想要開啟事務,或者想要改變它默認的序列化。
- 這時候該如何去做呢?
根據前面的分析,只要我們在容器中放入一個RedisTemplate Bean即可。
1 @Bean("redisTemplate") 2 public RedisTemplate<Object, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) { 3 RedisTemplate<Object, Object> template = new RedisTemplate<>(); 4 template.setConnectionFactory(redisConnectionFactory); 5 // 修改序列化為Jackson
6 template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer()); 7 // 開啟事務
8 template.setEnableTransactionSupport(true); 9 return template; 10 }
我們自己定義我們的RedisTemplate模板,修改序列化,開啟事務等操作。
我們將我們自己的Bean加入到IoC容器中以后,他就會默認的覆蓋掉原來的RedisTemplate,達到定制的效果。
我們在以Kafka為例:
假設我們想要消費的對象不是字符串,而是一個對象呢?比如Person對象,或者其他Object類呢?
- 我們首先去查找KafkaAutoConfiguration(xxxAutoConfiguration),看看是否有關於Serializer屬性的配置
- 假設沒有我們就去KafkaProperties文件查找是否有Serializer的配置
然后直接在application.properties修改默認序列化就好,連Bean都不需要自己重寫。
類似這種,可以使用Spring提供的Json序列化,也可以自動使用第三方框架提供的序列化,比如Avro, Protobuff等
spring.kafka.producer.key-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.consumer.key-deserializer=com.example.common.MyJson spring.kafka.consumer.value-deserializer=com.example.common.MyJson
-
HttpEncodingAutoConfiguration
在這么多xxxxAutoConfiguration中,我們以HttpEncodingAutoConfiguration(Http自動編碼)為例
1 @Configuration 2 //表示這是一個配置類,以前編寫的配置文件一樣,也可以給容器中添加組件 3 4 @EnableConfigurationProperties(HttpEncodingProperties.class) 5 //啟動指定類的ConfigurationProperties功能;將配置文件中對應的值和HttpEncodingProperties綁定起來;並把HttpEncodingProperties加入到ioc容器中 6 7 @ConditionalOnWebApplication 8 //Spring底層@Conditional注解(Spring注解版),根據不同的條件,如果滿足指定的條件,整個配置類里面的配置就會生效;這里是判斷當前應用是否是web應用,如果是,當前配置類生效 9 10 @ConditionalOnClass(CharacterEncodingFilter.class) 11 //判斷當前項目有沒有這個類CharacterEncodingFilter;SpringMVC中進行亂碼解決的過濾器; 12 13 @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) 14 //判斷配置文件中是否存在某個配置 spring.http.encoding.enabled;如果不存在,判斷也是成立的 15 //即使我們配置文件中不配置pring.http.encoding.enabled=true,也是默認生效的; 16 17 public class HttpEncodingAutoConfiguration { 18 19 //他已經和SpringBoot的配置文件映射了 20 private final HttpEncodingProperties properties; 21 22 //只有一個有參構造器的情況下,參數的值就會從容器中拿 23 public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) { 24 this.properties = properties; 25 } 26 27 @Bean 28 //給容器中添加一個組件,這個組件的某些值需要從properties中獲取 29 @ConditionalOnMissingBean(CharacterEncodingFilter.class) 30 //判斷容器沒有這個組件 31 public CharacterEncodingFilter characterEncodingFilter() { 32 CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); 33 filter.setEncoding(this.properties.getCharset().name()); 34 filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); 35 filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); 36 return filter; 37 } 38 } 39 ....... 40 }
通過上面的類的注解可以看到,通過使用@EnableConfigurationProperties,可以把配置文件中的屬性與HttpEncodingProperties類綁定起來並且加入到IOC容器中,進入HttpEncodingProperties類,可以看到他是通過@ConfigurationProperties 注解把配置文件中的spring.http.encoding值與該類的屬性綁定起來的。
@ConfigurationProperties(prefix = "spring.http.encoding") public class HttpEncodingProperties
同時我們可以注意到上面的類中使用了@ConditionalOnClass與@ConditionalOnWebApplication注解,這兩個都是@Conditional的派生注解,作用是必須是@Conditional指定的條件成立,才給容器中添加組件,配置里的內容才會生效
-
Conditional注解
下面我們以@ConditionalOnClass為例,來分析一下他的源代碼。
@Conditional(OnClassCondition.class) public @interface ConditionalOnClass {
進入OnClassCondition類,查看他的類繼承信息,可以看到他繼承SpringBootCondition類,SpringBootCondition又實現了Condition接口
OnClassCondition又override了SpringBootCondition的getMatchOutcome方法,該方法會返回條件匹配結果。
getMatchOutcome方法源代碼如下:
public ConditionOutcome getMatchOutcome(ConditionContext context, ..... List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader); ...... List<String> present = getMatches(onMissingClasses, MatchType.PRESENT, classLoader); }
可以看到getMatchOutcome中主要調用了getMatches方法
進入getMatches方法
private List<String> getMatches(Collection<String> candidates, MatchType matchType, ClassLoader classLoader) { List<String> matches = new ArrayList<String>(candidates.size()); for (String candidate : candidates) { if (matchType.matches(candidate, classLoader)) { matches.add(candidate); } } return matches; }
getMatches又調用了MatchType的matches方法。
private enum MatchType { PRESENT { @Override public boolean matches(String className, ClassLoader classLoader) { return isPresent(className, classLoader); } }, MISSING { @Override public boolean matches(String className, ClassLoader classLoader) { return !isPresent(className, classLoader); } }; private static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } try { forName(className, classLoader); return true; } catch (Throwable ex) { return false; } } private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException { if (classLoader != null) { return classLoader.loadClass(className); } return Class.forName(className); } public abstract boolean matches(String className, ClassLoader classLoader); }
進入MatchType類中,可以看到他有兩個枚舉類,進一步看枚舉類中的matches的源代碼可以發現最終是利用loadClass以及forName 方法,判斷類路徑下有沒有這個指定的類。
下面列舉出的是Spring Boot對@Conditional的擴展注解。
用好SpringBoot只要把握這幾點:
- SpringBoot啟動會加載大量的自動配置類
- 所要做的就是我們需要的功能SpringBoot有沒有幫我們寫好的自動配置類:
- 如果有就再來看這個自動配置類中到底配置了哪些組件,Springboot自動配置類里邊只要我們要用的組件有,我們就不需要再來配置了,但是如果說沒有我們所需要的組件,那么我們就需要自己來寫一個配置類來把我們相應的組件配置起來。
- 給容器中自動配置類添加組件的時候,會從properties類中獲取某些屬性,而這些屬性我們就可以在配置文件指定這些屬性