1. 程式人生 > 其它 >攻防世界web進階題—bug

攻防世界web進階題—bug

SpringBoot自動配置原理

備註:該SpringBoot自動配置原理不適合java剛入門學者以及不熟悉Spring4+Springmvc+maven的同學

1、當SpringBoot應用啟動的時候,就從主方法裡面進行啟動的。

@SpringBootApplication
public class SpringBoot02ConfigAutoconfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBoot02ConfigAutoconfigApplication.class, args);
    }
}

它主要載入了@SpringBootApplication註解主配置類,這個@SpringBootApplication註解主配置類裡邊最主要的功能就是SpringBoot開啟了一個@EnableAutoConfiguration註解的自動配置功能。

2、@EnableAutoConfiguration作用:

它主要利用了一個EnableAutoConfigurationImportSelector選擇器給Spring容器中來匯入一些元件。

@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 

3、那麼匯入了哪些元件呢?

我們來看EnableAutoConfigurationImportSelector這個類的父類selectImports

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}

父類裡面規定了一個方法叫selectImports這個方法,查看了selectImports這個方法裡面的程式碼內容就能知道匯入了哪些元件了。

在selectImports這個方法裡面主要有個configurations,並且這個configurations最終會被返回。

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			configurations = removeDuplicates(configurations);
			configurations = sort(configurations, autoConfigurationMetadata);
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			return configurations.toArray(new String[configurations.size()]);
		}

這個configurations它是獲取候選的配置。

List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);

這個configurations方法的作用就是利用SpringFactoriesLoader.loadFactoryNames從類路徑下得到一個資源

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		try {
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

4、那麼得到哪些資源呢?

它是掃描javajar包類路徑下的“META-INF/spring.factories”這個檔案

**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

那麼掃描到的這些檔案作用:是把這個檔案的urls拿到之後並把這些urls每一個遍歷,最終把這些檔案整成一個properties物件

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		try {
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			List<String> result = new ArrayList<String>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
				String factoryClassNames = properties.getProperty(factoryClassName);
				result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
			}
			return result;

然後它從properties物件裡邊獲取一些值,把這些獲取到的值來載入我們最終要返回的這個結果,這個結果就是我們要交給Spring容器中的所有元件,這相當於這factoryClassName就是我們傳過來的Class的這個類名。

而傳過來的Class是呼叫這個getSpringFactoriesLoaderFactoryClass()這個方法得到從properties中獲取到EnableAutoConfiguration.class類名對應的值

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

然後把它們新增在容器中

5、按照它的這個意思,來到第二個Springjar包的META-INF下的spring.factories這個檔案找到配置所有EnableAutoConfiguration的值加入到Spring容器中

所以說我們容器中最終會新增很多的類

比如:

# 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.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
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,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

每一個xxxAutoConfiguration類都是容器中的一個元件,並都加入到容器中。

加入到容器中之後的作用就是用它們來做自動配置

這就是Springboot自動配置之源,也就是自動配置的開始

只有這些自動配置類進入到容器中以後,接下來這個自動配置類才開始進行啟動

6、每一個自動配置類進行自動配置功能

以一個自動配置類HttpEncodingAutoConfiguration(HTTP的編碼自動配置)為例子來解釋SpringBoot的自動配置之原理:

​ 1). 這個HttpEncodingAutoConfiguration類上面標註了一大堆的註解:

@Configuration    //表示這是一個配置類,類似於以前編寫的配置檔案一樣,也可以給容器中新增元件
@EnableConfigurationProperties(HttpEncodingProperties.class) //啟用ConfigurationProperties功能:
//這個ConfigurationProperties裡面引入了一個類,這個類就是啟用指定類的ConfigurationProperties功能
//有了這個@EnableConfigurationPropertie註解以後相當於把配置檔案中對應值就和這個HttpEncodingProperties.class類繫結起來了。

@ConditionalOnWebApplication //這個註解的意思就是判斷當前是不是web應用,@Conditional是spring底層,意思就是根據不同的條件,來進行自己不同的條件判斷,如果滿足指定的條件,那麼整個配置類裡邊的配置才會生效。

@ConditionalOnClass(CharacterEncodingFilter.class)//看這個類裡邊有沒有這個過濾器,就是判斷當前專案裡邊有沒有CharacterEncodingFilter這個類,這個CharacterEncodingFilter類是Springmvc中亂碼解決的過濾器。

@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)//@ConditionalOnProperty註解是來判斷配置檔案中是否存在某個配置,就是是否存在spring.http.encoding.enabled這個配置,matchIfMissing的意思就是如果不存在也認為這個判斷是正確的
//即使配置檔案中不配置spring.http.encoding.enabled=true這個屬性,也是預設生效的
public class HttpEncodingAutoConfiguration {

點進去HttpEncodingProperties這個類,發現這個HttpEncodingProperties類上面標註了@ConfigurationProperties註解

@ConfigurationProperties(prefix = "spring.http.encoding") //從配置檔案中獲取指定的值和bean的屬性進行繫結
public class HttpEncodingProperties {
	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

所以說配置檔案中該配置什麼,我們就按照它的這個旨意,它要配spring.http.encoding這個屬性,這個屬性裡邊能配置什麼值,就對應HttpEncodingProperties這個類來配置,所有的配置檔案中能配置的屬性都是在xxx.Properties類中封裝著

@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {

	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

	/**
	 * Charset of HTTP requests and responses. Added to the "Content-Type" header if not
	 * set explicitly.
	 */
	private Charset charset = DEFAULT_CHARSET;

	/**
	 * Force the encoding to the configured charset on HTTP requests and responses.
	 */
	private Boolean force;

	/**
	 * Force the encoding to the configured charset on HTTP requests. Defaults to true
	 * when "force" has not been specified.
	 */
	private Boolean forceRequest;

	/**
	 * Force the encoding to the configured charset on HTTP responses.
	 */
	private Boolean forceResponse;

	/**
	 * Locale to Encoding mapping.
	 */
	private Map<Locale, Charset> mapping;

	public Charset getCharset() {
		return this.charset;
	}

	public void setCharset(Charset charset) {
		this.charset = charset;
	}

	public boolean isForce() {
		return Boolean.TRUE.equals(this.force);
	}

	public void setForce(boolean force) {
		this.force = force;
	}

	public boolean isForceRequest() {
		return Boolean.TRUE.equals(this.forceRequest);
	}

	public void setForceRequest(boolean forceRequest) {
		this.forceRequest = forceRequest;
	}

	public boolean isForceResponse() {
		return Boolean.TRUE.equals(this.forceResponse);
	}

	public void setForceResponse(boolean forceResponse) {
		this.forceResponse = forceResponse;
	}

	public Map<Locale, Charset> getMapping() {
		return this.mapping;
	}

	public void setMapping(Map<Locale, Charset> mapping) {
		this.mapping = mapping;
	}


所以說配置檔案能配置什麼就可以參照某一個功能對應的這個屬性類

7、這個HttpEncodingProperties類就是根據當前不同的條件判斷,決定這個配置類是否生效。

如果一旦生效了,所有的配置類都成功了,就給容器中新增各種元件,這些元件的屬性是從對應的properties類中獲取的,而這properties類裡邊的每一個屬性又是和配置檔案繫結的

	@Bean  //給容器中新增一個元件。
	@ConditionalOnMissingBean(CharacterEncodingFilter.class) //新增一個我們自己來new這個CharacterEncodingFilter,把這個filter新增過去,但是注意這個filter裡邊要獲取字符集的名字(filter.setEncoding(this.properties.getCharset().name());),你是UTF8編碼還是什麼編碼,它要從properties中進行獲取,意思就是這個元件的某些值需要從properties中獲取
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
		return filter;
	}

我們可以再深入的看一下properties

private final HttpEncodingProperties properties; //它已經和SpringBoot配置檔案進行映射了。

我們看到properties是HttpEncodingProperties,也就是說HttpEncodingProperties這個物件的值它是獲取配置檔案的值的,所以我們在配置這個filter到底要用什麼編碼的時候是從properties獲取的。

而且值得注意的是:

@Configuration
@EnableConfigurationProperties(HttpEncodingProperties.class)
@ConditionalOnWebApplication
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

	private final HttpEncodingProperties properties;
	//只有一個有參構造器
	public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
		this.properties = properties;
	}


這個HttpEncodingAutoConfiguration只有一個有參構造器,在只有一個有參構造器的情況下,引數的值就會從容器中拿

8、而容器中它怎麼去拿到呢?

相當於是前面的這個@EnableConfigurationProperties(HttpEncodingProperties.class) 註解,這個@EnableConfigurationProperties註解的作用就是把HttpEncodingProperties.class和配置檔案進行繫結起來並把HttpEncodingProperties加入到容器中。

接下來這個自動配置類,通過一個有參構造器把這個屬性拿到,而這個屬性已經和SpringBoot映射了,接下來要用什麼編碼,就是拿到HttpEncodingProperties這個類裡邊的屬性。

所以SpringBoot能配置什麼,它要設定編碼,它是獲取properties裡邊getCharset裡邊的name值。

filter.setEncoding(this.properties.getCharset().name());

所以就以此類推,配置一個Spring配置,就可以照著HttpEncodingProperties這裡邊的來配置。

比如在application.properties配置檔案下配置一個http.encoding.enabled屬性:

spring.http.encoding.enabled=true   //能配置這個就相當於是我們之前的判斷屬性

還能配置其他的一些屬性。

比如:

spring.http.encoding.charset=UTF-8

所以我們能夠配置哪些屬性,都是來源於這個功能的properties類

有了這個自動配置類,自動配置類就給容器中新增這個filter,然後這個filter就會起作用了。

用好SpringBoot只要把握這幾點:

​ 1).SpringBoot啟動會載入大量的自動配置類

​ 2).所要做的就是我們需要的功能SpringBoot有沒有幫我們寫好的自動配置類:

​ 3).如果有就再來看這個自動配置類中到底配置了哪些元件,Springboot自動配置類裡邊只要我們要用的元件有,我們就不需要再來配置了,但是如果說沒有我們所需要的元件,那麼我們就需要自己來寫一個配置類來把我們相應的元件配置起來。

​ 4).給容器中自動配置類新增元件的時候,會從properties類中獲取某些屬性,而這些屬性我們就可以在配置檔案指定這些屬性的值

以上內容就是SpringBoot自動配置原理的整個精髓