SpringBoot2.0自動配置原理和自定義配置方法總結

SpringBoot配置方法總結


SpringBoot的一大好處就是:大大簡化了Spring和其他框架和Spring整合時的配置,傳統的SSM套裝雖然很大程度地簡化了Web開發,但是其的配置文件卻較爲繁瑣,爲了簡化配置文件使開發者更專注於業務編碼(懶)可以使用SpringBoot來進行web開發,其精簡的配置和龐大繁茂的生態圈絕對令人驚歎!

SpringBoot之所以可以達到如此精簡的配置,主要原因就是SpringBoot大量的自動配置!!!

自動配置原理:

  1. SpringBoot應用從啓動類的main方法中啓動,加載SpringBoot主配置類(依賴@SpringBootApplication註解),主配置類(@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 {
    	。。。
    }
    
  2. 開啓自動配置功能(依賴**@EnableAutoConfiguration**註解):

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    	。。。
    }
    
  3. 選擇器(@Import(AutoConfigurationImportSelector.class))獲取候選配置,給容器導入一些組件:

    查看AutoConfigurationImportSelector類的**public String[] selectImports(AnnotationMetadata annotationMetadata)**方法:

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
       }
       AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
       AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
       return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
  4. 通過selectImports方法間接調用**getCandidateConfigurations(annotationMetadata, attributes)**方法:

    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;
    }
    
  5. 再調用SpringFactoriesLoader類的**loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
    getBeanClassLoader())**方法去掃描所有jar包類路徑下的:

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF /spring.factories";
    

    n7ASED.png
    n7AwG9.md.png

    把掃描到的properties文件的內容包裝成properties對象:

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
            if (result != null) {
                return result;
            } else {
                try {
                    Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                    LinkedMultiValueMap result = new LinkedMultiValueMap();
    
                    while(urls.hasMoreElements()) {
                        URL url = (URL)urls.nextElement();
                        UrlResource resource = new UrlResource(url);
                        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                        Iterator var6 = properties.entrySet().iterator();
    
                        while(var6.hasNext()) {
                            Entry<?, ?> entry = (Entry)var6.next();
                            String factoryClassName = ((String)entry.getKey()).trim();
                            String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                            int var10 = var9.length;
    
                            for(int var11 = 0; var11 < var10; ++var11) {
                                String factoryName = var9[var11];
                                result.add(factoryClassName, factoryName.trim());
                            }
                        }
                    }
    
                    cache.put(classLoader, result);
                    return result;
                } catch (IOException var13) {
                    throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
             }
            }
     }
    

    從包裝好的properties中獲取到EnableAutoConfiguration.class類(類名)對應的值,然後把它們添加到容器中去。

☆上面這五個步驟大致講述了SpringBoot自動配置的原理,可能還是比較抽象不易理解,接下來用一個例子再次具體分析理解☆

spring.factories中每一個XXXAutoConfiguration類都是容器中的一個組件,都加入到容器中,用他們來做自動配置。每一個XXX自動配置類都可以進行自動配置功能,舉個簡單的例子(HttpEncodingAutoConfiguration):

首先找到HttpEncodingAutoConfiguration類:

//表明這是一個配置類,像編寫配置文件一樣,也可以向容器中添加組件
@Configuration
//啓動指定類的ConfigurationProperties功能,
//將配置文件中對應的值和HttpProperties綁定起來,並將HttpProperties加入Ioc容器
@EnableConfigurationProperties(HttpProperties.class)
//Spring底層的@Conditional註解,根據不同的條件,如果滿足指定的條件
//整個配置類裏面的配置就會生效;判斷當前應用是否是web應用,如果是,則配置類生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//判斷當前項目有沒有CharacterEncodingFilter這個類
//SpringMvc中CharacterEncodingFilter這個類一般是配置Web.xml中解決亂碼的過濾器
@ConditionalOnClass(CharacterEncodingFilter.class)
//判斷配置文件中是否存在某個配置spring.http.encoding.enabled
//matchIfMissing = true如果不存在,判斷也成立,
//即使配置文件中不配置spring.http.encoding.enabled=true,也是默認生效的
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
    //他的值已經和配置文件映射了
    private final HttpProperties.Encoding properties;
	//只有一個有參構構造器的情況下,參數的值就會從容器中拿
	public HttpEncodingAutoConfiguration(HttpProperties properties) {
		this.properties = properties.getEncoding();
	}
    @Bean//給容器添加一個組件,這些組件的某些值需要從Properties中獲取
	@ConditionalOnMissingBean
	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類中獲取的,properties類裏面的每一個屬性又是和配置文件綁定的。

來看一看HttpProperties類:

@ConfigurationProperties(prefix = "spring.http")//從配置文件中獲取指定的值和bean的屬性進行綁定
public class HttpProperties {
    private boolean logRequestDetails;
	private final Encoding encoding = new Encoding();
	//getter/setter/is等等方法。。。 
	public static class Encoding {//這裏有一個Encoding靜態內部類
		public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
		private Charset charset = DEFAULT_CHARSET;
		private Boolean force;
		private Boolean forceRequest;
		private Boolean forceResponse;
		private Map<Locale, Charset> mapping;
        //getter/setter/is等等方法。。。 
	}
}

這時在我們的application.properties文件中加一條配置:

spring.http.encoding.charset=utf-8

ctrl左鍵這個Key,發現居然跳到了上面HttpProperties類的Encoding靜態內部類的setCharset(Charset charset)方法!!這下子恍然大悟了!當然這只是大量組件中較爲簡單的一個,但是每個組件的自動配置邏輯大同小異,只有掌握了SpringBoot的這一精髓,才能更好對其他的細節進行深入理解!


自定義配置方法

用戶很多時候不可避免的進行自定義配置,SpringBoot除了自動配置,理所當然的支持用戶的自定義配置!

SpringBoot的自定義配置主要有兩種:1.使用配置文件進行外部屬性配置。2.用配置類進行配置。

接下來對兩種配製方法展開說明:

1.使用配置文件進行外部屬性配置:

SpringBoot中比較常見且推薦的是**.properties.yml**兩種配置文件。

想知道配置文件中所有可配屬性,可以在SpringBoot官方說明文檔中找到 Common application properties——SpringBoot2.1.8
當然如果你已經理解了自動配置的原理,大可不必因爲這點小事去查詢文檔了

YAML是JSON的一個超集,可以將外部配置以層次結構形式存儲起來。當項目的類路徑中用SnakeYAML庫(spring-boot-starter中已經被包含)時,SpringApplication類將自動支持YAML作爲properties的替代。所以在優先級上YAML>properties

YAML的數據格式和JSON很像,都是樹狀結構都是K-V格式,並通過“:”進行賦值。

properties文件中以".“進行分割的,在yml文件中用”:“進行分割的。yml文件的每個”:"後面一定都要加一個空格,否則文件會報錯。

@Value和@ConfigurationProperties:

可以在編寫代碼時在屬性上使用@Value($(key))來取值並對被註解的屬性賦值,也可以在類上使用@ConfigurationProperties(prefix=“xxx”)註解取出所有以xxx爲前綴的key所對應的值來對被註解類的屬性進行匹配並賦值。

功能 @ConfigurationProperties @Value
鬆散綁定(鬆散語法) 支持 不支持
SpEL 不支持 支持
JSR303數據校驗 支持 不支持
複雜類型封裝 支持 不支持

無論是yml文件還是properties文件,這兩個註解都能獲取到值。

如果只是某個業務邏輯中需要獲取一下配置文件中的某個值,建議使用@Value。

如果是編寫一個JavaBean和配置文件進行映射,建議直接用@ConfigurationProperties。

@PropertySesource和@ImportResource:

上面介紹的兩個註解都是默認從默認全局配置文件(application.properties或application.yml)中讀取值,如果需要加載指定配置文件中的值,則需要使用**@PropertySesource(value = {“classpath:xxx.propertiex”},…)**來指定需要加載的一個或多個配置文件。

在使用springboot的時候一般是極少需要添加配置文件的(application.properties除外),但是在實際應用中也會存在不得不添加配置文件的情況,例如集成其他框架或者需要配置一些中間件等,在這種情況下,我們就需要引入我們自定義的xml配置文件了。在SpringBoot中依然支持xml文件的配置方式。但是需要在啓動類上加**@ImportResource(locations = {“classpath:xxx.xml”},…)**註解來指定一個或多個xml文件的位置從而對框架或中間件進行配置。


2.用配置類進行配置:

WebMvcConfigurationSupport

SpringBoot2.0之後建議自定義配置類繼承WebMvcConfigurationSupport,在自定義配置類上加上**@Component**註解就可以自動掃描到配置類並加載了。

WebMvcConfigurationSupport這個類中提供了很多很多方法大多數方法都是顧名思義:處理器異常解析器、添加攔截器等等。。。很多很便利的方法。

@Component
public class Configuration extends WebMvcConfigurationSupport {
    //添加自定義攔截器
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**").excludePathPatterns("/login","/toLogin");
        super.addInterceptors(registry);
    }
    //自定義視圖解析器,使用這個配置類之後properties文件中配置的視圖解析器就失效了
    @Bean
    public ViewResolver getViewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

@Bean

可以對配置類中的方法進行註解,將方法的返回值添加到容器中,容器中這個組件默認的id就是方法名。

@Bean
public UserService userService01(){//容器中就會加入一個UserService類型的組件,id是userService01
    return new UserService();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章