SpringBoot自动配置原理与自定义配置介绍

SpringBoot配置与自动配置原理

自动配置原理

SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration

@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

@EnableAutoConfiguration 的作用:

  • 利用EnableAutoConfigurationImportSelector给容器中导入一些组件
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)  //!!!
public @interface EnableAutoConfiguration
  • 可以查看AutoConfigurationImportSelector.selectImports()方法的内容;
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());
}
  • getAutoConfigurationEntry()方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        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 = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}
  • getCandidateConfigurations()方法:
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;
}

从META-INF下面找spring.factories文件,将里面配置的所有EnableAutoConfiguration的值加入到了容器中

SpringBoot自动配置原理与自定义配置介绍

每一个这样的xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置。

例如:自动配置类DispatcherServletAutoConfiguration

(节选内部类DispatcherServletConfiguration)

//配置文件
@Configuration(proxyBeanMethods = false)
//配置条件满足类DefaultDispatcherServletCondition的验证
@Conditional(DefaultDispatcherServletCondition.class)
//如果存在ServletRegistration类则进行配置
@ConditionalOnClass(ServletRegistration.class)
//如果存在对应的属性配置(WebMvcProperties对应的是spring.mvc.*)则启用配置
@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        //注意
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

    @Bean
    //如果存在类定义则配置
    @ConditionalOnBean(MultipartResolver.class)
    //判断如果不存在bean名称为DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME,则配置 bean
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
        // Detect if the user has created a MultipartResolver but named it incorrectly
        return resolver;
    }

}

通过上面的代码,可以看到这个自动配置类使用了许多@Conditional**注解来条件性地加载配置(比如说是否存在指定的类),配置的默认值从一个**Properties类中取出,如果用户配置了自定义属性,会覆盖掉**Properties中的默认值。

到这里,应该明白为什么几乎在没有任何配置下就能用Spring Boot启动 Spring MVC项目。但是有时候,我们需要对这些默认的环境进行修改以适应个性化的要求,这些在 Spring Boot中也是非常简单的,正如@EnableConfigurationProperties注解那样,它允许读入配置文件的内容来自定义自动初始化所需的内容。

Spring Boot配置文件

Spring Boot的所有的AutoConfiguration属性项可以在 https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#common-application-properties 看到。这一节我们主要介绍自定义配置。

yaml语法

配置文件推荐使用yaml语法来写,通过缩进来表达数据关系。

字符串

字符串​可以不加引号,如果加引号:

  • "":双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思
    • name: "zhangsan \n lisi"\n会解析为回车
  • '':单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据
    • name: 'zhangsan \n lisi'\n不会解析为回车

对象、Map

friends:
    lastName: zhangsan
    age: 20

行内写法:

friends: {lastName: zhangsan,age: 18}

数组(List、Set)

pets:
    - cat
    - dog
    - pig

行内写法:

pets: [cat,dog,pig]

自定义属性配置

可以在类上加上@ConfigurationProperties(prefix="")注解,把本类中所有属性和配置文件中相关的配置进行绑定。

只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能。

例:

JavaBean

@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String lastName;
    private Integer age;
    private Boolean boss;
    private Date birth;

    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;

    //getter and setter...
}

配置:

person:
    lastName: hello
    age: 18
    boss: false
    birth: 2000/01/01
    maps: {k1: v1,k2: 12}
    lists:
      - lisi
      - zhaoliu
    dog:
      name: 小狗
      age: 7

它默认从全局配置中获取值,可以使用@PropertySource(value={})来指定配置文件。

还有一种方法是使用@Value,它必须配合SpEL来指定对应配置的名称,不支持松散语法绑定(即大小写等不匹配)。

配置文件占位符

随机数

  • ${random.value}
  • ${random.int}
  • ${random.long}
  • ${random.int(10)}
  • ${random.int[1024,65536]}

占位符获取之前配置的值,如果没有可以是用:指定默认值

person.last-name=张三{random.uuid}
person.age={random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15

配置文件加载位置

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件

  • file:./config/
  • file:./
  • classpath:/config/
  • classpath:/

优先级由高到底,高优先级的配置会覆盖低优先级的配置

SpringBoot会从这四个位置全部加载主配置文件,互补配置

我们还可以通过spring.config.location来改变默认的配置文件位置。

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;

java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties

SpringBoot支持的所有的外部配置文件加载方法见:

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config

@ImportResource注解

@ImportResource可以帮助导入传统xml类型的配置,例如springcontext配置,直接将它写在启动类上就行了。

//导入Spring的配置文件让其生效
@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
public class BootHelloworldApplication {

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

}

多环境化配置Profile

properties

在真实的应用中,常常会有多个环境(如:开发,测试,生产等),不同的环境数据库连接都不一样,这个时候就需要用到spring.profile.active的强大功能了,它的格式为 application-{profile}.properties,这里的 application 为前缀不能改,{profile}是我们自己定义的。

application.properties就是默认的配置文件。

application.properties 配置文件中写入 spring.profiles.active=dev,就可以指定使用profile为dev的配置文件。

也可以使用虚拟机参数-Dspring.profiles.active=dev指定

yaml文档块

# document 1
server:
  port: 8081
spring:
  profiles:
    active: prod

---
# document 2
server:
  port: 8083
spring:
  profiles: dev

---
# document 3
server:
  port: 8084
spring:
  profiles: prod  #指定属于哪个环境

文档块之间使用---分隔,第一个文档块为默认使用的配置。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/springboot%e8%87%aa%e5%8a%a8%e9%85%8d%e7%bd%ae%e5%8e%9f%e7%90%86%e4%b8%8e%e8%87%aa%e5%ae%9a%e4%b9%89%e9%85%8d%e7%bd%ae%e4%bb%8b%e7%bb%8d/

发表评论

电子邮件地址不会被公开。