在微服務概念興起的今天,不少公司轉型使用微服務做爲架構。在技術選型上Spring Cloud 是很是好的選擇,它提供了一站式的分佈式系統解決方案,而Spring Cloud中的每一個組件都是基於Spring Boot構建的,Spring Boot提供J2EE一站式解決方案,具備如下優勢:java
模塊 | java文件數 |
---|---|
spring-boot | 551 |
spring-boot-actuator | 423 |
spring-boot-autoconfigure | 783 |
spring-boot-devtools | 169 |
spring-boot-cli | 180 |
spring-boot-tools | 355 |
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的依賴圖分析以下:react
在spring-boot-build 的pom中,咱們能夠看到:git
<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。github
下面咱們簡要介紹一下SpringBoot子modules。web
好比:redis
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
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。它的項目依賴圖以下:mongodb
能夠看出,這些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的依賴樹,以下圖:shell
SpringBoot 自動配置主要經過 @EnableAutoConfiguration, @Conditional, @EnableConfigurationProperties 或者 @ConfigurationProperties 等幾個註解來進行自動配置完成的。
@EnableAutoConfiguration 開啓自動配置,主要做用就是調用 Spring-Core 包裏的 loadFactoryNames(),將 autoconfig 包裏的已經寫好的自動配置加載進來。
@Conditional 條件註解,經過判斷類路徑下有沒有相應配置的 jar 包來肯定是否加載和自動配置這個類。
@EnableConfigurationProperties的做用就是,給自動配置提供具體的配置參數,只須要寫在application.properties 中,就能夠經過映射寫入配置類的 POJO 屬性中。數據庫
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
每次咱們直接直接啓動這個啓動類,SpringBoot就啓動成功了,而且幫咱們配置了好多自動配置類。其中最重要是 @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聲明時,引用了三個重要的註解:
之前咱們須要配置的東西,Spring Boot幫咱們自動配置;@EnableAutoConfiguration告訴SpringBoot開啓自動配置功能;這樣自動配置才能生效;
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
@EnableAutoConfiguration聲明時,引用了兩個重要的註解:
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放在項目的最高級中。
能夠從圖中看出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";外部文件。這個外部文件,有不少自動配置的
類。以下:
咱們以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類呢?
而後直接在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
在這麼多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指定的條件成立,纔給容器中添加組件,配置裏的內容纔會生效
下面咱們以@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只要把握這幾點: