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();
}
发布了44 篇原创文章 · 获赞 22 · 访问量 1910

猜你喜欢

转载自blog.csdn.net/qq_42758551/article/details/100986263