我們知道,在使用SpringBoot的時候,我們只需要如下方式即可直接啟動一個Web程序:
<code>@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
/<code>
比起之前Spring時繁瑣的配置簡直不要太方便,那麼SpringBoot實現這些的原理是什麼呢?
@SpringBootApplication註解
<code>@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 {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
*/<code>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class>[] scanBasePackageClasses() default {};
}
這個註解上邊包含的東西還是比較多的,咱們先看一下兩個簡單的熱熱身
@ComponentScan 註解
<code>@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
/<code>
這個註解咱們都是比較熟悉的,無非就是自動掃描並加載符合條件的Bean到容器中,這個註解會默認掃描聲明類所在的包開始掃描,這個註解一共包含以下幾個屬性:
- basePackages:指定多個包名進行掃描
- basePackageClasses:對指定的類和接口所屬的包進行掃
- excludeFilters:指定不掃描的過濾器
- includeFilters:指定掃描的過濾器
- lazyInit:是否對註冊掃描的bean設置為懶加載
- nameGenerator:為掃描到的bean自動命名
- resourcePattern:控制可用於掃描的類文件
- scopedProxy:指定代理是否應該被掃描
- scopeResolver:指定掃描bean的範圍
- useDefaultFilters:是否開啟對@Component,@Repository,@Service,@Controller的類進行檢測
@SpringBootConfiguration註解
這個註解更簡單了,它只是對Configuration註解的一個封裝而已
<code>@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
/<code>
@EnableAutoConfiguration註解
這個註解可是重頭戲了,SpringBoot的約定大於配置,也就是本文的重點自動裝配的原理就在這裡了
<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
/<code>
這個註解存在的意義就是:利用@Import註解,將所有符合自動裝配條件的bean注入到IOC容器中,關於@Import註解原理這裡就不再闡述,可以參考此篇文章:
可以看到@Import(AutoConfigurationImportSelector.class) 此處咱們進入AutoConfigurationImportSelector,觀察其selectImports方法,這個方法執行完畢後,Spring會把這個方法返回的類的全限定名數組裡的所有的類都注入到IOC容器中
<code>@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//調用下方getAutoConfigurationEntry方法
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
/<code>
- 第一行if時會首先判斷當前系統是否禁用了自動裝配的功能,判斷的代碼如下:
<code>protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
/<code>
- 如果當前系統禁用了自動裝配的功能則會返回
<code>private static final String[] NO_IMPORTS = new String[0];
/<code>
這個空的數組,後續也就無法注入bean了
- 此時如果沒有禁用自動裝配則進入else分支,第一步操作首先會去加載所有Spring預先定義的配置條件信息,這些配置信息在org.springframework.boot.autoconfigure包下的META-INF/spring-autoconfigure-metadata.properties文件中
4. 簡單打開配置文件看一下
這些配置條件主要含義大致是這樣的:如果你要自動裝配某個類的話,你覺得先存在哪些類或者哪些配置文件等等條件,這些條件的判斷主要是利用了@ConditionalXXX註解,關於@ConditionalXXX系列註解可以參考這篇文章
- 具體加載代碼就不做解析了,無非就讀取配置文件,加載過後,調用getAutoConfigurationEntry()方法
<code>protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//獲取註解屬性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//獲取候選配置,加載項目中所有spring.factories 文件配置
List<string> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//刪除重複的配置類
configurations = removeDuplicates(configurations);
//獲取註解上的exclude、excludeName屬性,排除一些類
Set<string> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//根據加載的配置條件信息來判斷各個配置類上的@ConditionalXXX系列註解是否滿足需求
configurations = filter(configurations, autoConfigurationMetadata);
//最後就是發佈自動裝配完成事件,然後返回所有能夠自動裝配的類的全限定名
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}/<string>/<string>/<code>
- 這裡又是關鍵的一步,可以看到剛才圖片中spring-autoconfigure-metadata.properties文件的上方存在一個文件spring.factories,這個文件可就不止存在於org.springframework.boot.autoconfigure包裡了,所有的包裡都有可能存在這個文件,所以這一步是加載整個項目所有的spring.factories文件。
- SpringBoot 中的starter 就是依靠這個文件完成的,假如我們需要自定義一個SpringBoot的starter,就可以在我們的項目的META-INF文件夾下新建一個spring.factories文件
<code>org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.demo.TestAutoConfiguration
/<code>
這樣當別的項目依賴我們的項目時就會自動把我們的TestAutoConfiguration類注入到Spring容器中
- filter(configurations, autoConfigurationMetadata); 這個就是根據加載的配置條件判斷各個配置類上的@ConditionalXXX系列註解是否滿足需求。這個filter 方法會獲取 Spring.factories 裡邊
到了這裡我們已經把SpringBoot自動裝配的原理搞清楚了,但是總感覺差點什麼,那我們從這些自動裝配的類裡面挑一個我們比較熟悉的關於Servlet的類來看看咋回事吧
<code>@ManagementContextConfiguration
@ConditionalOnWebApplication(type = Type.SERVLET)
public class ServletEndpointManagementContextConfiguration {
@Bean
public ExposeExcludePropertyEndpointFilter<exposableservletendpoint> servletExposeExcludePropertyEndpointFilter(
WebEndpointProperties properties) {
WebEndpointProperties.Exposure exposure = properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableServletEndpoint.class,
exposure.getInclude(), exposure.getExclude());
}
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public static class WebMvcServletEndpointManagementContextConfiguration {
private final ApplicationContext context;
public WebMvcServletEndpointManagementContextConfiguration(
ApplicationContext context) {
this.context = context;
}
@Bean
public ServletEndpointRegistrar servletEndpointRegistrar(
WebEndpointProperties properties,
ServletEndpointsSupplier servletEndpointsSupplier) {
DispatcherServletPath dispatcherServletPath = this.context
.getBean(DispatcherServletPath.class);
return new ServletEndpointRegistrar(
dispatcherServletPath.getRelativePath(properties.getBasePath()),
servletEndpointsSupplier.getEndpoints());
}
}
@Configuration
@ConditionalOnClass(ResourceConfig.class)
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
public static class JerseyServletEndpointManagementContextConfiguration {
private final ApplicationContext context;
public JerseyServletEndpointManagementContextConfiguration(
ApplicationContext context) {
this.context = context;
}
@Bean
public ServletEndpointRegistrar servletEndpointRegistrar(
WebEndpointProperties properties,
ServletEndpointsSupplier servletEndpointsSupplier) {
JerseyApplicationPath jerseyApplicationPath = this.context
.getBean(JerseyApplicationPath.class);
return new ServletEndpointRegistrar(
jerseyApplicationPath.getRelativePath(properties.getBasePath()),
servletEndpointsSupplier.getEndpoints());
}
}
}
/<exposableservletendpoint>/<code>
- 如果當前是Servlet環境則裝配這個bean
- 當存在類ResourceConfig以及不存在類DispatcherServlet時裝配JerseyServletEndpointManagementContextConfiguration
- 當存在DispatcherServlet類時裝配WebMvcServletEndpointManagementContextConfiguration
其他的自動配置類,也都差不多,自動裝配的套路都是一樣的。
閱讀更多 程序員進軍SJF 的文章