SpringApplication是Spring Boot提供的一个工具类,提供了run()方法来启动Spring容器,运行SpringBoot应用。
传统Spring框架大多采用XML文件作为配置文件,但SpringBoot推荐使用Java配置类(带@Configuration注解)作为配置文件。
推荐用@Configuration修饰带main()方法的主类,则该主类也是Spring Boot应用的配置源。
除了加载主配置源中所有的配置,SpringBoot还会自动扫描主配置源所在的包及其子包下所有带@Component注解(@Configuration、@Controller、@Service、@Repository都是@Component的变体)的配置类或Bean组件
SpringBoot可使用如下三个注解加载其他配置类或者扫描其他包下的配置类或Bean组件:
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);
}
}
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注入该控制器。
import org.springframework.stereotype.Component;
@Component
public class Dog
{
public String bark()
{
return "来自Dog的测试方法";
}
}
<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">
<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这三个实例变量的依赖注入,意味着前面三种方式都配置成功了。
使用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支持如下不同形式的配置源。
获取这些外部化的属性主要有如下几种方式:
所有先加载的配置源都可能被后加载的配置源覆盖,因此可认为后加载的配置源的优先级更高。
程序要加载YAML文件配置的属性,则SpringBoot提供了如下工具类。
\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);
}
}
还需要使用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)有一个特征:
接下来,其他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");
}
}
@ConfigurationProperties注解有两种主要用法:
在使用@ConfigurationProperties注解时可指定如下属性:
使用@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;
}
}
}
IDEA开发属性处理类,会提示添加spring-boot-configuration-processor依赖,添加该依赖后IDEA可提供自动补全功能。
SpringBoot不会自动启用@ConfigurationProperties注解,让SpringBoot启用该注解有如下方式:
当该属性处理类被配置成容器中的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;
}
}
@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"]
}
}
实际上,属性处理类同样也支持用构造器来完成属性值注入,只要额外使用@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;
}
}
}
\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);
}
}
@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();
}
}
fkjava:
book:
title: "认真学java"
price: 128
author: "fypyt"
keywords:
- Java
- Spring
- python
上面配置文件中配置的属性与Book类的属性对应,SpringBoot就会读取这些配置属性,并将它们注入Book 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特点
SpringBoot还可对属性处理类进行校验,只要为属性处理类添加@Validated注解,并使用JSR 303的校验注解修饰需要校验的实例变量,SpringBoot会自动校验配置文件中的属性值。
添加依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
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;
}
}
}
Profile就是一组配置环境、各种程序组件的合集。
在实际开发环境中,经常需要在不同的环境间切换,比如开发项目时用的是开发环境,测试项目时用的是测试环境。
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只需要:
在运行应用时,可通过spring.profiles.active属性指定激活哪个Profile。
遵循越早加载,优先级越低的规则,通过命令行参数指定的spring.profiles.active属性会覆盖前面几种方式指定的属性
通过SpringBoot提供的日志抽象层可以非常方便地管理应用的日志输出。
只要在项目中导入spring-boot-starter.jar依赖,就会传递导入spring-boot-starter-logging.jar。
spring-boot-starter-logging.jar依赖如下三个JAR包:
Java领域日志框架包括:SLF4J、Log4j、Logback等
这些日志框架又可分为:
SpringBoot默认使用SLF4J+Logback的日志组合,其中SLF4J作为日志门面(应用程序输出日志时也应该面向该API),Logback作为日志实现,开发者通常不需要直接操作日志实现的API。因此,SpringBoot默认会添加SLF4J依赖和Logback依赖。
由于SpringBoot框架需要整合大量第三方框架,这些框架底层可能会使用JCL、Log4j、JUL等日志,因此SpringBoot还要提供对应的日志路由,将其他日志框架所生成的日志信息统一路由给SLF4J来处理。
从依赖关系可以看到:
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个不同的日志级别:
日志系统规则:
SpringBoot输出的每条日志都包括以下信息:
改变日志级别的方式:
要将日志输出到文件,则可为SpringBoot设置如下两个属性:
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");
}
}
Lombok这个工具专门通过各种注解来生成常用的代码。
添加依赖:
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.16version>
dependency>
常用注解:
SpringBoot推荐在开发阶段使用spring-boot-devtools工具。只要在pom.xml文件添加如下依赖即可:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
devtools工具提供了大量开发时功能: