SpringBoot源碼解析之SpringBoot自動裝配原理

我們知道,在使用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.
*


* 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 {};

}

/<code>

這個註解上邊包含的東西還是比較多的,咱們先看一下兩個簡單的熱熱身

@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>
  1. 第一行if時會首先判斷當前系統是否禁用了自動裝配的功能,判斷的代碼如下:
<code>protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
/<code>
  1. 如果當前系統禁用了自動裝配的功能則會返回
<code>private static final String[] NO_IMPORTS = new String[0];
/<code>

這個空的數組,後續也就無法注入bean了

  1. 此時如果沒有禁用自動裝配則進入else分支,第一步操作首先會去加載所有Spring預先定義的配置條件信息,這些配置信息在org.springframework.boot.autoconfigure包下的META-INF/spring-autoconfigure-metadata.properties文件中
SpringBoot源碼解析之SpringBoot自動裝配原理

spring-autoconfigure-metadata.properties

4. 簡單打開配置文件看一下


SpringBoot源碼解析之SpringBoot自動裝配原理


這些配置條件主要含義大致是這樣的:如果你要自動裝配某個類的話,你覺得先存在哪些類或者哪些配置文件等等條件,這些條件的判斷主要是利用了@ConditionalXXX註解,關於@ConditionalXXX系列註解可以參考這篇文章

  1. 具體加載代碼就不做解析了,無非就讀取配置文件,加載過後,調用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>


  1. 這裡又是關鍵的一步,可以看到剛才圖片中spring-autoconfigure-metadata.properties文件的上方存在一個文件spring.factories,這個文件可就不止存在於org.springframework.boot.autoconfigure包裡了,所有的包裡都有可能存在這個文件,所以這一步是加載整個項目所有的spring.factories文件。
  1. SpringBoot 中的starter 就是依靠這個文件完成的,假如我們需要自定義一個SpringBoot的starter,就可以在我們的項目的META-INF文件夾下新建一個spring.factories文件
<code>org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.demo.TestAutoConfiguration
/<code>

這樣當別的項目依賴我們的項目時就會自動把我們的TestAutoConfiguration類注入到Spring容器中

  1. filter(configurations, autoConfigurationMetadata); 這個就是根據加載的配置條件判斷各個配置類上的@ConditionalXXX系列註解是否滿足需求。這個filter 方法會獲取 Spring.factories 裡邊
SpringBoot源碼解析之SpringBoot自動裝配原理

到了這裡我們已經把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>
  1. 如果當前是Servlet環境則裝配這個bean
  2. 當存在類ResourceConfig以及不存在類DispatcherServlet時裝配JerseyServletEndpointManagementContextConfiguration
  3. 當存在DispatcherServlet類時裝配WebMvcServletEndpointManagementContextConfiguration

其他的自動配置類,也都差不多,自動裝配的套路都是一樣的。


分享到:


相關文章: