1. 程式人生 > >SpringBoot原理分析

SpringBoot原理分析

1.起步依賴原理分析

1)分析spring-boot-starter-parent

按住Ctrl點選pom.xml中的spring-boot-starter-parent,

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.0.6.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

可以看到了spring-boot-starter-parent的pom.xml。

發現它還有一個parent

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath>../../spring-boot-dependencies</relativePath>
    </parent>

而此parent有一個<properties>標籤

<properties>
        <activemq.version>5.15.6</activemq.version>
        <antlr2.version>2.7.7</antlr2.version>
        <appengine-sdk.version>1.9.66</appengine-sdk.version>
        <artemis.version>2.4.0</artemis.version>
        <aspectj.version>1.8.13</aspectj.version>
        <assertj.version>3.9.1</assertj.version>
        <atomikos.version>4.0.6</atomikos.version>
        <bitronix.version>2.1.4</bitronix.version>
        <build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
        <byte-buddy.version>1.7.11</byte-buddy.version>
...省略

此標籤裡有很多的<xxx.version>,便是maven的版本控制,當我們給與spring-boot-starter-parent版本後,它會給相關的座標鎖定版本。而spring的缺點就是因為版本不一致導致jar包衝突,SpringBoot直接給我們鎖定了相關jar包的版本,也就避免的這個問題。

而spring-boot-dependencies.xml檔案中還有一個<dependencyManagement>

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot</artifactId>
                <version>2.0.6.RELEASE</version>
...省略

裡面包含autoconfigure,devtools...等依賴的管理

而build標籤中包含如下

<includes>
    <include>**/application*.yml</include>
    <include>**/application*.yaml</include>
    <include>**/application*.properties</include>
</includes>

表示springBoot讓我們配置的檔案是".yml",".yaml"和".properties",並以application開頭。

所以spring-boot-starter-parent作用主要是我們的SpringBoot工程繼承spring-boot-starter-parent後已經具備版本鎖定等配置,而起步依賴的作用就是進行依賴的傳遞。

2)分析spring-boot-starter-web

開啟spring-boot-starter-web.pom檔案:發現如下

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.0.6.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.0.6.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.0.6.RELEASE</version>
      <scope>compile</scope>
    </dependency>

開啟spring-boot-starter-json有如下座標:

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.0.10.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.7</version>
      <scope>compile</scope>
    </dependency>

這裡的jackson就是springMVC中轉換json所需要的jar包。

spring-boot-starter-web.pom檔案中spring-boot-starter-tomcat的座標便是tomcat相關功能,而檔案中包含了tomcat的相關座標。不僅如此,spring-boot-starter-web.pom檔案中還引入了spring-web和spring-webmvc的座標。所以當我們引入spring-boot-starter-web座標後工程自動將我們需要的spring環境進行了進入。這樣我們的工程只要引入spring-boot-starter-web起步依賴的座標就可以進行web開發了,同樣體現了依賴傳遞的作用。

2.自動配置原理解析

SpringBoot工程的入口是@SpringBootApplication,按住Ctrl點選此註解,打開發現如下:

@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 {
...省略

而其中@SpringBootConfiguration檔案中有一個@Configuration註解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

而@Configuration表示該類為springg的一個配置類,即@SpringBootConfiguration和@Configuration作用是相同的。所以,當類上加上@SpringBootApplication註解後,它具備@Configuration的作用。可以看到@SpringBootApplication註解還包含@EnableAutoConfiguration註解和@ComponentScan,

而@ComponentScan是主鍵掃描,而它的規則是引導類(類上有@SpringBootApplication註解)所在的包及其子包都會掃描:

所以當我們配置了@Controller後,並沒有配置掃描包,一樣能掃描到。

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

裡面有一個@Import註解,表示當前註解引入AutoConfigurationImportSelector.class,所以 AutoConfigurationImportSelector有的功能 EnableAutoConfiguration也會存在。進入AutoConfigurationImportSelector,有一個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);
	}

其中List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);即載入配置,getCandidateConfigurations方法中如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		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;
	}

SpringFactoriesLoader.loadFactoryNames 方法的作用就是從META-INF/spring.factories檔案中讀取指定類對應的類名稱列表,這裡就是AutoConfigurationImportSelector類所在的org.springframework.boot.autoconfigure,而“No auto configuration classes found in META-INF/spring.factories. If you...”說明在包下存在一個META-INF/spring.factories檔案

開啟spring.factories配置檔案存在大量的以Configuration為結尾的類名稱,這些類就是存有自動配置資訊的類,而 SpringApplication在獲取這些類名後再載入

例如:

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\

找到ServletWebServerFactoryAutoConfiguration類,有一個@EnableConfigurationProperties註解

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

開啟ServerProperties類,發現他有@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)註解;

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
	private Integer port;
	private InetAddress address;
	@NestedConfigurationProperty
	private final ErrorProperties error = new ErrorProperties();
	private Boolean useForwardHeaders;
	private String serverHeader;
	private int maxHttpHeaderSize = 0; // bytes
	private Duration connectionTimeout;
	@NestedConfigurationProperty
	private Ssl ssl;
	@NestedConfigurationProperty
	private final Compression compression = new Compression();
	@NestedConfigurationProperty
	private final Http2 http2 = new Http2();
	private final Servlet servlet = new Servlet();
	private final Tomcat tomcat = new Tomcat();
	private final Jetty jetty = new Jetty();
	private final Undertow undertow = new Undertow();

其中,prefix = "server" 表示SpringBoot配置檔案中的字首,SpringBoot會將配置檔案中以server開始的屬性對映到該類的欄位中,而這個配置檔案就是和spring.factories在同一目錄下的spring-configuration-metadata.json檔案

開啟檔案可以看到如圖:

很明顯這裡就是工程啟動tomcat預設的埠地址。

所以當這些預設的配置配好後,會通過ServerProperties類進行載入,載入完後在ServletWebServerFactoryAutoConfiguration中進行引入(ServletWebServerFactoryAutoConfiguration類通過@EnableConfigurationProperties(ServerProperties.class)),引入完後AutoConfigurationImportSelector的 getCandidateConfigurations方法會去載入ServletWebServerFactoryAutoConfiguration的預設配置,這樣就配好了。當我們通過".yml",".yaml"和".properties"檔案進行配置事,就會將相同名稱的進行覆蓋,如上圖埠號name為server.port,配置server.port=xxxx,就覆蓋了。