SpringBoot从入门到精通系列二:应用配置与自动配置
一、SpringApplication与Spring容器
SpringApplication是Spring Boot提供的一个工具类,提供了run()方法来启动Spring容器,运行SpringBoot应用。
1.类配置与XML配置
传统Spring框架大多采用XML文件作为配置文件,但SpringBoot推荐使用Java配置类(带@Configuration注解)作为配置文件。
推荐用@Configuration修饰带main()方法的主类,则该主类也是Spring Boot应用的配置源。
除了加载主配置源中所有的配置,SpringBoot还会自动扫描主配置源所在的包及其子包下所有带@Component注解(@Configuration、@Controller、@Service、@Repository都是@Component的变体)的配置类或Bean组件
SpringBoot可使用如下三个注解加载其他配置类或者扫描其他包下的配置类或Bean组件:
- @Import:该注解显示指定SpringBoot要加载的类
- @ComponentScan:该注解指定SpringBoot扫描指定包及其子包下所有的配置类或Bean组件
- @ImportResource:使用@ImportResource注解来导入XML配置文件
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
// 额外指定扫描org.crazyit.app和org.fkit.app包及其子包下所有配置类和Bean组件
@SpringBootApplication(scanBasePackages = {"org.crazyit.app", "org.fkit.app"})
// 加载类加载路径下beans.xml文件作为配置文件
@ImportResource("classpath:beans.xml")
// 加载cn.fkjava.app包下的MyConfig文件作为配置类
@Import(cn.fkjava.app.MyConfig.class)
public class App
{
public static void main(String[] args)
{
// 创建Spring容器、运行Spring Boot应用
SpringApplication.run(App.class, args);
}
}
- 配置类使用@Bean注解配置了一个DateFormat Bean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.text.DateFormat;
@Configuration
public class MyConfig
{
@Bean
public DateFormat dateFormat()
{
return DateFormat.getDateInstance();
}
}
下面提供一个简单的控制器类,Spring容器会将上面通过不同方式配置的Bean注入该控制器。
- dog类
import org.springframework.stereotype.Component;
@Component
public class Dog
{
public String bark()
{
return "来自Dog的测试方法";
}
}
- bird类
<?xml version="1.0" encoding="utf-8"?>
<!-- Spring配置文件的根元素,使用spring-beans.xsd语义约束 -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 用XML配置Bean组件 -->
<bean id="bird" class="org.fkjava.app.Bird"/>
</beans>
package org.fkjava.app;
import org.springframework.stereotype.Component;
public class Bird
{
public String fly()
{
return "来自Bird的fly()方法";
}
}
import org.fkit.app.Dog;
import org.fkjava.app.Bird;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.DateFormat;
import java.util.Date;
@RestController
public class HelloController
{
// 依赖注入容器中的Dog类型的Bean
@Autowired
private Dog dog;
// 依赖注入容器中的Bird类型的Bean
@Autowired
private Bird bird;
// 依赖注入容器中的DateFormat类型的Bean
@Autowired
private DateFormat dateFormat;
@GetMapping("/")
public String test()
{
return "Hello, " +
dog.bark() + ", " +
bird.fly() + ", " +
dateFormat.format(new Date());
}
}
运行上面的App主类启动SpringBoot应用,使用浏览器访问"http://localhost:8080"测试上面的test()方法,可以看到Spring容器完成了dog、bird、dataFormat这三个实例变量的依赖注入,意味着前面三种方式都配置成功了。
2.启动日志和失败分析器
使用SpringApplication的静态run()方法来运行SpringBoot应用,则默认显示INFO级别的日志信息,包括一些启动相关的详情。
想关闭启动信息的日志,则可将如下属性设为false。
spring.main.log-startup-info=false
在application.properties文件中添加如下配置,可以将ConditionEvaluationReportLoggingListener的日志级别设为DEBUG
logging.level.org.springframework.boot.autoconfigure.logging=debug
通过JAR包来运行SpringBoot应用,可通过"–debug"选项来开启debug属性。例如如下命令:
java -jar firstboot-0.0.1-SNAPSHOT.jar --debug
二、外部配置源
SpringBoot允许使用配置文件对应用程序进行配置,SpringBoot支持如下不同形式的配置源。
- 属性文件(application.properties)
- YAML文件(后缀是.yml或.yaml)
- 环境变量
- 命令行参数
获取这些外部化的属性主要有如下几种方式:
- 使用@Value注解将属性值插入任何Bean组件
- 使用Spring的Environment抽象层进行访问
- 使用@ConfigurationProperties注解将一批特定属性绑定到指定Java对象
1.配置源的加载顺序与优先级
所有先加载的配置源都可能被后加载的配置源覆盖,因此可认为后加载的配置源的优先级更高。
- 默认属性(通过SpringApplication.setDefaultProperties()方法指定)
- 配置类(@Configuration修饰的类)上用@PropertySource注解加载的属性文件中的属性值
- 配置数据(application.properties文件等)
- 命令行参数
- 测试用例类上通过@SpringBootTest注解的properties所指定的属性
- 测试用例类上用@TestPropertySource注解加载的属性文件中的属性值
2.使用YAML配置文件
程序要加载YAML文件配置的属性,则SpringBoot提供了如下工具类。
- YamlPropertiesFactoryBean:将YAML文件加载为Properties对象
- YamlMapFactoryBean:将YAML文件加载为Map对象
- YamlPropertySourceLoader:将YAML文件加载为PropertySource
\resource\fk\fk.yml
fkjava:
name: "疯狂软件"
age: 20
servers:
- www.fkjava.org
- www.crazyit.org
\resource\application.yml
fkjava:
server:
name: "疯狂软件服务器"
port: 9000
定义一个环境配置后处理器来加载这份fk.yml文件,该后处理器的代码如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.IOException;
public class FkEnvironmentPostProcessor implements EnvironmentPostProcessor
{
// 创建YamlPropertySourceLoader,用于加载YAML文件
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application)
{
// 指定自定义的配置文件
Resource path = new ClassPathResource("fk/fk.yml");
// 加载自定义配置文件
PropertySource<?> ps = null;
try
{
ps = this.loader.load("custom-resource", path).get(0);
}
catch (IOException e)
{
e.printStackTrace();
}
System.out.println("fkjava.name: " + ps.getProperty("fkjava.name"));
System.out.println("fkjava.age: " +ps.getProperty("fkjava.age"));
System.out.println("fkjava.servers[0]: " +ps.getProperty("fkjava.servers[0]"));
System.out.println("fkjava.servers[1]: " +ps.getProperty("fkjava.servers[1]"));
// 将PropertySource中的属性添加到Environment配置环境中
environment.getPropertySources().addLast(ps);
}
}
- private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();创建了YamlPropertySourceLoader对象
- ps = this.loader.load(“custom-resource”, path).get(0);调用了该对象的load()方法来加载YAML配置文件
还需要使用META-INF/spring.factories文件来定义该配置文件后处理器,文件内容如下:
# 定义配置环境后处理器
org.springframework.boot.env.EnvironmentPostProcessor=\
org.crazyit.app.FkEnvironmentPostProcessor
这样一来,默认的application.yml和fk/fk.yml文件都被加载进来,接下来即可在其他任何Bean组件(如控制器)中通过@Value注解来访问它们。
\controller\HelloController.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController
{
// 使用@Value注解访问配置属性
@Value("${fkjava.server.name}")
private String serverName;
@Value("${fkjava.server.port}")
private String serverPort;
@Value("${fkjava.age}")
private String age;
@GetMapping
public String hello()
{
return "名称: " + serverName + ", 端口: " + serverPort
+ ", 年龄: " + age;
}
}
通过浏览器访问"http://localhost:8080/"测试上面的hello()方法,将看到如下输出:
名称: 疯狂软件服务器,端口:9000,年龄:20
如果仅仅需要加载自定义的YAML文件,在普通组件中使用这配置属性,并不需要将YAML文件中的属性添加到配置环境中,那么只要在容器中配置一个YamlPropertiesFactoryBean工厂Bean或YamlMapFactoryBean工厂Bean,它们就会自动读取YAML文件,并将其中的配置内容加载为Properties对象或Map对象
例如,下面的示例对上面的示例进行一些修改,删除其中的配置环境后处理器,然后添加如下配置类。
\app\App.java
package org.crazyit.app;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
@Configuration
public class MyConfig
{
// 在容器中配置一个YamlPropertiesFactoryBean
@Bean
public YamlPropertiesFactoryBean fkProps()
{
var factory = new YamlPropertiesFactoryBean();
factory.setResources(new ClassPathResource("fk/fk.yml"));
return factory;
}
}
上面的配置类在容器中配置了一个YamlPropertiesFactoryBean工厂Bean,并指定该工厂Bean要加载fk/fk.yml文件。Spring容器中的工厂Bean(实现FactoryBean接口的Bean)有一个特征:
- 当程序通过Spring容器获取工厂Bean时,Spring容器实际返回的是该工厂Bean的产品(getObject()方法返回的值)。
- 因此,当程序获取上面配置的fkProps时,实际返回的只是一个Properties对象
接下来,其他Bean组件(如控制器)则可通过如下方式来访问fk/fk.yml文件中的属性
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Properties;
@RestController
public class HelloController
{
// 使用@Value注解访问配置属性
@Value("${fkjava.server.name}")
private String serverName;
@Value("${fkjava.server.port}")
private String serverPort;
// 指定将容器中fkProps Bean注入fkProps实例变量
@Resource(name = "fkProps")
private Properties fkProps;
@GetMapping
public String hello()
{
return "名称: " + serverName + ", 端口: " + serverPort
+ ", 年龄: " + fkProps.getProperty("fkjava.age");
}
}
- @Resource(name = “fkProps”) private Properties fkProps;指定将容器中的fkProps Bean注入fkProps实例变量,而fkProps就是上面配置类中配置的YamlPropertiesFactoryBean的产品,也就是它所加载的YAML文件转换得到的Properties对象
三、类型安全的绑定
- @Value注解来读取配置文件中的属性,但使用@Value注解每次只能读取一个配置属性
- 若需要整体读取多个属性,或者读取具有某种结构关系的一组属性,SpringBoot则提供了@ConfigurationProperties注解来进行处理
@ConfigurationProperties注解有两种主要用法:
- 修饰属性处理类:当@ConfigurationProperties注解修饰的类被部署为容器中的Bean时,该注解指定的属性将会被注入该Bean的属性,因此,将@ConfigurationProperties注解修饰的类称为属性处理类
- 修饰@Bean注解修饰的方法:使用@Bean修饰的方法将会配置一个容器中的Bean,@ConfigurationProperties注解指定的属性将会被注入该Bean的属性
在使用@ConfigurationProperties注解时可指定如下属性:
- prefix(value):指定要加载的属性的前缀
- ignoreInvalidFields():指定是否忽略无效属性值。比如处理类定义了某个字段的类型是Integer,但在配置文件中为该字段配置的值是abc,这就是无效的值。
- ignoreUnknownFields():指定是否忽略未知的字段值,如果在配置文件中配置的属性比处理类需要的属性更多,那么多出来的属性就属于未知属性。
1.使用属性处理类获取配置属性
使用@ConfigurationProperties注解修饰类的例子。
\src\main\resources\application.properties
org.crazyit.enabled=true
org.crazyit.name=Crazy Java
org.crazyit.remoteAddress=192.168.1.188
org.crazyit.item.brand=Tesla
org.crazyit.item.comments=Good, Excellent
接下来定义如下带@ConfigurationProperties注解的属性处理类来处理上面的配置信息
\app\config\CrazyitProperties.java
package org.crazyit.app.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// 指定读取以org.crazyit.*开头的属性
@ConfigurationProperties(prefix = "org.crazyit", ignoreUnknownFields=false)
@Component
public class CrazyitProperties
{
private boolean enabled;
private String name;
private InetAddress remoteAddress;
private final Item item = new Item();
public boolean isEnabled()
{
return this.enabled;
}
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public InetAddress getRemoteAddress()
{
return remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress)
{
this.remoteAddress = remoteAddress;
}
public Item getItem()
{
return item;
}
public static class Item
{
private String brand;
private List<String> comments = new ArrayList<>(Collections.singleton("GREAT"));
public String getBrand()
{
return brand;
}
public void setBrand(String brand)
{
this.brand = brand;
}
public List<String> getComments()
{
return comments;
}
public void setComments(List<String> comments)
{
this.comments = comments;
}
}
}
- 使用了@ConfigurationProperties注解修饰CrazyitProperties类,因此该类会被配置成容器中的Bean,且配置文件中以"org.crazyit"开头的属性将会被注入该Bean实例。
IDEA开发属性处理类,会提示添加spring-boot-configuration-processor依赖,添加该依赖后IDEA可提供自动补全功能。
SpringBoot不会自动启用@ConfigurationProperties注解,让SpringBoot启用该注解有如下方式:
- 为@ConfigurationProperties注解修饰的类添加@Component注解
- 将@ConfigurationProperties注解修饰的类显示配置成容器中的Bean
- 使用@EnableConfigurationProperties注解,该注解可显示指定一个或多个属性处理类,SpringBoot将会启用这些属性处理类上的@ConfigurationProperties注解
- 使用@ConfigurationPropertiesScan注解,该注解可指定启用一个或多个包及其子包下所有带@ConfigurationProperties注解的类
当该属性处理类被配置成容器中的Bean之后,接下来该Bean可被注入任何其他Bean组件(如控制器),这个其他Bean组件即可通过该属性处理类的实例来读取所有以org.crazyit开头的属性。
import org.crazyit.app.config.CrazyitProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController
{
private final CrazyitProperties crazyitProperties;
// 依赖注入CrazyitProperties属性处理Bean
@Autowired
public HelloController(CrazyitProperties crazyitProperties)
{
this.crazyitProperties = crazyitProperties;
}
@GetMapping
public CrazyitProperties hello()
{
return crazyitProperties;
}
}
- 这段代码将容器中的CrazyitProperties依赖注入控制器,这样该控制器即可通过CrazyitProperties来访问所有以“org.crazyit”开头的属性
@Autowired
public HelloController(CrazyitProperties crazyitProperties)
{
this.crazyitProperties = crazyitProperties;
}
启动该应用,使用浏览器访问"http://localhost:8080/"来测试hello()方法,将会看到如下输出:
{
“enabled”: true,
"name": "java",
"remoteAddress": "192.168.1.188",
"item": {
"brand": "Tesla",
"comments": ["Good","Exception"]
}
}
- 从输出来看,使用@ConfigurationProperties注解修饰的CrazyitProperties可整体读取所有以"org.crazyit"开头的属性,确实非常方便
- 刚刚定义的CrazyitProperties为每个配置属性都提供了同名的实例变量和setter方法,这种SpringBoot会通过反射调用这些setter方法来完成属性值注入
实际上,属性处理类同样也支持用构造器来完成属性值注入,只要额外使用@ConstructorBinding注解修饰或@ConfigurationPropertiesScan注解来启用@ConfigurationProperties注解
\config\CrazyitProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ConfigurationProperties(prefix = "org.crazyit", ignoreUnknownFields=false)
public class CrazyitProperties
{
private boolean enabled;
private String name;
private InetAddress remoteAddress;
private final Item item;
@ConstructorBinding
public CrazyitProperties(boolean enabled, String name, InetAddress remoteAddress, Item item)
{
this.enabled = enabled;
this.name = name;
this.remoteAddress = remoteAddress;
this.item = item;
}
public boolean isEnabled()
{
return this.enabled;
}
public String getName()
{
return name;
}
public InetAddress getRemoteAddress()
{
return remoteAddress;
}
public Item getItem()
{
return item;
}
public static class Item
{
private String brand;
private List<String> comments = new ArrayList<>(Collections.singleton("GREAT"));
public String getBrand()
{
return brand;
}
public void setBrand(String brand)
{
this.brand = brand;
}
public List<String> getComments()
{
return comments;
}
public void setComments(List<String> comments)
{
this.comments = comments;
}
}
}
- 上面的属性处理类并未为实例变量定义setter方法,而是定义了一个带参数的构造器,且该构造器使用了@ConstructorBinding修饰,这样SpringBoot会使用构造器来完成属性值的注入
\src\main\resources\application.yml
org:
crazyit:
enabled: true
name: java
remote-address: 192.168.1.188
item:
brand: Apple
comments:
- Good
- Excellent
留意上面配置的属性为org.crazyit.remote-address,与CrazyitProperties类中定义的remoteAddress属性并不完全相同,但SpringBoot支持所谓的宽松绑定。
- 宽松绑定并不要求配置属性的属性名与属性处理类中的属性名完全相同。
对于用构造器执行属性值注入的属性处理类,要求使用@ConfigurationPropertiesScan或@EnableConfigurationProperties注解来启用@ConfigurationProperties,因此本例在应用主类上增加了@ConfigurationPropertiesScan注解。
import org.crazyit.app.config.CrazyitProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
// 指定扫描org.crazyit.app.config包及其子包下的@ConfigurationProperties注解修饰的类
@ConfigurationPropertiesScan("org.crazyit.app.config")
public class App
{
public static void main(String[] args)
{
// 创建Spring容器、运行Spring Boot应用
SpringApplication.run(App.class, args);
}
}
- @ConfigurationPropertiesScan(“org.crazyit.app.config”)指定了要启用org.crazyit.app.config包及其子包下所有属性处理类上的@ConfigurationProperties注解
2.为容器中的Bean注入配置属性
@ConfigurationProperties注解除了可修饰属性处理类,还可修饰@Bean注解修饰的方法,这样SpringBoot将会读取@ConfigurationProperties注解加载的配置属性,并将属性值注入该@Bean方法所配置的Bean组件
示例:定义一个Book类
import java.util.List;
public class Book
{
private String title;
private double price;
private String author;
private List<String> keywords;
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public double getPrice()
{
return price;
}
public void setPrice(double price)
{
this.price = price;
}
public String getAuthor()
{
return author;
}
public void setAuthor(String author)
{
this.author = author;
}
public List<String> getKeywords()
{
return keywords;
}
public void setKeywords(List<String> keywords)
{
this.keywords = keywords;
}
}
接下来使用Java配置该Bean类,并使用@ConfigurationProperties注解修饰该@Bean方法。
\src\main\java\org\crazyit\app\MyConfig.java
import org.crazyit.app.domain.Book;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig
{
@Bean
// @ConfigurationProperties注解会驱动Spring自动调用该Bean的setter方法
@ConfigurationProperties("fkjava.book")
public Book book()
{
return new Book();
}
}
- @Bean注解的book()方法配置了Book类的实例。上面的配置只是创建了一个默认的Book对象,并没有为之设置任何属性。
- 但由于该book()方法使用了@ConfigurationProperties(“fkjava.book”)注解修饰,SpringBoot将会自动读取所有以"fkjava.book"开头的属性,并将这些属性值对应地注入该Book对象
fkjava:
book:
title: "认真学java"
price: 128
author: "fypyt"
keywords:
- Java
- Spring
- python
上面配置文件中配置的属性与Book类的属性对应,SpringBoot就会读取这些配置属性,并将它们注入Book Bean
- 从本质上看,@ConfigurationProperties注解的作用就是驱动被修饰的@Bean方法所配置的对象调用相应的setter方法,比如@ConfigurationProperties注解读取到title属性,它就会驱动SpringBoot以反射方式执行@Bean方法所配置的对象的setTitle()方法,并将title属性值作为setTitle()方法的参数
- 通过使用@ConfigurationProperties修饰@Bean方法,让SpringBoot将属性值注入@Bean方法所配置的Bean组件
当容器有了属性完备的Book对象之后,接下来可将它注入任何Bean组件(如控制器组件)
import org.crazyit.app.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController
{
private final Book book;
@Autowired
public HelloController(Book book)
{
this.book = book;
}
@GetMapping
public Book hello()
{
return book;
}
}
上面的控制器接受容器注入的Book Bean
运行主类,启动SpringBoot应用,使用浏览器访问"http://localhost:8080/"测试hello()方法,将会看到如下输出:
{
"title":"认真学java",
"price":128,"author":"fypyt",
"keywords":["Java","Spring","python"]
}
@ConfigurationProperties与@Value特点
- @Value注解读取配置属性简单、方便(只要该注解修饰实例变量即可)
- 但每个@Value注解只能注入一个配置属性
- @ConfigurationProperties可整体注入一批配置属性,但他需要一个额外定义的属性处理类,即使修饰@Bean方法,也需要有一个@Bean类
- @Value对宽松绑定并不完全支持
3.校验@ConfigurationProperties
SpringBoot还可对属性处理类进行校验,只要为属性处理类添加@Validated注解,并使用JSR 303的校验注解修饰需要校验的实例变量,SpringBoot会自动校验配置文件中的属性值。
添加依赖:
<!-- 添加Spring Boot Validation依赖库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
为属性处理类添加校验注解
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// 指定读取以org.crazyit.*开头的属性
@ConfigurationProperties(prefix = "org.crazyit", ignoreUnknownFields=false)
@Component
@Validated
public class CrazyitProperties
{
@NotEmpty
private String name;
@Range(max = 150, min=90, message = "价格必须位于90~150之间")
private double price;
@Pattern(regexp = "[1][3-8][0-9]{9}", message = "必须输入有效的手机号")
private String mobile;
@Valid
private final Item item = new Item();
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public double getPrice()
{
return price;
}
public void setPrice(double price)
{
this.price = price;
}
public String getMobile()
{
return mobile;
}
public void setMobile(String mobile)
{
this.mobile = mobile;
}
public Item getItem()
{
return item;
}
public static class Item
{
@Length(min=5, max=10, message = "品牌名长度必须在5到10个字符")
private String brand;
@Size(min = 1, message = "comments至少包含一个元素")
private List<String> comments = new ArrayList<>(Collections.singleton("GREAT"));
public String getBrand()
{
return brand;
}
public void setBrand(String brand)
{
this.brand = brand;
}
public List<String> getComments()
{
return comments;
}
public void setComments(List<String> comments)
{
this.comments = comments;
}
}
}
- 上面的属性处理类使用了@Validated注解修饰,且其中的name、price、mobile等属性使用了@NotEmpty、@Range、@Pattern注解修饰,因此SpringBoot将会对它们对应的配置属性进行校验。
- @Valid
private final Item item = new Item(); 属性处理类中的item属性是Item类型的,程序使用了@Valid修饰它,这样可保证SpringBoot对Item类包含的属性也执行数据校验
四、Profile
Profile就是一组配置环境、各种程序组件的合集。
在实际开发环境中,经常需要在不同的环境间切换,比如开发项目时用的是开发环境,测试项目时用的是测试环境。
Profile可包括程序组件和配置文件,声明程序组件和配置文件的Profile:
- 使用@Profile注解修饰@Component、@Configuration、@ConfigurationProperties等注解修饰的类,这限制了这些类仅对特定的Profile有效
- 通过配置文件的文件名限制Profile。比如:application-dev.properties(application-dev.yml)、application-test.properties(application-test.yml)
- 在配置文件中使用特定语法限制某些属性仅对特定的Profile有效,这种特殊的配置文件被称为多Profile配置文件
spring:
datasource:
# 指定连接prod数据库
url: jdbc:mysql://localhost:3306/prod?serverTimezone=UTC
username: root
password: 32147
\app\controller\ProdController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
@RestController
@Profile("prod")
public class ProdController
{
private final DataSource dataSource;
@Autowired
public ProdController(DataSource dataSource)
{
this.dataSource = dataSource;
}
@GetMapping
public Map<String, String> hello() throws SQLException
{
return Map.of("class", "产品场的控制器","数据库",
dataSource.getConnection().getCatalog());
}
}
在SpringBoot中使用Profile只需要:
- 配置文件通过文件名限制所属的Profile
- 程序组件通过@Profile注解来限制所属的Profile
在运行应用时,可通过spring.profiles.active属性指定激活哪个Profile。
- 通过application.properties文件指定
- 使用操作系统的SPRING_PROFILES_ACTIVE环境变量指定
- 使用系统属性指定
- 使用命令行参数指定
遵循越早加载,优先级越低的规则,通过命令行参数指定的spring.profiles.active属性会覆盖前面几种方式指定的属性
五、日志配置
1.理解SpringBoot日志设计
通过SpringBoot提供的日志抽象层可以非常方便地管理应用的日志输出。
只要在项目中导入spring-boot-starter.jar依赖,就会传递导入spring-boot-starter-logging.jar。
spring-boot-starter-logging.jar依赖如下三个JAR包:
- logback-classic.jar:传递依赖于logback-core.jar和slf4j-api.jar
- log4j-to-slf4j.jar:传递依赖于logback-core.jar和slf4j-api.jar
- jul-to-slf4j.jar:传递依赖于slf4j-api.jar
Java领域日志框架包括:SLF4J、Log4j、Logback等
这些日志框架又可分为:
- 门面类(抽象层):SLF4J、JCL、JBoss Logging
- 日志实现:Log4j、Log4j2、Logback、JUL
SpringBoot默认使用SLF4J+Logback的日志组合,其中SLF4J作为日志门面(应用程序输出日志时也应该面向该API),Logback作为日志实现,开发者通常不需要直接操作日志实现的API。因此,SpringBoot默认会添加SLF4J依赖和Logback依赖。
由于SpringBoot框架需要整合大量第三方框架,这些框架底层可能会使用JCL、Log4j、JUL等日志,因此SpringBoot还要提供对应的日志路由,将其他日志框架所生成的日志信息统一路由给SLF4J来处理。
从依赖关系可以看到:
- log4j-to-slf4j.jar:负责将Log4j日志路由到SLF4J
- jul-to-slf4j.jar:负责将JUL日志路由到SLF4J
2.日志级别与格式
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class HelloController
{
Logger logger = LoggerFactory.getLogger(this.getClass());
@GetMapping
public Map<String, Object> hello()
{
logger.trace("-------TRACE级别的日志-------");
logger.debug("-------DEBUG级别的日志-------");
logger.info("-------INFO级别的日志-------");
logger.warn("-------WARN级别的日志-------");
logger.error("-------ERROR级别的日志-------");
return Map.of("hello", "Hello");
}
}
5个不同的日志级别:
- TRACE
- DEBUG
- INFO
- WARN
- ERROR
日志系统规则:
- 只有当日志输出方法的级别高于或等于日志的设置级别时,该日志才会实际输出
SpringBoot输出的每条日志都包括以下信息:
- 日期和时间:时间精确到毫秒
- 日志级别:ERROR、WARN、INFO、DEBUG、TRACE
- 进程ID
- 三个减号—:分隔符
- 线程名:用方括号括起来的是线程名
- 日志名:输出日志的完整类名
- 日志信息
改变日志级别的方式:
- 通过debug=true改变整个SpringBoot核心的日志级别
- 通过logging.level.=属性设置指定日志的日志级别
3.输出日志到文件
要将日志输出到文件,则可为SpringBoot设置如下两个属性:
- logging.file:设置日志文件
- logging.path:设置日志文件的目录
logging:
level:
# 将org.crazyit.app包及其子包下所有日志级别设为TRACE
org.crazyit.app: trace
file:
# 指定日志文件的输出目录,默认文件名为spring.log
# path: logs/
# 指定日志文件
name: my.log
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@Slf4j
public class HelloController
{
@GetMapping
public Map<String, Object> hello()
{
log.trace("-------TRACE级别的日志-------");
log.debug("-------DEBUG级别的日志-------");
log.info("-------INFO级别的日志-------");
log.warn("-------WARN级别的日志-------");
log.error("-------ERROR级别的日志-------");
return Map.of("hello", "Hello");
}
}
- 控制器类使用了@Slf4j注解修饰符,接下来程序就可以使用log对象的trace()、debug()等方法
4.Lombok
Lombok这个工具专门通过各种注解来生成常用的代码。
添加依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
常用注解:
- @Getter:为所有实例变量生成getter方法
- @Setter:为所有非final实例变量生成setter方法
- @ToString:自动生成toString()方法
- @EqualsAndHashCode:自动生成equals()和hashCode()方法
- @AlllArgsConstructor:自动生成带所有参数的构造器
- @NoArgsConstructro:自动生成无参数的构造器
- @Data:自动生成一个数据类,相当于@Getter、@Setter、@ToString、@EqualsAndHashCode和@NoArgsConstructor等注解的组合
- @Log、@Log4j、@Log4j2、@Slf4j、@XSlf4j、@CommonsLog、@JBossLog、@Flogger为对应的日志实现生成一个日志对象
六、开发者工具
SpringBoot推荐在开发阶段使用spring-boot-devtools工具。只要在pom.xml文件添加如下依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
devtools工具提供了大量开发时功能:
- 应用快速重启
- 浏览器实时重加载
- 各种开发时配置属性
- devtools工具还会自动将web日志组设为DEBUG级别,这样就会详细显示各个请求、处理请求的Handler,以及为请求生成的响应等信息。