Spring boot 自動裝配機制

前述

在解析spring boot starter自動裝配機制之前,我們先來回顧一下web工程是如何搭建的:

  1. gradle/pom文件中引入項目依賴jar包;

  2. 配置web.xml,Servlet配置,攔截器設置,Listener配置…;

  3. 數據庫連接,配置spring事務;

  4. 配置視圖解析器;

  5. 開啓註解,自動掃描功能

  6. 配置完成後部署tomcat,調試等…

在搭建這些環境的時候是非常耗時間耗精力的,而且有時還會缺斤少兩,連蹦bug…

而有了springboot之後,所有的環境配置就會非常便捷。

自動配置原理

我們先來搭建一個最簡單的springboot工程,如下:

image-20200704145030663.png

application.properties文件如下:

server.port=8080

這樣,我們就搭好了一個最簡單的springboot工程,隨後就可以啓動了。

我們或許有點好奇,明明自己就只配置了一個端口號,剩下的自己啥也沒配,爲啥這個web程序就可以啓動了??爲什麼???

接下來,我們來分析以下,以下是我的思路分析過程(爲了防止後面看着看着就迷路,我先來個大綱):

  1. SpringbootApplication啓動類的註解@SpringBootApplication,即程序入口;
  2. @EnableAutoConfiguration註解(bean的裝配與加載)重點
  3. @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`幹了這麼幾件事:

  1. 掃描所有jar包類路徑下 META‐INF/spring.factories ;
  2. 把掃描到的這些文件的內容包裝成properties對象 ;
  3. 從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();
    ...}

首先我們來分析一下:

  1. @Configuration與@bean的結合使用相當於是:創建一個基於java代碼的配置類,可以用來替代相應的xml配置文件。

  2. @EnableConfigurationProperties:引入外部文件,與springboot的配置文件形成映射。

  3. 其餘就是各種@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的自動狀態,我們大致可總結爲這麼幾個步驟:

  1. 主程序中的@springbootApplication修飾;

  2. 在@EableAutoConfiguration註解中(實現bean的裝配與加載),實現了以下兩步:

    • @Import引入的AutoCongurationImportSelector負責找到需要導入bean;
    • @AutoConfigurationPackage負責將其bean加載到容器中;

    被@EableAutoConfiguration修飾的註解:

    • 通過@Conguration與@bean使用java的方式配置bean;
    • @EnableConfigurationProperties引入配置文件屬性,與spring boot的配置文件相映射(這也是爲什麼你在application.yml裏面配置文件的時候可以生效);
    • @Conditionxx條件,滿足則存在;
  3. @CompomentScan掃描包。(實現bean的掃描與發現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章