工程配置

SpringBoot工程包含了大量的约定配置,我们也可以通过application.properties覆盖约定配置,另外我们还可以通过JavaConfig等方式覆盖配置,我们自己的程序代码也可以读取application.properties中的配置,SpringBoot的配置方式的复杂但灵活的。本片笔记我们详细介绍SpringBoot工程配置相关的内容。

SpringBoot自动约定配置

自动约定配置是SpringBoot的核心特性之一。如果不使用SpringBoot搭建Spring项目,我们需要编写大量的XML(或JavaConfig方式),来装配配置Bean。而SpringBoot采用约定大于配置的思想,将大量配置的最佳实践集成到了框架内部,我们搭建项目时就不必从头编写各种配置了,只需要在原配置基础上覆盖默认即可。

自动约定配置虽然用起来简单,也没什么可讲的,但我们需要真正了解其内部的原理,才能用得放心。这里我们就学习其中的基本概念和框架内部的相关代码逻辑。

@SpringBootApplication注解分析

在使用SpringBoot之前,我们首先需要了解SpringBoot是如何进行自动配置的。在手动配置Spring时代我们编写的大段XML被放在了哪里,以及我们编写的自定义配置应该放在哪里。

下面是一个SpringBoot的启动类:

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

我们可以看到,SpringBoot的启动类上标注了@SpringBootApplication注解,我们查看其源码,其实它是一个复合的注解,其中主要包括@SpringBootConfiguration@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 {
    // ...
}

查看源码我们可以发现,@SpringBootConfiguration其实和@Configuration的作用相同,都是用来标注配置类:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    // ...
}

@EnableAutoConfiguration也是一个复合注解,包括@AutoConfigurationPackage@Import

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    // ...
}

AutoConfigurationImportSelector用于扫描加载SpringBoot内置的配置类。我们可以在spring-boot-autoconfigure包中的META-INF/spring.factories文件中找到自动配置类的列表。

@AutoConfigurationPackage中也包含了@Import注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
    // ...
}

AutoConfigurationPackages.Registrar用于扫描启动类包的子包中的组件注册到Spring容器。

总结来说,SpringBoot框架默认会从启动类和spring-boot-autoconfigure定义的自动配置类注册Spring配置,此外还会扫描启动类包的子包中的组件。

spring-boot-autoconfigure包

spring-boot-autoconfigure包中包含了大量自动配置,我们具体开发如果用到相关功能,推荐查看一下其源码,简要了解都配置了哪些内容:

@ComponentScan 自定义注解扫描路径

前面我们了解到,SpringBoot会扫描启动类包的子包中的组件,然而如果我们定义的SpringBean不在启动类包的子包下,就需要手动指定扫描包。

SpringBoot提供了@ComponentScan注解,我们可以将其标注到启动类上。

例子:

@SpringBootApplication
@ComponentScan({"com.gacfox.bootdemo"})
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

application.properties配置文件

如果我们使用Spring Initializr工程模板,我们会发现工程内存在一个src/main/resources/application.properties配置文件。自动配置类中用到的例如数据库URL、端口等信息,我们可以在该文件中指定,SpringBoot自动配置生效时,会自动应用该配置文件中的值来覆盖默认值。除了可以使用application.properties,SpringBoot也支持yaml格式,根据我们个人习惯选择即可,但二者选其一,要么全用properties要么全用yaml,不要同时使用以免造成配置分散和管理混乱。

由于SpringBoot的spring-boot-autoconfigure定义了大量的约定配置,我们这里不可能全部列举介绍,具体使用时我们按照文档对application.properties进行配置即可。

多个application.properties的加载顺序

除了src/main/resources/application.properties,这个配置文件也可以放在其它地方,按照优先级从高到低有如下位置:

  1. 当前目录下的config文件夹
  2. 当前目录
  3. classpath下的config目录
  4. classpath根目录

优先级高的配置会覆盖优先级低的配置。

注意:不要滥用配置的优先级规则!工程的配置文件应该是简单、明确的,千万不要把同一个配置分散到超过两个地方,如果你写了一堆类似的配置文件然后利用它们的覆盖优先级规则生成最终的配置,这会给配置文件的维护造成极大困难!

使用命令行参数指定配置文件

除了上面默认查找配置文件的几个目录,SpringBoot还可以在启动参数中指定配置文件路径。实际上,我更推荐使用这些参数明确指定配置文件,这样更利于配置文件的维护。

java -Dspring.config.location=/home/ubuntu/application.properties -jar springboot.jar
  • -Dspring.config.location:指定配置文件路径,这里可以指定多个(使用逗号分隔),后指定的相同配置键会覆盖前面的

明确指定一个或一组配置文件位置后,前面介绍的4个优先级位置将被覆盖掉。

bootstrap.properties配置文件

除了application.properties实际上我们还可以指定一个bootstrap.properties,它比application.properties先加载,用于应用程序上下文的引导阶段,但bootstrap.properties的优先级更高且不可被本地相同的配置键值覆盖。

有些特殊的框架可能要求配置必须写在bootstrap.properties,比如SpringCloudConfig,该组件的配置就必须这样配置,以引导外部配置中心的配置加载。

覆盖JavaConfig配置

仅仅覆盖application.properties中的配置项,在某些极特殊情况下可能不能满足我们的需求,实际上spring-boot-autoconfigure包中的自动配置类我们也可以覆盖。

这里我们简单分析一下,其实我们可以打开任意一个SpringBoot的自动配置类,下面是一个例子:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

我们看到JavaConfig装配的Bean标注了@ConditionalOnMissingBean注解,该注解表示如果我们自定义了配置Bean,使用我们自定义的;如果只有该Bean存在,才使用该配置对象。

那么如果我们想覆盖默认的JavaConfig也就比较简单了,我们按照自动配置类定义的Bean,注册我们自己的JavaConfig配置类即可。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。