本文包含:SpringBoot的自動配置原理及如何自定義SpringBootStar等java
咱們知道,在使用SpringBoot的時候,咱們只須要以下方式便可直接啓動一個Web程序:web
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
和咱們以前使用普通Spring時繁瑣的配置相比簡直不要太方便,那麼你知道SpringBoot實現這些的原理麼面試
首先咱們看到類上方包含了一個@SpringBootApplication
註解spring
@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 {}; }
這個註解上邊包含的東西仍是比較多的,我們先看一下兩個簡單的熱熱身sql
@ComponentScan
註解@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
這個註解我們都是比較熟悉的,無非就是自動掃描並加載符合條件的Bean到容器中,這個註解會默認掃描聲明類所在的包開始掃描,例如: 類cn.shiyujun.Demo
類上標註了@ComponentScan
註解,則cn.shiyujun.controller
、cn.shiyujun.service
等等包下的類均可以被掃描到數組
這個註解一共包含如下幾個屬性:ide
basePackages:指定多個包名進行掃描 basePackageClasses:對指定的類和接口所屬的包進行掃 excludeFilters:指定不掃描的過濾器 includeFilters:指定掃描的過濾器 lazyInit:是否對註冊掃描的bean設置爲懶加載 nameGenerator:爲掃描到的bean自動命名 resourcePattern:控制可用於掃描的類文件 scopedProxy:指定代理是否應該被掃描 scopeResolver:指定掃描bean的範圍 useDefaultFilters:是否開啓對@Component,@Repository,@Service,@Controller的類進行檢測
@SpringBootConfiguration
註解這個註解更簡單了,它只是對Configuration
註解的一個封裝而已this
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
EnableAutoConfiguration
註解這個註解但是重頭戲了,SpringBoot號稱的約定大於配置,也就是本文的重點自動裝配的原理就在這裏了spa
@Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
簡單歸納一下,這個註解存在的意義就是:利用@Import
註解,將全部符合自動裝配條件的bean注入到IOC容器中,關於@Import
註解原理這裏就再也不闡述,感興趣的同窗能夠參考此篇文章:Spring @Import註解源碼解析.net
進入類AutoConfigurationImportSelector
,觀察其selectImports
方法,這個方法執行完畢後,Spring會把這個方法返回的類的全限定名數組裏的全部的類都注入到IOC容器中
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = this.getAttributes(annotationMetadata); List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this.filter(configurations, autoConfigurationMetadata); this.fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); } }
觀察上方代碼:
protected boolean isEnabled(AnnotationMetadata metadata) { return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true; }
private static final String[] NO_IMPORTS = new String[0];
此時若是沒有禁用自動裝配則進入else分枝,第一步操做首先會去加載全部Spring預先定義的配置條件信息,這些配置信息在org.springframework.boot.autoconfigure
包下的META-INF/spring-autoconfigure-metadata.properties
文件中
這些配置條件主要含義大體是這樣的:若是你要自動裝配某個類的話,你以爲先存在哪些類或者哪些配置文件等等條件,這些條件的判斷主要是利用了@ConditionalXXX
註解,關於@ConditionalXXX
系列註解能夠參考這篇文章:SpringBoot條件註解@Conditional
這個文件裏的內容格式是這樣的: ``` org.springframework.boot.actuate.autoconfigure.web.servlet.WebMvcEndpointChildContextConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,io.micrometer.core.instrument.MeterRegistry org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
3. 具體的加載代碼就不列出了,沒法就是個讀取配置文件 4. 這裏放個加載以後的結果圖: ![file](https://oscimg.oschina.net/oscnet/4f513deaabf03f10498327ae2edabb5dc29.jpg) 4. 獲取`@EnableAutoConfiguration`註解上的exclude、excludeName屬性,這兩個屬性的做用都是排除一些類的 5. 這裏又是關鍵的一步,能夠看到剛纔圖片中spring-autoconfigure-metadata.properties文件的上方存在一個文件spring.factories,這個文件可就不止存在於`org.springframework.boot.autoconfigure`包裏了,全部的包裏都有可能存在這個文件,因此這一步是加載整個項目全部的spring.factories文件。這個文件的格式是這樣的
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration
6. 這裏存在一個知識點,SpringBoot中的star就是依靠這個文件完成的,假如咱們須要自定義一個SpringBoot的Star,就能夠在咱們的項目的META-INF文件夾下新建一個spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.shiyujun.TestAutoConfiguration
這樣當別的項目依賴咱們的項目時就會自動把咱們的`TestAutoConfiguration`類注入到Spring容器中 7. 刪除重複的自動配置類 8. 下面三行就是去除咱們指定排除的配置類 9. 接着這一行的邏輯稍微複雜一些,主要就是根據加載的配置條件信息來判斷各個配置類上的`@ConditionalXXX`系列註解是否知足需求 10. 最後就是發佈自動裝配完成事件,而後返回全部可以自動裝配的類的全限定名 到了這裏咱們已經把SpringBoot自動裝配的原理搞清楚了,可是總感受差點什麼,那咱們從這些自動裝配的類裏面挑一個咱們比較熟悉的關於Servlet的類來看看咋回事吧:
@Configuration @ConditionalOnWebApplication( type = Type.SERVLET ) public class ServletEndpointManagementContextConfiguration { public ServletEndpointManagementContextConfiguration() { }
@Bean public ExposeExcludePropertyEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(WebEndpointProperties properties) { Exposure exposure = properties.getExposure(); return new ExposeExcludePropertyEndpointFilter(ExposableServletEndpoint.class, exposure.getInclude(), exposure.getExclude(), new String[0]); } @Configuration @ConditionalOnClass({ResourceConfig.class}) @ConditionalOnMissingClass({"org.springframework.web.servlet.DispatcherServlet"}) public class JerseyServletEndpointManagementContextConfiguration { public JerseyServletEndpointManagementContextConfiguration() { } @Bean public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) { return new ServletEndpointRegistrar(properties.getBasePath(), servletEndpointsSupplier.getEndpoints()); } } @Configuration @ConditionalOnClass({DispatcherServlet.class}) public class WebMvcServletEndpointManagementContextConfiguration { private final ApplicationContext context; public WebMvcServletEndpointManagementContextConfiguration(ApplicationContext context) { this.context = context; } @Bean public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) { DispatcherServletPathProvider servletPathProvider = (DispatcherServletPathProvider)this.context.getBean(DispatcherServletPathProvider.class); String servletPath = servletPathProvider.getServletPath(); if (servletPath.equals("/")) { servletPath = ""; } return new ServletEndpointRegistrar(servletPath + properties.getBasePath(), servletEndpointsSupplier.getEndpoints()); } }
}
自上而下觀察整個類的代碼,你會發現這些自動裝配的套路都是同樣的 1. 若是當前是Servlet環境則裝配這個bean 2. 當存在類`ResourceConfig`以及不存在類`DispatcherServlet`時裝配`JerseyServletEndpointManagementContextConfiguration` 3. 當存在`DispatcherServlet`類時裝配`WebMvcServletEndpointManagementContextConfiguration` 4. 接下來若是還有面試官問你,你會了麼? ![1](https://oscimg.oschina.net/oscnet/5ffaf05bf0873d4bfb4f2e62bacb767ae4e.jpg)