本文将对 Spring Boot 进行介绍,包括基本使用、高级特性等。
对于 Spring、Spring MVC、Spring Boot 等生态环境,可以直接在官网进行了解,这里将不在给出。
Demo
至于 Demo 的编写,可以按照 Getting Started 一步一步的完成。
需要说明的一点,如下:
1
2
3
4
5
6
7
8
9
|
@Controller
@ResponseBody
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "Hello World!";
}
}
|
当浏览器请求localhost:8080/hello
时,由于hello()
方法是向浏览器返回一个字符串,因此需要使用@ResponseBody
注解,这个注解放在类上或方法上都可以,用于表明向浏览器返回信息。
当然,也可以直接使用@RestController
注解代替@Controller
和@ResponseBody
注解,效果都是相同的。如下所示:
1
2
3
4
5
6
7
8
|
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "Hello World!";
}
}
|
配置文件
Spring Boot 可以简化配置,即它会使用application.properties
配置文件,自动读取相应的配置。例如,将服务器的端口号改为8888
,如下所示:
那么在启动 Spring Boot 的时候,就以8888
的端口号来启动服务了。具体可以配置哪些参数,可以在官网中进行查找。
简化部署
可以直接在 pom 文件中引入 Maven 插件,进行打包时,会将当前应用打成 jar 包,以供外部服务使用。这里可以在官网进行查看。
依赖管理
默认情况下,Spring Boot 会依赖 pom 文件中 parent 标签下的版本,其依赖路径为spring-boot-starter-parent
->spring-boot-dependencies
。如果其使用的版本号与我们的不同,那么可以直接在 pom 文件中的<properties>
标签下指定想要使用的版本号即可,如下所示:
1
2
3
4
5
6
7
8
9
10
11
|
<properties>
<mysql.version>5.1.3</mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
|
以上具体内容如何写,还需要参照spring-boot-dependencies
中是如何配置的。
你可以看到,在 pom 文件中,有形如spring-boot-starter-web
的依赖。它表明:Spring Boot 会通过这种依赖关系,直接导入与web
开发所需环境的包,也就是将与web
相关的包都进行了依赖传递。具体细节,可以查看官方文档。
容器功能
自动配置
之前在使用 Spring MVC 的时候,需要在 xml 文件中配置 bean,提供一些组件的支持。而我们在 Demo 中其实没有进行相应的配置,那么 Spring Boot 是如何进行自动配置的呢?
在 Spring Boot 的启动类SpringBootDemoApplication
中,其SpringApplication.run
会返回 IoC 容器,我们将其打印,看看有哪些组件被配置了,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
// 返回 IoC 容器
ConfigurableApplicationContext beans = SpringApplication.run(SpringBootDemoApplication.class, args);
// 查看容器中的组件
String[] names = beans.getBeanDefinitionNames();
for (int i = 0; i < names.length; i++) {
System.out.println(i + "-name: " + names[i]);
}
}
}
|
输出结果如下:
1
2
3
4
5
6
7
8
9
10
11
|
0-name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
1-name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
2-name: org.springframework.context.annotation.internalCommonAnnotationProcessor
3-name: org.springframework.context.event.internalEventListenerProcessor
4-name: org.springframework.context.event.internalEventListenerFactory
5-name: springBootDemoApplication
6-name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
7-name: helloController
8-name: org.springframework.boot.autoconfigure.AutoConfigurationPackages
9-name: org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
// 以下省略
|
此外,Spring Boot 在扫描包时,会自动扫描主程序启动类所在的包及其子包下的组件。如果想要扫描到其他位置的组件,则可以在主程序启动类中,添加@SpringBootApplication(scanBasePackages="com.example")
即可,也就是将扫描包的范围扩大。当然,也可以使用@ComponentScan
执行扫描路径。
其实,这里的@SpringBootApplication
包含以下三种注解:
1
2
3
|
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.example")
|
我们直接在主程序启动类中,添加如上三种注解也是可以的。
在配置文件中,所配置的值其实是与特定的类进行绑定的,各种配置都有默认值。
@Configuration
我们给一个类添加@Configuration
注解,以此来代替以前使用beans.xml
的方式。如下所示:
1
2
3
4
5
6
|
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Pet {
private String name;
}
|
1
2
3
4
5
6
7
|
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private Integer age;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Configuration
public class MyConfig {
/**
* 给容器中添加组件
* <p>
* 以方法名作为组件 id,返回类型作为组件类型,方法返回的值就是组件在容器中的实例
*
* @return
*/
@Bean
public User user() {
return new User("zhangsan", 25);
}
@Bean("carol")
public Pet pet() {
return new Pet("Tom");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
String[] names = context.getBeanDefinitionNames();
for(String name : names) {
System.out.println(name);
}
}
}
|
输出结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springBootDemoApplication
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
myConfig
helloController
user
carol
// 以下省略
|
可以看到,组件的名称也被打印了出来,说明组件的确是被 Spring Boot 加载了。
需要注意的是,通过@Configuration
注解默认配置的组件是单实例的,并且配置类本身也是组件。该注解中有一个proxyBeanMethods
属性,默认为true
,表示不管调用多少次这个 bean 的某个方法,其返回的对象都是一样的。也就是说,Spring Boot 总会检查该组件是否在容器中。如果已经在容器中了,则直接取容器中的组件。如果将其置为false
,那么每次都是不同的,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Configuration(proxyBeanMethods = false)
public class MyConfig {
/**
* 给容器中添加组件
* <p>
* 以方法名作为组件 id,返回类型作为组件类型,方法返回的值就是组件在容器中的实例
*
* @return
*/
@Bean
public User user() {
return new User("zhangsan", 25);
}
@Bean("carol")
public Pet pet() {
return new Pet("Tom");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
MyConfig bean = context.getBean(MyConfig.class);
User user1 = bean.user();
User user2 = bean.user();
System.out.println(user1 == user2);
}
}
|
输出结果如下:
针对该属性,在 Spring Boot V1 版本和 Spring Boot V2 版本中,最大的差别就在于此。也就是说,如果配置类组件之间无依赖关系,则使用 Lite 模式(proxyBeanMethods = false)会加速容器的启动过程,减少判断;如果配置类组件之间有依赖关系,则使用 Full 模式(proxyBeanMethods = true),表明方法会得到单实例组件。
综上,在 Full 模式下,能够保证每个被@Bean
注解修饰的方法被调用多次所返回的组件都是相同的;而在 Lite 模式下,能够保证每个被@Bean
注解修饰的方法被调用多次所返回的组件都是新创建的。
其实,Lite 模式相当于不在该类上添加@Configuration
注解,可以将其换成@Component
注解,其效果与 Lite 模式是一样的。
@Import
使用@Import
可以获取容器中的其他组件,如下图所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Import({User.class, DBHelper.class})
@Configuration
public class MyConfig {
/**
* 给容器中添加组件
* <p>
* 以方法名作为组件 id,返回类型作为组件类型,方法返回的值就是组件在容器中的实例
*
* @return
*/
@Bean
public User user() {
return new User("zhangsan", 25);
}
@Bean("carol")
public Pet pet() {
return new Pet("Tom");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
String[] userNames = context.getBeanNamesForType(User.class);
for(String name : userNames) {
System.out.println(name);
}
String[] dBHelperNames = context.getBeanNamesForType(DBHelper.class);
for(String name : dBHelperNames) {
System.out.println(name);
}
}
}
|
输出结果如下:
1
2
3
|
com.example.springbootdemo.boot.bean.User
user
ch.qos.logback.core.db.DBHelper
|
其中,全类名所表示的就是通过@Import
注解导入的组件,而user
是我们之前通过@Bean
注解注册到容器中的组件。
@Conditional
该注解表示按照条件装配,即满足@Conditional
指定的条件时,才会进行组件的注入。我们在这里演示@ConditionalOnBean
注解的作用。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Configuration
public class MyConfig {
@ConditionalOnBean(name = "carol")
@Bean
public User user() {
User user = new User("zhangsan", 25, new Pet("Jack"));
return user;
}
//@Bean("carol")
public Pet pet() {
return new Pet("Tom");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
boolean carol = context.containsBean("carol");
boolean user = context.containsBean("user");
System.out.println(user);
System.out.println(carol);
}
}
|
在MyConfig
类中,user()
方法使用到了Pet
对象,然而Pet
对象没有添加@Bean
注解,即没有被注入到容器中。那么此时User
也不会被注入到容器中。因为这里使用了@ConditionalOnBean(name = "carol")
,即只有声明为carol
的 Bean 在容器中,那么User
才会被注册到容器中。输出结果如下:
@ImportResource
如果你的项目比较老,还是使用类似于beans.xml
配置文件进行 bean 的注入,那么如果想将该文件的所有配置通过注解的方式注入到容器中,那么可以使用@ImportResource
组件,如下所示:
1
2
3
4
|
@ImportResource("classpath:beans.xml")
public class MyConfig() {
...
}
|
配置绑定
配置绑定可以将 properties 文件中的内容绑定到 JavaBean 中。
@ConfigurationProperties
将配置文件中的属性值绑定到 Car 中的属性中,如下所示:
1
2
|
mycar.brand=BMW
mycar.price=100000.01
|
1
2
3
4
5
6
|
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Double price;
}
|
1
2
3
4
5
6
7
8
9
10
11
|
@RestController
public class HelloController {
@Autowired
private Car car;
@RequestMapping("/car")
public Car testCar() {
return car;
}
}
|
在浏览器中访问localhost:8080/car
,输出结果如下:
1
2
3
4
|
{
"brand": "BMW",
"price": 100000.01
}
|
当然,也可以结合使用@ConfigurationProperties(prefix = "mycar")
、@EnableConfigurationProperties(Car.class)
实现上述功能,需要注意的是,这里要将后者添加在配置类中。对于后者的作用,一方面可以开启配置绑定功能,另一方面会将Car
这个组件自动注入到容器中。
自动配置原理
整理 P13、P14、P15
此外,如果想查看 Spring Boot 在初始化时哪些配置生效了,哪些配置没有生效,可以在application.properties
配置文件中声明debug=true
,在启动项目时,即可看到输出信息。
可以在 pom 文件中添加 dev-tools 依赖,当我们对代码进行修改后,不用重启 Spring Boot 环境,可以直接通过Build Projet(Command + F9)
来进行热更新。
1
2
3
4
5
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
|
yaml
我们可以使用application.yaml
来代替之前的application.properties
文件。其语法格式有如下规定:
- key: value(key 和 value 之间有空格);
- 大小写敏感;
- 使用缩进表示层级关系;
- 缩进不允许使用 Tab,只允许空格;
- 缩进的空格数不重要,只要相同层级的元素左对齐即可;
#
表示注释;
- 字符串无需加引号,如果要加,
''
表示字符串的内容不会被转义,""
表示字符串的内容会被转义。
- 例如,
'zhangsan\nlisi'
会在控制台输出zhangsan\nlisi
,而在浏览器的页面中会输出zhangsan\\nlisi
;
- 例如
"zhangsan\nlisi"
会在控制台换行,而在浏览器页面中会输出zhangsan\nlisi
。
对于 yaml 的数据类型,其可以表示字面量、对象、数组等,如下所示:
字面量:单个的、不可再分的值。例如 date、boolean、string、number、null,如下所示:
对象:键值对的集合。例如 map、hash、set、object,如下所示:
1
2
3
4
5
6
|
行内写法:k: {k1:v1,k2:v2,k3:v3}
# 或者
k:
k1: v1
k2: v2
k3: v3
|
数组:一组按次序排列的值。例如 array、list、queue,如下所示:
1
2
3
4
5
6
|
行内写法:k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
|
以下为代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salary;
private Map<String, List<Pet>> allPets;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
person:
userName: zhangsan
boss: true
birth: 2020/12/12
age: 25
pet:
name: Dog
interests: [篮球,乒乓球,Play Game]
animal:
- Tom
- Jack
score:
english:
first: 60
second: 89
third: 99
math: [131,141,111]
chinese: {first: 128,second: 145}
salary: [18000,1900.11,2000.21]
allPets:
sick:
- {name: tom}
- {name: jerry}
health: [{name: mario}]
|
1
2
3
4
5
6
7
8
9
10
11
12
|
@RestController
public class HelloController {
@Autowired
private Person person;
@RequestMapping("/person")
public Person testApplicationYaml() {
System.out.println(person.getUserName());
return person;
}
}
|
如果想要在 yaml 文件中有代码提示的话,则可以引入 processor 到 pom 文件中,如下所示:
1
2
3
4
5
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
|