(一)初识 Spring Boot

什么是 Spring Boot?

  Spring Boot:

  • 可以轻松创建独立的,基于生产级别的 Spring 应用,这代表着你只需要关注“运行”程序
  • 用一些固定的方式来构建生产级别的 Spring 应用
  • 推崇约定大于配置的方式,以便于你能够尽可能快速地启动并运行程序。

  人们把 Spring Boot 称为搭建程序的脚手架,其最主要作用就是快速地构建庞大的 Spring 项目,并且尽可能地减少一切 XML 配置,做到开箱即用,迅速上手,让我们关注业务而非配置

为什么使用 Spring Boot?

  原先使用 Spring 框架的时候,有非常复杂的配置和混乱的依赖关系,而 Spring Boot 能为我们带来以下好处:

  • 创建独立的 Spring 应用程序
  • 内嵌 Tomcat,Jetty 或 Undertow(无需部署war文件)
  • 提供固定的“入门”依赖项以简化构建配置
  • 尽可能自动配置 Spring 和第三方库
  • 提供生产就绪功能,例如指标,运行状况检查和外部化配置
  • 绝对没有代码生成,也不需要 XML 配置

快速入门

  在 IDEA 中,可以通过下面两种方式创建一个 Spring Boot 程序:

  • 使用Spring Initializr创建项目(推荐)
  • 使用 Maven 工具构建项目

  但此处使用第二种方式。下面为其步骤:

Maven工程创建项目

  在pom.xml文件加入父工程并添加添加类路径依赖关系:

1
2
3
4
5
6
7
8
9
10
11
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

  Spring Boot 提供了许多Starters启动器),可以将依赖包添加到类路径中。
  比如spring-boot-starter-parent是一个特殊的Starter启动器),提供有用的 Maven 默认值。它还提供了一个dependency-management部分,以便在添加依赖的时候可以省略版本号version
  其他Starters启动器)提供了在开发特定类型的应用程序时可能需要的依赖项。
  你可以在官方文档看到更多启动器的介绍:Spring Boot应用程序启动器
  由于我们正在开发 Web 应用程序,因此我们添加了spring-boot-starter-web依赖项。该依赖项添加后会添加大量依赖包进入工程:如日志包、tomcat 包、jackson 包、springframe 框架包等等,具体见下图:

依赖

编写相关代码

  要完成我们的应用程序,我们需要创建一个 Java文件。
  默认情况下,Maven编译src/main/java目录下的源代码,因此您需要在该目录下创建相关类,如下面的DemoBootApplication类:

1
2
3
4
5
6
7
8
9
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

  main方法将通过调用委托给 Spring Boot 中的SpringApplication类的run方法。
  SpringApplication类将引导我们的应用程序,启动Spring,然后启动自动配置的 Tomcat Web 服务器。
  DemoBootApplication.class将作为参数传递给run方法,以告诉SpringApplication类哪个是主要的 Spring 组件。而args数组也被作为参数传递,用来公开命令行的任何参数。
  运行后输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.6.RELEASE)

2019-07-02 09:34:43.095 INFO 47926 --- [ main] com.demo.DemoBootApplication : Starting DemoBootApplication on lovikes-Mac with PID 47926 (/Users/apple/Documents/IDEA/Study/demo/target/classes started by lovike in /Users/apple/Documents/IDEA/Study/demo)
2019-07-02 09:34:43.098 INFO 47926 --- [ main] com.demo.DemoBootApplication : No active profile set, falling back to default profiles: default
2019-07-02 09:34:43.947 INFO 47926 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-07-02 09:34:43.969 INFO 47926 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-07-02 09:34:43.969 INFO 47926 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.21]
2019-07-02 09:34:44.045 INFO 47926 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-07-02 09:34:44.045 INFO 47926 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 894 ms
2019-07-02 09:34:44.228 INFO 47926 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-07-02 09:34:44.398 INFO 47926 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-07-02 09:34:44.401 INFO 47926 --- [ main] com.demo.DemoBootApplication : Started DemoBootApplication in 1.776 seconds (JVM running for 2.707)

  上面即为 Spring Boot 的简单启动。
  和我们写 SSM 框架一些,在web层写相关映射,Spring Boot 也类似。
  首先创建web层的相关类:

1
2
3
4
5
6
7
8
9
10
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {
@RequestMapping("/test")
public String helloWorld(){
return "Hello World!";
}
}

  该@RequestMapping注释提供“路由”的信息。它告诉Spring,任何带/test路径的 HTTP 请求都应该映射到该helloWorld方法。而@RestController注解告诉 Spring 将得到的字符串直接返回给调用者。

运行测试

  当启动 Spring Boot 后,内置的 Tomcat 也会启动,访问http://localhost:8080/test将显示:

1
Hello World!

疑问:Spring Boot 中的配置文件在哪里?

  在入门案例中,我们没有任何的配置,就实现了一个 Spring MVC 项目,快速且高效!
  但是有人会有疑问,如果没有任何的xml文件,要配置一个Bean该怎么办?

传统的 xml 配置

  在 Spring Framework 框架中,要配置数据库连接池,首先会在pom.xml文件加入依赖:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>

  其次会定义一个jdbc.properties资源文件:

1
2
3
4
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/DatabaseName
jdbc.username=root
jdbc.password=123

  最后在相关的xml文件引入它:

1
2
3
4
5
6
7
8
9
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>

<property name="maxActive" value="10"/>
<property name="minIdle" value="5"/>
</bean>

  那么,在 Spring Boot 中该怎么做呢?

基于 Java 类的配置

  我们可以使用基于 Java 类的配置(类中使用 Spring 相关注解)方式来代替传统的xml配置,具体步骤如下:
  前两步和前面一样,第三步需要编写数据库的配置类相关代码并添加相关注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {

@Value("${jdbc.driverClass}")
String driverClass;
@Value("${jdbc.url}")
String url;
@Value("${jdbc.username}")
String username;
@Value("${jdbc.password}")
String password;

@Bean
public DataSource dataSource(){
DruidDataSource dataSource =new DruidDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}

  其中@Configuration注解是 Spring Framework 自带的注解,该注解注释的类类似于一个 xml配置文件,可以将该类理解为xml文件的 Java 版本。
  在@Configuration注解标识的类中,可以使用xml文件中的一些写法(如 Bean 的声明,依赖注入),比如:

  • @Bean注解:对应xml文件中的<bean/>,即Bean的声明;
  • @ComponentScan注解:对应xml文件中的<context:component-scan/>,即自动扫描。

  @PropertySource注解则用来指定类路径下需要读取的配置文件。

外部化配置(常用)

  Spring Boot 允许使用外部化配置,以便可以在不同的环境中使用相同的应用程序代码。
  可以使用以下方式来进行外部化配置:

  • .properties文件
  • .yaml文件
  • 环境变量
  • 命令行参数

Properties 文件

  SpringApplication类可以从application.properties文件加载相关属性,之后将它们加入到 Spring 的环境中。
  下面为一个application.properties文件示例:

1
2
3
4
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/DatabaseName
jdbc.username=root
jdbc.password=123

YAML 文件(常用)

  YAML 是 JSON 的超集,这是一种简洁的非标记语言,它以数据为中心,使用空白、缩进、分行组织数据,从而使得代码更加简洁易读。
  SpringApplication支持使用 YAML 来替代.properties文件,只要你有 SnakeYAML 类库在你的类路径(这和启动器相关,基本都会包含)。
  YAML 文件的后缀名为:.yml.yaml,即application.ymlapplication.yaml
  现在将数据内容以 YAML 文件格式重写:

1
2
3
4
5
jdbc:
driverClass: com.mysql.jdbc.Driver
url:jdbc: mysql://localhost:3306/DatabaseName
username: root
password: 123

注入文件数据

  对于 Properties 文件或 YAML 文件来说,属性值都可以通过@Value注解直接注入到相关的bean 中。
  但此处采用更为优雅的方式来使用。
  下面对数据库的例子以这种方式配置:
① 首先创建application.propertiesapplication.yml文件(详细数据见前文)
② 创建 JDBC 相关类与application文件做映射(prefix代表给属性添加jdbc前缀):

1
2
3
4
5
6
7
8
@Data // lombok 注解
@ConfigurationProperties(prefix = "jdbc")
public class JdbcProperties {
String driverClass;
String url;
String username;
String password;
}

③ 之后通过@EnableConfigurationProperties注解指定相关属性类:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcConfig {
@Bean
public DataSource dataSource(JdbcProperties properties){
DruidDataSource dataSource =new DruidDataSource();
dataSource.setDriverClassName(properties.getDriverClass());
dataSource.setUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
return dataSource;
}
}

  一旦使用了@EnableConfigurationProperties注解并将相关配置类填入参数中,就可以在任何你想要注入的地方使用:

1
2
3
4
5
6
@EnableConfigurationProperties(XXXProperties.class)
public class XXXServiceImpl {

@Autowired
private XXXProperties properties;
}

  当然,若需要在多处进行@Autowired配置类的属性,如上做法确实不错。
  若是对文件属性仅仅需要使用一次,可以使用如下更为优雅的方式:

1
2
3
4
5
6
7
8
@Configuration
public class JdbcConfig {
@Bean
@ConfigurationProperties(prefix = "jdbc")
public DataSource dataSource(){
return new DruidDataSource();;
}
}

  对于添加@Bean注解并return的类,Spring 会检查该类是否含有@ConfigurationProperties(prefix = "jdbc")注解依赖Properties文件中相关 4 个属性的setter方法,若有则将文件中的相关参数注入。

注意

  需要注意的是:application.propertiesapplication.yml文件只能放在以下四个位置:

  • 外置,在相对于应用程序运行目录的../config子目录里
  • 外置,在应用程序运行的目录里
  • 内置,在 config 包内
  • 内置,在 Classpath 根目录

  列表按照优先级排序。也就是说,../config子目录里的application.properties会覆盖应用程序Classpath里的application.properties中的相同属性。
  此外,若在同一优先级位置同时有application.propertiesapplication.yml,那么application.yml里的属性会被application.properties里的属性覆盖。

Spring Boot 功能特点

能使用 Spring 中的任何注解

  在 Spring Boot 中,可以随意地使用 Spring Framework 框架中的任何注解来定义 bean 及其注入的依赖项。
  因此我们会经常使用@ComponentScan(找到你的bean)和@Autowired(做构造函数注入)注解,它们搭配起来效果非常好。
  如果按照上面的建议构建代码(即在src/main/java下创建应用程序类),则可以添加@ComponentScan注解不带任何参数的代码。这代表着src/main/java所有包中只要有包含Bean相关注解的(如@Component@Service@Repository@Controller等)组件类将被自动扫描。
  换而言之,上面的做法类似于在 SSM 框架中的几个 Spring 配置文件中进行注解扫描,一般会分别对web层、service层、dao层的Bean扫描一遍。
  而 Spring Boot 更为方便,可以一次就扫描所有Bean

  当然我们一般都使用@SpringBootApplication注解类,因为它的作用更强。

一箭三雕:SpringBootApplication

  许多 Spring Boot 开发人员喜欢让他们的应用程序使用 3 个功能:

  • 自动配置
  • 开启组件扫描
  • 能够在“应用程序类”上定义额外的配置

  而使用了@SpringBootApplication的类可以启用这三个功能,即:

  • @EnableAutoConfiguration注解:启用Spring Boot的自动配置机制
  • @ComponentScan注解:在应用程序所在的包上启用扫描(扫描所有@Component相关组件)(请参阅最佳实践
  • @Configuration注解:允许在上下文中注册额外的 bean 或导入其他配置类

  @SpringBootApplication注解相当于同时使用以下注解和它们的默认属性:

  • @Configuration
  • @EnableAutoConfiguration
  • @ComponentScan

  示例如下:

1
2
3
4
5
6
7
8
9
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// same as @Configuration @EnableAutoConfiguration @ComponentScan
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

  为什么呢?
  我们来查看下@SpringBootApplication注解类的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import ...
@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 {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};

@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}

  哦,原来如此!该类上已经使用了@EnableAutoConfiguration@ComponentScan注解,而@Configuration注解又在@SpringBootConfiguration注解类中。也就是说@SpringBootApplication类拥有这3个类的功能,而且不仅如此,它还有拥有添加的其他注解类的功能。

能自动配置 Spring 应用程序

  Spring Boot 中的自动配置代表着,Spring Boot 将会尝试根据你添加的 jar 依赖项去自动配置 Spring 应用程序。
  例如,如果HSQLDB在你的类路径上,并且你尚未手动配置任何数据库连接相关的bean,则 Spring Boot 会自动配置内存数据库。
  你可以通过向其中一个类添加@EnableAutoConfiguration@SpringBootApplication注解来开启自动配置。

自定义配置替换部分自动配置(常用)

  当然,自动配置是非侵入性的。在任何时候,你都可以去定义自己的配置以替换自动配置的特定部分。
  例如,在application.yml文件中修改 Tomcat 启动时的端口号:

1
2
server:
port: 8081

  对于 Spring MVC 框架的其他属性配置可以参考官方文档
  常用的配置将在下一篇文章解释。

禁用特定的自动配置类(了解)

  如果你发现应用有你不需要的特定自动配置类,则可以使用@EnableAutoConfiguration注解的exclude属性禁用它们,示例如下:

1
2
3
4
5
6
7
8
import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.jdbc.*;
import org.springframework.context.annotation.*;

@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}

自动配置原理

  当我们在一个类上加上@SpringBootApplication注解,Spring Boot 通过SpringApplication类启动时就会自动配置,这是为什么呢?它一个类就可以完成这个功能嘛?
  当然不是,这和SpringApplication类相关。

Spring Boot的开启者:SpringApplication

  SpringApplication类提供了方便的方式去来引导一个 Spring 应用程序,当我们通过main方法开始执行一个被SpringBootApplication注解的类,后续一般会委派给SpringApplication类的静态run方法来启动应用,main方法如下:

1
2
3
public static void main(String[] args) {
SpringApplication.run(MySpringConfiguration.class, args);
}

  那么,到底什么是SpringApplication?下面是源码对该类的描述:
  可用于从 Java 的main方法引导和启动 Spring 应用程序的类。默认情况下,该类将执行以下步骤来引导您的应用程序:

  • 创建适当的 ApplicationContext实例(取决于你添加的依赖包)
  • 注册CommandLinePropertySource并将命令行参数作为 Spring 的配置属性
  • 刷新应用程序上下文,加载所有单例 bean
  • 触发任何CommandLineRunner bean
  • SpringApplication可以从各种不同的源读取 bean,这和你的配置有关

  简单来讲,当我们通过该类的run方法启动后,因为使用了自动配置,它会根据相关的依赖包来自动创建应用,比如说你加了spring-boot-starter-web依赖包,启动时会在类路径读取一个叫做META-INF/spring.factories的文件,该文件是添加的jar包spring-boot-autoconfigure-2.1.6.RELEASE.jar下的文件,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomrAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

  可以看到,该文件被读取时会将这些需要自动配置的类都给你配置好。所以启动 Spring Boot 应用后会根据这些默认配置来启动 Spring 应用,由于 Tomcat 也在其中,所以也会启动。

错误处理

  默认情况下,Spring Boot 提供了一个/error映射来明确地处理所有错误的映射,其已在 Servlet 容器中注册为“全局”的错误页面。
  对于机器客户端,它将生成一个 JSON 响应,其中包含错误,HTTP 状态和异常消息的详细信息。
  对于浏览器客户端,生成一个“ 空白标签”的错误视图以 HTML 格式来呈现相同的数据。

自定义错误信息

  我们可以定义一个带有@ControllerAdvice注解的类,其能针对特定的异常类型(或控制器)返回自定义的 JSON 数据,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 全局异常处理器,当控制层对象出现异常后:
* 1)检测控制层对象内部是否有 @ExceptionHandler 描述异常处理方法,有则直接使用,无则执行第二步
* 2)检测 Spring 容器中是否有类添加了 @ControllerAdvice注解,
* 有则使用类中添加了 @ExceptionHandler 的异常处理方法对特定异常进行处理,如果添加 @ResponseBody 返回信息则为JSON 格式。
* 而 @RestControllerAdvice 相当于 @ControllerAdvice@ResponseBody 的结合体。
*
* @author Chris Wong
* @date : 2019-09-26 16:38
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* @ExceptionHandler 的 RuntimeException 处理其他异常
*/
@ExceptionHandler(RuntimeException.class)
public Result doHandleRuntimeException(RuntimeException e) {
// 封装异常信息返回
return new Result(e);
}

/**
* @ExceptionHandler 的 CustomException 处理自定义异常
*/
@ExceptionHandler(CustomException.class)
public Result doHandleRuntimeException(CustomException e) {
// 封装状态信息返回
return new Result(e.getCode(), e.getMsg());
}
}

  其中自定义的异常类CustomException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CustomException extends RuntimeException {
private int code;
private String msg;

public CustomException(ICustomEnum iCustomEnum) {
this.code = iCustomEnum.getCode();
this.msg = iCustomEnum.getMsg();
}

public int getCode() {
return code;
}

public String getMsg() {
return msg;
}
}

  当自定义异常被抛出时,会返回自定义的 JSON 类Result的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@Data
public class Result<T> {
/**
* 状态码
*/
private int code;

/**
* 状态信息
*/
private String msg;

/**
* 返回的数据
*/
private T data;

/**
* 自定义异常处理返回的构造函数
*
* @param code
* @param msg
*/
public Result(int code, String msg) {
this.code = code;
this.msg = msg;
}

/**
* 其他异常处理返回的构造函数
* @param t
*/
public Result(Throwable t) {
this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
this.msg = t.getMessage();
}

/**
* 返回 HttpStatus 中状态码、状态信息
*
* @param httpStatus
*/
private Result(HttpStatus httpStatus) {
this.code = httpStatus.value();
this.msg = httpStatus.getReasonPhrase();
}

/**
* 返回 HttpStatus 中状态码、状态信息及自定义数据
*
* @param httpStatus
* @param data
*/
public Result(HttpStatus httpStatus, T data) {
this.code = httpStatus.value();
this.msg = httpStatus.getReasonPhrase();
this.data = data;
}

/**
* 返回自定义的状态码、状态信息及数据
*
* @param iCustomEnum
* @param data
*/
public Result(ICustomEnum iCustomEnum, T data) {
this.code = iCustomEnum.getCode();
this.msg = iCustomEnum.getMsg();
this.data = data;
}

public static Result ok() {
return new Result(HttpStatus.OK);
}

public static Result error() {
return new Result(HttpStatus.INTERNAL_SERVER_ERROR);
}
}

  自定义异常类中使用了自定义枚举接口,自定义的枚举都需要实现该接口:

1
2
3
4
public interface ICustomEnum {
int getCode();
String getMsg();
}

  下面为一个用户枚举类UserEnum示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Getter
@AllArgsConstructor
public enum UserEnum implements ICustomEnum {

UN_LOGIN(4004, "用户未登录");

/**
* 状态码
*/
private final int code;

private final String msg;
}

自定义错误页面(一般是前端定义)

  如果要显示给定状态代码的自定义 HTML 错误页面,可以将自定义的 HTML 页面添加到静态资源(如publicstaticresources)的/error文件夹下。
  错误页面可以是静态 HTML,也可以是模板。

  我们可以自定义一个实现了ErrorController接口的错误控制器来替换默认情况的error页面,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import ...
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class GlobalErrorController implements ErrorController {

@Override
public String getErrorPath() {
return "error";
}

private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
} catch (Exception ex) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}

/**
* produces 指定处理请求提交的Content-Type(内容类型),如 application/json, text/html;
* @param request
* @param model
* @return
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, Model model) {
HttpStatus status = getStatus(request);
if (status.is4xxClientError()) {
model.addAttribute("msg", "页面不存在哦");
}
if (status.is5xxServerError()) {
model.addAttribute("msg", "服务器崩溃了,请稍后再试");
}
return new ModelAndView("error");
}
}

  当然你也可以映射到具体的页面,如映射 404 错误或 500 错误到静态HTML文件,文件夹结构应如下:

1
2
3
4
5
6
7
8
9
10
src/
+- main/
+- java/
| + <source code>
+- resources/
+- static/
+- error/
| +- 404.html
| +- 500.html
+- <other public assets>

  我们可以在自定义的错误控制器里应根据确切的状态代码或系列掩码来返回不同的页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import ...
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class GlobalErrorController implements ErrorController {

...

/**
* produces 指定处理请求提交的Content-Type(内容类型),如 application/json, text/html;
* @param request
* @param model
* @return
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status.is4xxClientError()) {
return new ModelAndView("404");
}
if (status.is5xxServerError()) {
return new ModelAndView("500");
}
}
}

Profile 多环境配置

  在开发项目时,通常同一套程序会被发布到几个不同的环境,比如:开发、测试、生产等。
  由于每个环境的数据库地址、Redis 地 址、服务器端口等等配置都会不同,因此在为不同环境打包时,都要频繁修改配置文件,这是个非常繁琐且容易发生错误的事。
  对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,而 Spring Boot 就更简单了,打好包后,只需在不同的环境下启动时候指定读取的配置文件就可以了。

  在 Spring Boot 中多环境配置文件名需要满足application-{profile}.properties的格式,其中{profile}对应你的环境标识,比如:

  • application-dev.properties:开发环境
  • application-test.properties:测试环境
  • application-prod.properties:生产环境

  至于哪个具体的配置文件会被加载,需要在application.properties文件中通过spring.profiles.active属性来设置,其值对应{profile}值。
  如spring.profiles.active=test就会加载application-test.properties配置文件内容。

  在运行项目时,可以通过指定参数--spring.profiles.active来启动具体执行哪个环境。
  下面为一个示例:

1
java -jar boot-lesson-4-0.0.1-SNAPSHOT.jar --spring.profiles.active=test

MockMvc 单元测试

  对模块进行集成测试时,希望能够通过输入 URL 对 接口进行测试,如果通过启动服务器,建立 HTTP Client 进行 测试,这样会使得测试变得麻烦。
  假如我们的项目启动速度慢、网络环境差,这样就会造成我们的测试效率底下而且由于网络的原因 出现一些不可描述bug。
  因此为了方便我们对接口的测试,Spring Boot 引入了 MockMVC。
  MockMvc 实现了对 HTTP 请求的模拟,能够直接转换到接口的调用,不依赖网络环境,并且还提供了一套验证的工具,使我们能直观的看到认证的结果,大大提高了自测效率。

MockMvc 流程

  MockMvc 具体的使用步骤如下:

  • 通过MockMvcBuilders构造MockMvc
  • mockMvc调用perform,执行一个RequestBuilder请求,调用controller的业务处理逻辑
  • perform返回ResultActions,返回操作结果,ResultActions提供了统一的验证方式
  • 通过StatusResultMatchers对请求结果进行验证
  • 通过ContentResultMatchers对请求返回的内容进行验证

MockMvcBuilders

  MockMvcBuilders用来构造MockMvc,比如:

  • MockMvcBuilders.webAppContextSetup(WebApplicationContext context):可以指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc

MockMvcRequestBuilders

  从名字可以看出,MockMvcRequestBuilders是用来构建请求的,主要的 API为:

  • get(String urlTemplate, Object... uriVars):根据 URL 模板和 URI 变量值得到一个GET请求方式的RequestBuilder

  若在Controller的方法中method选择的是RequestMethod.GET,那在 mockMvc单元测试中对应就要使用MockMvcRequestBuilders.get
  后续的postputdeleted等请求方式以此类推。

ResultActions

  调用MockMvc.perform(RequestBuilder requestBuilder)后将得到ResultActions,它有以下三种处理方法:

  • .andExpect:添加执行完成后的断言。添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确;
  • .andDo:添加一个结果处理器,比如此处使用.andDo(MockMvcResultHandlers.print())输出整个响应结果信息
  • .andReturn:表示执行完成后返回相应的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package cn.lovike.community;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.junit.Before;
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.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CommunityApplicationTests {
private MockMvc mockMvc;

@Autowired
private WebApplicationContext context;

@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}

@Test
public void testGet() throws Exception {
MvcResult result = mockMvc.perform(
MockMvcRequestBuilders.get("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8) // 指定客户端能够接收的内容类型
.param("pageNum", "1")
.param("pageSize", "8"))
.andDo(print())
.andExpect(status().isOk())
// .andExpect(jsonPath("$.userId").value("1")) // jsonPath 验证 userId 是否为 1
// .andExpect(jsonPath("$.name").value("zhangsan"))
.andReturn();
System.out.println(result.getResponse().getContentAsString());
}

@Test
public void mockMvcPost() throws Exception {
UserInfo userInfo = new UserInfo();
userInfo.setName("lisi");
userInfo.setUserId("2");
ObjectMapper mapper = new ObjectMapper();
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
String json = ow.writeValueAsString(userInfo);
MvcResult mvcResult = mockMvc.perform(
MockMvcRequestBuilders.post("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(json).accept(MediaType.APPLICATION_JSON_UTF8))
.andDo(print())
.andExpect(jsonPath("$.userId").value("1")) //验证userId是否为1,jsonPath的使用
.andExpect(jsonPath("$.name").value("zhangsan"))//验证name是否为zhangsan,jsonPath的使用
.andReturn();
System.out.println(mvcResult.getResponse().getContentAsString());
}
}

参考

0%