本章内容
自定义属性快速入门
外化配置
自动配置
自定义创建 Starter 组件
摘录: 读书是读完这些文字还要好好用心去想想, 写书也一样, 做任何事也一样
图 2 第二章目录结构图
第 2 章 Spring Boot 配置
Spring Boot 配置, 包括自动配置和外化配置. 本章先实现自定义属性工程, 将属性外化配置在 application.properties 应用配置文件, 然后在工程中获取该属性值. 接着会详细介绍属性的获取方式, 外化配置和自动配置. 最后会介绍利用自动配置自定义 Start 组件.
2.1 快速入门工程
第一章的 HelloBookController 控制层中, 在代码中以硬编码的方式使用字符串表示书信息. 下面把书的信息作为属性, 外化配置在 application.properties . 好处是将应用参数, 业务参数或第三方参数等统一配置在应用配置文件中, 避免配置侵入业务代码, 达到可配置的方式, 方便及时调整修改.
2.1.1 配置属性
新建工程命名为 chapter-2-spring-boot-config , 在 application.properties 中配置书名和作者, 配置如下:
- ## 书信息
- demo.book.name=[Spring Boot 2.x Core Action]
- demo.book.writer=BYSocket
.properties 文件的每行参数被存储为一对字符串, 即一个存储参数名称, 被称为键; 另一个为值. 一般称为键值对配置. 井号 (#) 或者英文状态下的叹号 (!) 作为第一行中第一个非空字符来表示该行的文本为注释. 另外, 反斜杠 (\) 用于转义字符.
Spring Boot 支持并推荐使用 YAML 格式的配置文件, 将 application.properties 文件替换成 application.YAML 文件, 并配置相同的属性, 配置如下:
## 书信息
demo:
book:
name: 《Spring Boot 2.x 核心技术实战 - 上 基础篇》
writer: 泥瓦匠 BYSocket
YAML 是一个可读性高, 用来表达数据序列的格式. 表示键值对格式时, 注意键和值由冒号及空白字符分开. 强调下, 空白字符是必须的, IDE 一般也会提示. 两种配置方式都非常便捷, 在开发中选择 .properties 或 .YAML 文件配置. 但如果两种配置文件同时存在的时候, 默认优先使用 .properties 配置文件. YAML 与 .properties 配置文件对比如图 2-1 所示:
图 2-1 YAML 与 .properties 配置文件对比
注意:
在 application.properties 配置中文值, 读取时会出现中文乱码问题. 因为 Java .properties 文件默认编码方式是 iso-8859 ,Spring Boot 应用以 UTF-8 的编码方式读取, 就导致出现乱码问题.
官方 Issue 中的解决方法是, 将 .properties 文件中配置的中文值转义成 Unicode 编码形式. 例如 demo.book.writer = 泥瓦匠 应该配置成 demo.book.writer=\u6ce5\u74e6\u5320 . 利用 IDEA properties 插件 或利用 Java 文件转码工具 native2ascii 来快速地进行转义. 该工具有在线版实现, 地址如下:
https://javawind.net/tools/native2ascii.jsp
2.1.2 创建属性类
在工程中新建包目录 demo.springboot.config , 并在目录中创建名为 BookProperties 的属性类, 代码如下:
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.stereotype.Component;
- /**
- * 书属性
- */
- @Component
- public class BookProperties {
- /**
- * 书名
- */
- @Value("${demo.book.name}")
- private String name;
- /**
- * 作者
- */
- @Value("${demo.book.writer}")
- private String writer;
- // ... 省略 getter / setter 方法
- }
利用 @Component 注解定义了书的属性 Bean, 并通过 @Value 注解为该 Bean 的成员变量 (或者方法参数) 自动注入 application.properties 文件的属性值.@Value 注解是通过 "${propName}" 的形式引用属性, propName 表示属性名称.
核心注解的知识点:
@Component 注解:
@Component 对类进行标注, 职责是泛指组件 Bean , 应用启动时会被容器加载并加入容器管理. 常见的 @Controller,@Service ,@Repository 是 @Component 的分类细化组件, 分别对应控制层, 服务层, 持久层的 Bean.
@Value 注解:
@Value 对 Bean 的字段或者方法参数进行标注, 职责是基于表达式给字段或方法参数设置默认属性值. 通常格式是注解 + SpEL 表达式, 如 @Value("SpEL 表达式").
使用 @Vlaue 注解来引用属性值时, 确保所引用的属性值在 application.properties 文件存在并且相对应匹配, 否则会造成 Bean 的创建错误, 引发 java.lang.IllegalArgumentException 非法参数异常.
2.1.3 获取属性
修改原有的 HelloBookController 类, 通过注入的方式获取书属性 Bean 并返回. 代码如下:
- import demo.springboot.config.BookProperties;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.Web.bind.annotation.RestController;
- @RestController
- public class HelloBookController {
- @Autowired
- BookProperties bookProperties;
- @GetMapping("/book/hello")
- public String sayHello() {
- return "Hello," + bookProperties.getWriter() + "is writing"
- + bookProperties.getName() + "!";
- }
- }
通过 @Autowired 注解标记在 BookProperties 字段, 控制层自动装配属性 Bean 并使用. 默认情况下要求被注解的 Bean 必须存在, 需要允许 NULL 值, 可以设置其 required 属性为 false: @Autowired(required = false).
2.1.4 运行工程
执行 ConfigApplication 类启动, 在控制台看到成功运行的输出后, 打开浏览器访问 /book/hello 地址, 可以看到如图 2-2 所示的返回结果:
图 2-2 Hello Book 页面
也可以通过单元测试的方式验证属性获取是否成功, 单元测试具体相关的会在第 9 章节介绍. 单元测试代码如下:
- import demo.springboot.config.BookProperties;
- import org.junit.Assert;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class ConfigApplicationTests {
- @Autowired
- BookProperties bookProperties;
- @Test
- public void testBookProperties() {
- Assert.assertEquals(bookProperties.getName(),"'Spring Boot 2.x Core Action'");
- Assert.assertEquals(bookProperties.getWriter(),"BYSocket");
- }
- }
2.2 配置属性的获取方式
配置属性的常用获取方式有基于 @Value 和 @ConfigurationProperties 注解两种方式. 两种方式适合的场景不同, 下面具体介绍其使用方法和场景.
2.2.1 @Value 注解
@Value 注解对 Bean 的变量或者方法参数进行标注, 职责是基于表达式给字段或方法参数设置默认属性值. 通常格式是注解 + SpEL 表达式, 如 @Value("SpEL 表达式"), 并标注在对应的字段或者方法上方, 且必须对变量一一标注. 这种方式适用于小而不复杂的属性结构. 属性结构复杂, 字段很多的情况下, 这种方式会比较繁琐, 应该考虑使用 @ConfigurationProperties 注解.
另外通过 @PropertySource 注解引入对应路径的其他 .properties 文件. 将书信息重新配置在 classpath 下新的 book.properties 配置文件后, 读取新配置文件的代码如下:
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.context.annotation.PropertySource;
- import org.springframework.stereotype.Component;
- /**
- * 书属性
- */
- @Component
- @PropertySource("classpath:book.properties")
- public class BookProperties {
- /**
- * 书名
- */
- @Value("${demo.book.name}")
- private String name;
- /**
- * 作者
- */
- @Value("${demo.book.writer}")
- private String writer;
- // ... 省略 getters / setters 方法
- }
2.2.2 @ConfigurationProperties 注解
在包目录 demo.springboot.config 中创建名为 BookComponent 的属性类, 并使用 @ConfigurationProperties 注解获取属性, 代码如下:
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
- /**
- * 书属性
- *
- */
- @Component
- @ConfigurationProperties(prefix = "demo.book")
- public class BookComponent {
- /**
- * 书名
- */
- private String name;
- /**
- * 作者
- */
- private String writer;
- // ... 省略 getters / setters 方法
- }
类似 @Value 注解方式, 使用 @ConfigurationProperties(prefix = "demo.book") 注解标注在类上方可以达到相同的效果. @ConfigurationProperties 注解的 prefix 是指定属性的参数名称. 会匹配到配置文件中 "demo.book.*" 结构的属性, 星号 "*" 是指会一一对应匹配 BookComponent 类的字段名. 例如, 字段 name 表示书名, 会匹配到 demo.book.name 属性值.
@Value 注解方式强制字段必须对应在配置文件, @ConfigurationProperties 注解方式则不是必须的. 一般情况下, 所有字段应该保证一一对应在配置文件. 如果没有属性值对应的话, 该字段默认为空, @ConfigurationProperties 注解方式也不会引发任何异常, Spring Boot 推荐使用 @ConfigurationProperties 注解方式获取属性.
同样使用单元测试验证获取属性是否成功. 单元测试代码如下:
- @Autowired
- BookComponent bookComponent;
- @Test
- public void testBookComponent() {
- Assert.assertEquals(bookComponent.getName(),"'Spring Boot 2.x Core Action'");
- Assert.assertEquals(bookComponent.getWriter(),"BYSocket");
- }
API org.springframework.boot.context.properties.ConfigurationProperties 注解参数
prefix
字符串值, 绑定该名称前缀的属性对象.
value
字符串值, 功能同 prefix 参数.
ignoreInvalidFields
布尔值, 默认 false. 绑定对象时, 忽略无效字段.
ignoreUnknownFields
布尔值, 默认 true. 绑定对象时, 忽略未知字段.
2.2.3 @ConfigurationProperties 数据验证
@ConfigurationProperties 注解方式支持验证功能, 即当属性类被 @Validated 注解标注时, Spring Boot 初始化时会验证类的字段. 在类的字段上添加 JSR-303 约束注解, 进行数据验证. 下面为书属性字段添加非 NULL 和字符串非空约束, 代码如下:
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
- import org.springframework.validation.annotation.Validated;
- import javax.validation.constraints.NotEmpty;
- import javax.validation.constraints.NotNull;
- /**
- * 书属性
- *
- */
- @Component
- @ConfigurationProperties(prefix = "demo.book")
- @Validated
- public class BookComponent {
- /**
- * 书名
- */
- @NotEmpty
- private String name;
- /**
- * 作者
- */
- @NotNull
- private String writer;
- // ... 省略 getters / setters 方法
- }
通过 @Validated 注解开启对 BookComponent 类字段的数据验证, 如果 name 字段为 NULL 或者为空字符串时, 会引发 BindValidationException 绑定数据验证异常. 数据验证常用在邮箱格式或者有长度限制的属性字段. 另外, 验证嵌套属性的值, 必须在嵌套对象字段上方标注 @Valid 注解, 用来触发其验证. 例如, 在书属性中新增嵌套对象出版社 Publishing, 就需要在该对象上方标注 @Valid 注解, 来开启对 Publishing 对象的数据验证. 综上, 两种属性获取方式各有优缺点, 对比如图 2-3 所示:
图 2-3 @ConfigurationPropertiesd vs @Value
2.3 外化配置
Spring Boot 可以将配置外部化, 即分离存储在 classpath 之外, 这种模式叫做 "外化配置". 常用在不同环境中, 将配置从代码中分离外置, 只要简单地修改下外化配置, 可以依旧运行相同的应用代码. 外化配置表现形式不单单是 .properties 和 .YAML 属性文件, 还可以使用环境变量和命令行参数等来实现. 那么, 多处配置了相同属性时, Spring Boot 是通过什么方式来控制外化配置的冲突呢? 答案是外化配置优先级.
2.3.1 外化配置优先级
用命令行配置去覆盖 .properties 文件配置方法很简单. 正常情况下利用 Java 命令运行工程, 代码如下:
- // chapter-2-spring-boot-config 目录下运行
- java -jar target/chapter-2-spring-boot-config-1.0.jar
- ## 书信息
- demo.book.name=[Spring Boot 2.x Core Action]
- demo.book.writer=BYSocket
- demo.book.description=${
- demo.book.writer
- }'s${
- demo.book.name
- }
- my.secret=${
- random.value
- }
- my.number=${
- random.int
- }
- my.bignumber=${
- random.long
- }
- my.uuid=${
- random.uuid
- }
- my.number.Less.than.ten=${
- random.int(10)
- }
- my.number.in.range=${
- random.int[1024,65536]
- }
- ## 书信息
- demo.book.name=[Spring Boot 2.x Core Action] From Dev
- demo.book.writer=BYSocket
- ## 书信息
- demo.book.name=<Spring Boot 2.x Core Action Dev> From Prod
- demo.book.writer=BYSocket
- org.springframework.boot.autoconfigure
- org.springframework.boot.autoconfigure.data.jpa
- org.springframework.boot.autoconfigure.thymeleaf
- org.springframework.boot.autoconfigure.Web.servlet
- org.springframework.boot.autoconfigure.Web.reactive
- JpaRepositoriesAutoConfiguration
- ThymeleafAutoConfiguration
- WebMvcAutoConfiguration
- WebFluxAutoConfiguration
- package org.springframework.boot.autoconfigure.Web.servlet;
- @Configuration
- @ConditionalOnClass({ServletRequest.class})
- @ConditionalOnWebApplication(
- type = Type.SERVLET
- )
- @EnableConfigurationProperties({ServerProperties.class})
- @Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class})
- public class ServletWebServerFactoryAutoConfiguration {
- @SpringBootApplication
- @EnableAutoConfiguration(exclude = {
- DataSourceAutoConfiguration.class
- })
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-configuration-processor</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>${version.swagger}</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>${version.swagger}</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-bean-validators</artifactId>
- <version>${version.swagger}</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.16.12</version>
- <scope>provided</scope>
- </dependency>
- </dependencies>
import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.boot.context.properties.ConfigurationProperties; import springfox.documentation.schema.ModelRef; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @Data @ConfigurationProperties("swagger") public class SwaggerProperties { /** 是否开启 swagger**/ private Boolean enabled; /** 标题 **/ private String title = ""; /** 描述 **/ private String description = ""; /** 版本 **/ private String version = ""; /** 许可证 **/ private String license = ""; /** 许可证 URL**/ private String licenseUrl = ""; /** 服务条款 URL**/ private String termsOfServiceUrl = ""; private Contact contact = new Contact(); /**swagger 会解析的包路径 **/ private String basePackage = ""; /**swagger 会解析的 url 规则 **/ private List<String> basePath = new ArrayList<>(); /** 在 basePath 基础上需要排除的 url 规则 **/ private List<String> excludePath = new ArrayList<>(); /** 分组文档 **/ private Map<String, DocketInfo> docket = new LinkedHashMap<>(); /**host 信息 **/ private String host = ""; /** 全局参数配置 **/ private List<GlobalOperationParameter> globalOperationParameters;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @Configuration @ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true) @Import({ Swagger2DocumentationConfiguration.class, BeanValidatorPluginsConfiguration.class }) public class SwaggerAutoConfiguration implements BeanFactoryAware { private BeanFactory beanFactory; @Bean @ConditionalOnMissingBean public SwaggerProperties swaggerProperties() { return new SwaggerProperties(); } @Bean @ConditionalOnMissingBean @ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true) public List<Docket> createRestApi(SwaggerProperties swaggerProperties) {
} @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } }
BeanValidatorPluginsConfiguration . @ConditionalOnMissingBean
@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({ SwaggerAutoConfiguration.class }) public @interface EnableSwagger2Doc { }
<!-- 自定义 swagger2 Starter 组件依赖 --> <dependency> <groupId>com.spring4all</groupId> <artifactId>spring-boot-starter-swagger</artifactId> <version>2.0</version> </dependency>
import com.spring4all.swagger.EnableSwagger2Doc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @EnableSwagger2Doc // 开启 Swagger @SpringBootApplication public class ConfigApplication { public static void main(String[] args) { SpringApplication.run(ConfigApplication.class, args); } }
来源: https://www.cnblogs.com/Alandre/p/10494614.html