长大后想做什么?做回小孩!

0%

SpringBoot配置方法总结

SpringBoot的一大好处就是:大大简化了Spring和其他框架和Spring整合时的配置,传统的SSM套装虽然很大程度地简化了Web开发,但是其的配置文件却较为繁琐,为了简化配置文件使开发者更专注于业务编码(懒)可以使用SpringBoot来进行web开发,其精简的配置和庞大繁茂的生态圈绝对令人惊叹!

SpringBoot之所以可以达到如此精简的配置,主要原因就是SpringBoot大量的自动配置!!!

自动配置原理:

  1. SpringBoot应用从启动类的main方法中启动,加载SpringBoot主配置类(依赖@SpringBootApplication注解),主配置类(@SpringBootApplication):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @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注解):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @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)方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @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)方法:

    1
    2
    3
    4
    5
    6
    7
    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包类路径下的:
    1
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF /spring.factories";

    n7ASED.png

    n7AwG9.md.png

    把扫描到的properties文件的内容包装成properties对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//表明这是一个配置类,像编写配置文件一样,也可以向容器中添加组件
@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类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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文件中加一条配置:

1
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这个类中提供了很多很多方法大多数方法都是顾名思义:处理器异常解析器、添加拦截器等等。。。很多很便利的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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就是方法名。

1
2
3
4
@Bean
public UserService userService01(){//容器中就会加入一个UserService类型的组件,id是userService01
return new UserService();
}