前述
在解析spring boot starter自動裝配機制之前,我們先來回顧一下web工程是如何搭建的:
-
gradle/pom文件中引入項目依賴jar包;
-
配置web.xml,Servlet配置,攔截器設置,Listener配置…;
-
數據庫連接,配置spring事務;
-
配置視圖解析器;
-
開啓註解,自動掃描功能
-
配置完成後部署tomcat,調試等…
…
在搭建這些環境的時候是非常耗時間耗精力的,而且有時還會缺斤少兩,連蹦bug…
而有了springboot之後,所有的環境配置就會非常便捷。
自動配置原理
我們先來搭建一個最簡單的springboot工程,如下:
application.properties文件如下:
server.port=8080
這樣,我們就搭好了一個最簡單的springboot工程,隨後就可以啓動了。
我們或許有點好奇,明明自己就只配置了一個端口號,剩下的自己啥也沒配,爲啥這個web程序就可以啓動了??爲什麼???
接下來,我們來分析以下,以下是我的思路分析過程(爲了防止後面看着看着就迷路,我先來個大綱):
- SpringbootApplication啓動類的註解@SpringBootApplication,即程序入口;
- @EnableAutoConfiguration註解(bean的裝配與加載)重點;
- @ComponentScan註解(bean的掃描與發現)
程序入口
我們先來看一下主程序入口:
@SpringBootApplication
public class SpringbootStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootStudyApplication.class, args);
}
}
如以上代碼,很常見,是一個main方法啓動類,與javaEE程序不同的是,它被一個@springbootApplication註解修飾,那麼這個註解何德何能,可以讓一個web應用跑起來呢?
走進@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
我們暫時不要關注這個類的方法,我們先看看這個類上的幾個註解:
- @SpringBootConfiguration:在這裏可以理解爲是一個@Configuration註解;
- @EnableAutoConfiguration:自動裝配的核心!!用作bean的裝配與加載
- @ComponentScan:掃描包,用作bean的發現(此處掃描當前main下面app的所在包及其下屬包)
@EnableAutoConfiguration
我們來看看它究竟做了什麼事:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
點進去之後可以發現有兩個比較重要的註解:
- @AutoConfigurationPackage:其作用是自動配置的包;
- @Import:他的作用是導入需要自動配置的組件;
@AutoConfigurationPackage如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
...
}
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
以上代碼的作用就是:
加載啓動類所在的包下的主類與子類的所有組件註冊到spring容器。
那麼問題來了,要蒐集並註冊到spring容器的那些beans來自哪裏?
我們的@import註解就排上用場了,我們也可以細心的發現,@import註解裏包括了一個AutoConfigurationImportSelector.class
類,那麼這個類具體是幹嘛的呢?
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
//選擇導入
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
getAutoConfigurationEntry()如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
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 = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
//候選者方法如下
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//掃描所有jar包類路徑下 META‐INF/spring.factories 所有EnableAutoConfiguration的值 把掃描到的這些文件的內容包裝成properties對象
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;
}
//獲取被@EnableAutoConfiguration修飾的類
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
META‐INF/spring.factories 所有EnableAutoConfiguration的值:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
...略
看了以上代碼,我們可以發現``AutoConfigurationImportSelector`幹了這麼幾件事:
- 掃描所有jar包類路徑下 META‐INF/spring.factories ;
- 把掃描到的這些文件的內容包裝成properties對象 ;
- 從properties中獲取到EnableAutoConfiguration.class類(類名)對應的值。
來小結一下:
@EnableAutoConfiguration這個註解幹了那些事?
- 通過@import註解,引入了
AutoConfigurationImportSelector
這個類; AutoConfigurationImportSelector
這個類掃描了所有被@EnableAutoConfiguration修飾的類;- 通過上面掃描到的類,通過@AutoConfigurationPackage將其掃描到的類裝配到容器中;
接下來我們來看一下被@EnableAutoConfiguration修飾的類長什麼樣:
以下解析是以
HttpEncodingAutoConfiguration
爲例解釋自動裝配的原理的。
@Configuration(proxyBeanMethods = false)//表示這是一個配置類,以前編寫的配置文件一樣,也可以給容器中添加組件
@EnableConfigurationProperties(ServerProperties.class)//啓動指定類的 ConfigurationProperties功能;將配置文件中對應的值和HttpEncodingProperties綁定起來;並把 HttpEncodingProperties加入到ioc容器中
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)//Spring底層@Conditional註解(Spring註解版),根據不同的條件,如果 滿足指定的條件,整個配置類裏面的配置就會生效; 判斷當前應用是否是web應用,如果是,當前配置類生效
@ConditionalOnClass(CharacterEncodingFilter.class)//判斷當前項目有沒有這個類 CharacterEncodingFilter;SpringMVC中進行亂碼解決的過濾器;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true) //判斷配置文件中是否存在某個配置 spring.http.encoding.enabled;如果不存在,判斷也是成立的 //即使我們配置文件中不配置pring.http.encoding.enabled=true,也是默認生效的;
public class HttpEncodingAutoConfiguration {
//與SpringBoot的配置文件映射
private final Encoding properties;
//只有一個有參構造器的情況下,參數的值就會從容器中拿,與上述的@EnableConfigurationProperties引進來的文件相呼應。
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean //給容器中添加一個組件,這個組件的某些值需要從properties中獲取
@ConditionalOnMissingBean //判斷容器沒有這個組件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
...略
}
}
ServerProperties文件如下
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind.
*/
private InetAddress address;
@NestedConfigurationProperty
private final ErrorProperties error = new ErrorProperties();
...略
}
首先我們來分析一下:
-
@Configuration與@bean的結合使用相當於是:創建一個基於java代碼的配置類,可以用來替代相應的xml配置文件。
-
@EnableConfigurationProperties:引入外部文件,與springboot的配置文件形成映射。
-
其餘就是各種@condition條件,達到條件之後,這個類才生效;
下面是一些condition條件的具體應用:
@Conditional擴展註解 作用(判斷是否滿足當前指定條件) @ConditionalOnJava 系統的java版本是否符合要求 @ConditionalOnBean 容器中存在指定Bean; @ConditionalOnMissingBean 容器中不存在指定Bean; @ConditionalOnExpression 滿足SpEL表達式指定 @ConditionalOnClass 系統中有指定的類 @ConditionalOnMissingClass 系統中沒有指定的類 @ConditionalOnSingleCandidate 容器中只有一個指定的Bean,或者這個Bean是首選Bean @ConditionalOnProperty 系統中指定的屬性是否有指定的值 @ConditionalOnResource 類路徑下是否存在指定資源文件 @ConditionalOnWebApplication 當前是web環境 @ConditionalOnNotWebApplication 當前不是web環境 @ConditionalOnJndi JNDI存在指定項
@ComponentScan註解(bean的掃描與發現)
這塊就是掃描springboot app程序的所在包,好像也沒什麼可說的。
總結
springboot的自動狀態,我們大致可總結爲這麼幾個步驟:
-
主程序中的@springbootApplication修飾;
-
在@EableAutoConfiguration註解中(實現bean的裝配與加載),實現了以下兩步:
- @Import引入的AutoCongurationImportSelector負責找到需要導入bean;
- @AutoConfigurationPackage負責將其bean加載到容器中;
被@EableAutoConfiguration修飾的註解:
- 通過@Conguration與@bean使用java的方式配置bean;
- @EnableConfigurationProperties引入配置文件屬性,與spring boot的配置文件相映射(這也是爲什麼你在application.yml裏面配置文件的時候可以生效);
- @Conditionxx條件,滿足則存在;
-
@CompomentScan掃描包。(實現bean的掃描與發現)