Spring Boot 官文阅读笔记

参考

Spring Boot Docs
2.0: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-installing-spring-boot
2.0: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/html/
1.5: http://docs.spring.io/spring-boot/docs/1.5.x/reference/html/

Spring Boot Maven Plugins Docs
http://docs.spring.io/spring-boot/docs/2.0.0.BUILD-SNAPSHOT/maven-plugin/usage.html

前言

本文为作者的原创作品,转载需注明出处;

Requirement

这里我使用的是 Spring Boot 2.0.0 版本,要求 JDK 8.0 和 Spring 5.0.0 以上版本;

目的

通读 Spring Boot 官方文档,并形成一份阅读官网的手稿,做到边读边记录,并做出一些重点的批注;

Spring Boot CLI

Spring Boot CLI 是一个 Command Line Tool 帮助你快速构建 Spring Boot 应用;

安装

mac

1
2
$ brew tap pivotal/tap 
$ brew install springboot

Homebrew will install spring to /usr/local/bin.

Eclipse 配置

导入 Spring Boot 工程以前,需要首先在 Spring Boot 工程目录中执行mvn dependency:tree命令,自动导入 Spring Boot 的依赖包;然后再使用 Eclipse 导入 maven 工程;否则会有编译的错误

素材

Part III - Using Spring Boot

Starters

Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop-shop for all the Spring and related technology that you need, without having to hunt through sample code and copy paste loads of dependency descriptors.

All official starters follow a similar naming pattern; spring-boot-starter-*, where * is a particular type of application.

all starters: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/html/using-boot-build-systems.html#using-boot-starter

If you find that specific auto-configure classes are being applied that you don’t want, you can use the exclude attribute of @EnableAutoConfiguration to disable them

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 {
}

Chapter 17 - Spring Beans and dependency injection

If you structure your code as suggested above (locating your application class in a root package), you can add @ComponentScan without any arguments. All of your application components (@Component, @Service, @Repository, @Controller etc.) will be automatically registered as Spring Beans.

constructor injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DatabaseAccountService implements AccountService {

private final RiskAssessor riskAssessor;

@Autowired
public DatabaseAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
}

// ...

}

And if a bean has one constructor, you can omit the @Autowired.

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class DatabaseAccountService implements AccountService {

private final RiskAssessor riskAssessor;

public DatabaseAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
}

// ...

}

Chapter 18. Using the @SpringBootApplication annotation

The @SpringBootApplication annotation is equivalent to using @Configuration, @EnableAutoConfiguration and @ComponentScan with their default attributes:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.myproject;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
public class Application {

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

}

Chapter 19 Running Your Application

Running from Eclipse

我的思路如下,

通过 run configurations 配置

如果需要 debug,直接 Debug As … 即可。

Chapter 20 - Developer tools

  1. 提供了热启动和热部署
  2. 类似于 Grunt LiveReload 的 LiveReload 功能;
  3. Remote applications
    这部分是精华了,
    • 可以通过 Client 的代码的变动,自动的将改动的代码 push 到 remote,触发热部署,热启动等
      看看 20.5.2 Remote Update 小节的描述

      The remote client will monitor your application classpath for changes in the same way as the local restart. Any updated resource will be pushed to the remote application and (if required) trigger a restart.
      强大,就相当于是直接用本地环境来开发服务器应用了…

    • 20.5.3 Remote Debug tunnel
      开篇即说明了为什么要定义 Spring Boot 自己专有的 Remote debug 通道

      Java remote debugging is useful when diagnosing issues on a remote application. Unfortunately, it’s not always possible to enable remote debugging when your application is deployed outside of your data center. Remote debugging can also be tricky to setup if you are using a container based technology such as Docker.
      可见,如果你的 Client 和 Server 部署的不是在同一个数据中心(言外之意,因为防火墙的原因,不能够直接通过 TCP/IP 访问),那么通过 Java 默认的远程调试就困难重重了。正式因为如此,所以 Sprint Boot 提供了自己的远程调试模式,
      To help work around these limitations, devtools supports tunneling of remote debug traffic over HTTP.
      使用 HTTP 协议,避免防火墙等等限制…
      我的思考,如果使用的 VPC 呢?HTTP 转发?需验证.

Chapter 21. Packaging your application for production

Executable jars can be used for production deployment. As they are self-contained, they are also ideally suited for cloud-based deployment.

官网是建议直接通过 jars 来部署产品级应用的,不过如果部署的是 web 应用,要做到动静分离;所有静态资源还是需要放置在 nginx 上.

Part IV Spring Boot Features

23. SpringApplication

The SpringApplication class provides a convenient way to bootstrap a Spring application that will be started from a main() method.

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

FailureAnalyzers

如果启动失败,可以使用 FailureAnalyzers 进行分析,它可以将错误信息转换为 humman readable 的内容。

Application events and listeners

Web environment

SpringApplication 会自动的判断当前是否是 web 应用而选择不同的ApplicationContext,普通应用使用 AnnotationConfigApplicationContext,web 应用使用 AnnotationConfigEmbeddedWebApplicationContext

It is often desirable to call setWebEnvironment(false) when using SpringApplication within a JUnit test.

Accessing application arguments

If you need to access the application arguments that were passed to SpringApplication.run(…​) you can inject a org.springframework.boot.ApplicationArguments bean.

1
2
3
4
5
6
7
8
9
10
11
@Component
public class MyBean {

@Autowired
public MyBean(ApplicationArguments args) {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
// if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
}

}

Using the ApplicationRunner or CommandLineRunner

If you need to run some specific code once the SpringApplication has started, you can implement the ApplicationRunner or CommandLineRunnerinterfaces. Both interfaces work in the same way and offer a single run method which will be called just before SpringApplication.run(…​) completes.

The CommandLineRunner interfaces provides access to application arguments as a simple string array

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.boot.*
import org.springframework.stereotype.*

@Component
public class MyBean implements CommandLineRunner {

public void run(String... args) {
// Do something...
}

}

MyBean 在 SpringApplication.run(…) 结束以前执行,并且只会执行一次;String[] args 接收的是 SpringApplication.run(…​) 中的参数,通常是通过命令行法启动的时候顺带输入的。

Application exit

DisposableBean
@PreDestroy
org.springframework.boot.ExitCodeGenerator: to return a specific exit code when the application ends.

Admin features

spring.application.admin.enabled

This exposes the SpringApplicationAdminMXBean on the platform MBeanServer. You could use this feature to administer your Spring Boot application remotely. This could also be useful for any service wrapper implementation.

24 Externalized Configuration (外部化的配置)

Spring Boot allows you to externalize your configuration so you can work with the same application code in different environments.You can use properties files, YAML files, environment variables and command-line arguments to externalize configuration. Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties.

意义在其引用的第一段话就说得非常明确了,Spring Boot 允许你通过外部化配置使得同一个应用在不同的环境中运行;

注意,后面,这里讲解了 17 种不同的配置方式,以及相应的加载顺序; 这里比较重要 http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/html/boot-features-external-config.html#boot-features-external-config

Configuring random values
Accessing command line properties
Application property files

SpringApplication will load properties from application.properties files in the following locations and add them to the Spring Environment:

  1. A /config subdirectory of the current directory.
  2. The current directory
  3. A classpath /config package
  4. The classpath root

The list is ordered by precedence (properties defined in locations higher in the list override those defined in lower locations). 彼此之间存在覆盖关系,从上到下依次加载,后面的覆盖前面的配置。

You can also use YAML ('.yml') files as an alternative to ‘.properties’.

可以通过spring.config.namespring.config.location配置配置文件的加载方式;

Profile-specific properties

In addition to application.properties files, profile-specific properties can also be defined using the naming convention application-{profile}.properties.

特定环境的配置文件(Profile-specific)是通过application-{profile}.properties的方式进行配置的。

Profile-specific properties are loaded from the same locations as standard application.properties, with profile-specific files always overriding the non-specific ones irrespective of whether the profile-specific files are inside or outside your packaged jar.

Profile-specific properties 是从 application.properties 相同的位置进行加载的,Profile-specific files 总是会覆盖非特定环境的配置文件( non-specific ones ) 而无论 Profile-specific files 是在 jar 文件的内部还是在外部定义的。

Placeholders in properties
1
2
app.name=MyApp
app.description=${app.name} is a Spring Boot application

注意,只可以引用前面定义好的属性;

Using YAML instead of Properties

YAML is a superset of JSON, and as such is a very convenient format for specifying hierarchical configuration data

Loading YAML

YamlPropertiesFactoryBean自动加载YAMLProperties
YamlMapFactoryBean自动加载YAMMap

1
2
3
4
5
6
7
environments:
dev:
url: http://dev.bar.com
name: Developer Setup
prod:
url: http://foo.bar.com
name: My Cool App

转成 properties 如下,

1
2
3
4
environments.dev.url=http://dev.bar.com
environments.dev.name=Developer Setup
environments.prod.url=http://foo.bar.com
environments.prod.name=My Cool App
1
2
3
4
my:
servers:
- dev.bar.com
- foo.bar.com

转换成 properties

1
2
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com

可以通过@ConfigurationProperties加载属性,在 target bean 中指定 prefix,然后初始化一个 List 或者 Set 队列进行加载,比如我们将在上述的配置 my.servers

1
2
3
4
5
6
7
8
9
@ConfigurationProperties(prefix="my")
public class Config {

private List<String> servers = new ArrayList<String>();

public List<String> getServers() {
return this.servers;
}
}
Exposing YAML as properties in the Spring Environment

The YamlPropertySourceLoader class can be used to expose YAML as a PropertySource in the Spring Environment. This allows you to use the familiar @Value annotation with placeholders syntax to access YAML properties.

Multi-profile YAML documents

You can specify multiple profile-specific YAML documents in a single file by using a spring.profiles key to indicate when the document applies. For example:

1
2
3
4
5
6
7
8
9
10
11
12
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production
server:
address: 192.168.1.120

In the example above, the server.address property will be 127.0.0.1 if the development profile is active.

If the development and production profiles are not enabled, then the value for the property will be 192.168.1.100.

YAML shortcomings 短板

YAML files can’t be loaded via the @PropertySource annotation. So in the case that you need to load values that way, you need to use a properties file.

Merging YAML lists

定义了同一个 yaml 属性,在不同的 profiling 的情况下,yaml 的属性是如何转换的。这点对于不同环境如何使用不同的配置,至关重要。

Type-safe Configuration Properties
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
import org.springframework.boot.context.properties.ConfigurationProperties;

@Component
@ConfigurationProperties("prefix=foo")
public class FooProperties {

private boolean enabled;

private InetAddress remoteAddress;

private final Security security = new Security();

public boolean isEnabled() { ... }

public void setEnabled(boolean enabled) { ... }

public InetAddress getRemoteAddress() { ... }

public void setRemoteAddress(InetAddress remoteAddress) { ... }

public Security getSecurity() { ... }

public static class Security {

private String username;

private String password;

private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

public String getUsername() { ... }

public void setUsername(String username) { ... }

public String getPassword() { ... }

public void setPassword(String password) { ... }

public List<String> getRoles() { ... }

public void setRoles(List<String> roles) { ... }

}
}

通过 @EnableConfigurationProperties 注册

1
2
3
4
@Configuration
@EnableConfigurationProperties(FooProperties.class)
public class MyConfiguration {
}

application.yml 需要进行严格的配置,否则通过 FooProperties 进行加载会报错

1
2
3
4
5
6
7
foo:
remote-address: 192.168.1.1
security:
username: foo
roles:
- USER
- ADMIN

可以直接将 @ConfigurationProperties 对象作为一个 bean 进行注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class MyService {

private final FooProperties properties;

@Autowired
public MyService(FooProperties properties) {
this.properties = properties;
}

//...

@PostConstruct
public void openConnection() {
Server server = new Server(this.properties.getRemoteAddress());
// ...
}

}
Third-party configuration

上面是把 yaml 配置和 bean 的对应关系通过 bean 来配置的,其实也可以通过方法来配置,这样,可以将配置文件通过 getter 提供给第三方使用

1
2
3
4
5
@ConfigurationProperties(prefix = "bar")
@Bean
public BarComponent barComponent() {
...
}

Any property defined with the bar prefix will be mapped onto that BarComponent bean in a similar manner as the FooProperties example above

Relaxed binding

一种比较宽松的绑定方式,可以通过context-path绑定为 java 属性contextPath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConfigurationProperties(prefix="person")
public class OwnerProperties {

private String firstName;

public String getFirstName() {
return this.firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

}

下面对应的配置属性都可以被匹配
person.firstName
person.first-name
person.fisrt_name
PERSON_FIRST_NAME

Properties conversion

Spring will attempt to coerce the external application properties to the right type when it binds to the @ConfigurationProperties beans

当外部的应用属性通过@ConfigurationProperties被绑定以后,Spring 会试图强制的将属性转换成 java 所对应的类型的属性;

If you need custom type conversion you can provide a ConversionService bean (with bean id conversionService) or custom property editors (via a CustomEditorConfigurer bean) or custom Converters (with bean definitions annotated as @ConfigurationPropertiesBinding).

如果你需要自定义类型转换,三种方式可以做到,一,通过ConversionService,二、通过 custom property editors,一个 CustomEditorConfigurer bean。三,通过 custom Converters

@ConfigurationProperties Validation

通过使用 @Validated annotation 来激活 Spring Boot 验证,

You can use JSR-303 javax.validation constraint annotations directly on your configuration class,Simply ensure that a compliant JSR-303 implementation is on your classpath, then add constraint annotations to your fields:

1
2
3
4
5
6
7
8
9
10
@ConfigurationProperties(prefix="foo")
@Validated
public class FooProperties {

@NotNull
private InetAddress remoteAddress;

// ... getters and setters

}

In order to validate values of nested properties, you must annotate the associated field as @Valid to trigger its validation. For example, building upon the above FooProperties example:

要能够验证嵌套属性,你必须将对应的属性声明为 @Valid 才可以激活验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@ConfigurationProperties(prefix="connection")
@Validated
public class FooProperties {

@NotNull
private InetAddress remoteAddress;

@Valid
private final Security security = new Security();

// ... getters and setters

public static class Security {

@NotEmpty
public String username;

// ... getters and setters

}

}

security 是一个嵌套属性,所以,必须对它声明为 @Valid

You can also add a custom Spring Validator by creating a bean definition called configurationPropertiesValidator

你也可以通过configurationPropertiesValidator自定义 Spring Validator

@ConfigurationProperties vs. @Value

@Value is a core container feature and it does not provide the same features as type-safe Configuration Properties.

@Value 是核心容器的特性,它不提供类型安全配置属性(type-safe Configuration Properties)的特性;

The table below summarizes the features that are supported by @ConfigurationProperties and @Value:

Feature @ConfigurationProperties @Value
Relaxed binding Yes No
Meta-data support Yes No
SpEL evaluation Yes No

25 Profiles

Spring Profiles provide a way to segregate parts of your application configuration and make it only available in certain environments. Any @Component or @Configuration can be marked with @Profile to limit when it is loaded:

Spring Prifiles 提供这样一种方式来区分不同的应用配置,并且可以将这些不同的配置分别应用在不同的环境中;任何的@Component或者@Configuration注解都可以被标注@Profile

1
2
3
4
5
6
7
@Configuration
@Profile("production")
public class ProductionConfiguration {

// ...

}

or in application.properties

1
spring.profiles.active=dev,hsqldb

or using the command line --spring.profiles.active=dev,hsqldb

Adding active profiles
  1. Java API: setAdditionalProfiles()
  2. 通过 Command Line switch 切换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ---
    my.property: fromyamlfile
    ---
    spring.profiles: prod
    spring.profiles.include:
    + proddb
    + prodmq
    ---
    spring.profiles: dev
    spring.profiles.include:
    + devdb
    + devmq

    when an application with following properties is run using the switch –spring.profiles.active=prod the proddb and prodmq profiles will also be activated:
    再看一个例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    server:
    port: 9000
    ---

    spring:
    profiles: dev
    server:
    port: 9001

    ---

    spring:
    profiles: prod
    server:
    port: 0

    In this example the default port is 9000, but if the Spring profile ‘development’ is active then the port is 9001, and if ‘production’ is active then it is 0.
    To do the same thing with properties files you can use application-${profile}.properties to specify profile-specific values.

26. Logging

27. Developing web applications

Most web applications will use the spring-boot-starter-web module to get up and running quickly.

The ‘Spring Web MVC framework’
Spring MVC auto-configuration

If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

HttpMessageConverters

Spring MVC uses the HttpMessageConverter interface to convert HTTP requests and responses

If you need to add or customize converters you can use Spring Boot’s HttpMessageConverters class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;

@Configuration
public class MyConfiguration {

@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = ...
HttpMessageConverter<?> another = ...
return new HttpMessageConverters(additional, another);
}

}

Any HttpMessageConverter bean that is present in the context will be added to the list of converters. You can also override default converters that way.

Custom JSON Serializers and Deserializers

Spring Boot provides an alternative @JsonComponent annotation which makes it easier to directly register Spring Beans. You can use @JsonComponent directly on JsonSerializer or JsonDeserializer implementations. You can also use it on classes that contains serializers/deserializers as inner-classes. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import org.springframework.boot.jackson.*;

@JsonComponent
public class Example {

public static class Serializer extends JsonSerializer<SomeObject> {
// ...
}

public static class Deserializer extends JsonDeserializer<SomeObject> {
// ...
}

}

All @JsonComponent beans in the ApplicationContext will be automatically registered with Jackson, and since @JsonComponent is meta-annotated with @Component, the usual component-scanning rules apply.

Spring Boot also provides JsonObjectSerializer and JsonObjectDeserializer base classes which provide useful alternatives to the standard Jackson versions when serializing Objects. See the Javadoc for details.

MessageCodesResolver

Spring MVC has a strategy for generating error codes for rendering error messages from binding errors: MessageCodesResolver. Spring Boot will create one for you if you set the spring.mvc.message-codes-resolver.format property PREFIX_ERROR_CODE or POSTFIX_ERROR_CODE (see the enumeration in DefaultMessageCodesResolver.Format).

注意是设置错误消息返回样式的..

Static Content

By default Spring Boot will serve static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext. It uses the ResourceHttpRequestHandler from Spring MVC so you can modify that behavior by adding your own WebMvcConfigurerAdapter and overriding the addResourceHandlers method.

默认的,Spring Boot 会以 classpath 或者ServletContext为根路径下的/static或者/public或者/resources或者/META-INF/resources目录下的资源为静态资源;静态资源是通过ResourceHttpRequestHandler来进行处理的,你也可以通过添加你自己的WebMvcConfigurerAdapter,使用重载addResourceHandlers的方式来添加你自己处理静态资源的方式。

1
spring.mvc.static-path-pattern=/resources/**

让我一直疑虑的地方是,如果将静态资源打包在 jar 中,客户端能否缓存 css/js 等文件?

本章节提到另外一种静态资源的实现,通过 Webjars content

其实,静态资源放到 Spring BOOT 中,都不是最佳的选择,放到 Nginx 中是最好的。

Custom Favicon
ConfigurableWebBindingInitializer

Spring MVC uses a WebBindingInitializer to initialize a WebDataBinder for a particular request. If you create your own ConfigurableWebBindingInitializer @Bean, Spring Boot will automatically configure Spring MVC to use it.

Template engines

Spring Boot includes auto-configuration support for the following templating engines:

FreeMarker
Groovy
Thymeleaf
Mustache

When you’re using one of these templating engines with the default configuration, your templates will be picked up automatically from src/main/resources/templates.

Error Handling

Spring Boot provides an /error mapping by default that handles all errors in a sensible way, and it is registered as a ‘global’ error page in the servlet container.

For machine clients it will produce a JSON response with details of the error, the HTTP status and the exception message.

For browser clients there is a ‘whitelabel’ error view that renders the same data in HTML format (to customize it just add a View that resolves to ‘error’).

You can also define a @ControllerAdvice to customize the JSON document to return for a particular controller and/or exception type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ControllerAdvice(basePackageClasses = FooController.class)
public class FooControllerAdvice extends ResponseEntityExceptionHandler {

@ExceptionHandler(YourException.class)
@ResponseBody
ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}

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

}

In the example above, if YourException is thrown by a controller defined in the same package as FooController, a json representation of the CustomerErrorType POJO will be used instead of the ErrorAttributes representation.

可以捕获你自己定义的异常YourException,并且通过自定义CustomErrorType JSON 徐哦呜消息格式来替换默认的ErrorAttributes错误消息格式。

Custom error pages

Mapping error pages outside of Spring MVC

Spring HATEOAS

处理超媒体用的..

CORS support

Cross-origin resource sharing (CORS) is a W3C specification implemented by most browsers that allows you to specify in a flexible way what kind of cross domain requests are authorized, instead of using some less secure and less powerful approaches like IFRAME or JSONP.

CORS 是一种比使用 IFRAME 或者 JSONP 更为健壮的跨域的解决方案。

Global CORS configuration can be defined by registering a WebMvcConfigurer bean with a customized addCorsMappings(CorsRegistry) method:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MyConfiguration {

@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
JAX-RS and Jersey
Embedded servlet container support

Spring Boot includes support for embedded Tomcat, Jetty, and Undertow servers. Most developers will simply use the appropriate ‘Starter’ to obtain a fully configured instance. By default the embedded server will listen for HTTP requests on port 8080.

Spring Boot 有内嵌的 Tomcat, Jetty, Undertow 等 web 服务器,可以通过简单的使用 “Starter” 就可以获取他们。

(If you choose to use Tomcat on CentOS be aware that, by default, a temporary directory is used to store compiled JSPs, file uploads etc. This directory may be deleted by tmpwatch while your application is running leading to failures. To avoid this, you may want to customize your tmpwatch configuration so that tomcat.* directories are not deleted, or configure server.tomcat.basedir so that embedded Tomcat uses a different location.)

Servlets, Filters, and listeners
Servlet Context Initialization

Embedded servlet containers will not directly execute the Servlet 3.0+ javax.servlet.ServletContainerInitializer interface, or Spring’s org.springframework.web.WebApplicationInitializer interface.`

If you need to perform servlet context initialization in a Spring Boot application, you should register a bean that implements the org.springframework.boot.context.embedded.ServletContextInitializer interface. The single onStartup method provides access to the ServletContext, and can easily be used as an adapter to an existing WebApplicationInitializer if necessary.

Scanning for Servlets, Filters, and listeners

When using an embedded container, automatic registration of @WebServlet, @WebFilter, and @WebListener annotated classes can be enabled using @ServletComponentScan.

The EmbeddedWebApplicationContext

Under the hood Spring Boot uses a new type of ApplicationContext for embedded servlet container support.

The EmbeddedWebApplicationContext is a special type of WebApplicationContext that bootstraps itself by searching for a single EmbeddedServletContainerFactory bean. Usually a TomcatEmbeddedServletContainerFactory, JettyEmbeddedServletContainerFactory, or UndertowEmbeddedServletContainerFactory will have been auto-configured.

Customizing embedded servlet containers

Common servlet container settings can be configured using Spring Environment properties. Usually you would define the properties in your application.properties file.

可以通过application.properties来设置嵌入的 servlet 容器,可以设置 IP,PORT,SESSION,server.error.path,SSL,HTTP compression

Programmatic customization

If you need to configure your embedded servlet container programmatically you can register a Spring bean that implements the EmbeddedServletContainerCustomizer interface. EmbeddedServletContainerCustomizer provides access to the ConfigurableEmbeddedServletContainer which includes numerous customization setter methods.

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.boot.context.embedded.*;
import org.springframework.stereotype.Component;

@Component
public class CustomizationBean implements EmbeddedServletContainerCustomizer {

@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(9000);
}

}

Customizing ConfigurableEmbeddedServletContainer directly

If the above customization techniques are too limited, you can register the TomcatEmbeddedServletContainerFactory, JettyEmbeddedServletContainerFactory or UndertowEmbeddedServletContainerFactory bean yourself.

1
2
3
4
5
6
7
8
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.setPort(9000);
factory.setSessionTimeout(10, TimeUnit.MINUTES);
factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notfound.html"));
return factory;
}

还是通过配置来改变是正道,这里通过程序来改变最大的问题是不能通用。

JSP limitations

当使用嵌入容器执行 JSP 的时候有一些限制,

JSP Tomcat 可以在 WAR 中执行,但是在 JAR 中不行

28. Security

33. Calling REST services

If you need to call remote REST services from your application, you can use Spring Framework’s RestTemplate class. Since RestTemplate instances often need to be customized before being used, Spring Boot does not provide any single auto-configured RestTemplate bean. It does, however, auto-configure a RestTemplateBuilder which can be used to create RestTemplate instances when needed. The auto-configured RestTemplateBuilder will ensure that sensible HttpMessageConverters are applied to RestTemplate instances.

这章节重要了,讲述了作为客户端如何去调用 REST 接口;Spring Boot 是通过自动配置一个RestTemplateBuilder实例,可以通过它生成RestTemplate实例;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class MyBean {

private final RestTemplate restTemplate;

public MyBean(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}

public Details someRestCall(String name) {
return this.restTemplate.getForObject("/{name}/details", Details.class, name);
}

}

我的疑问是,为什么每次都要通过restTemplateBuilder.build()去获得一个restTemplate? 我的理解是,既然restTemplate是全局单例的,那么直接在容器里面注册一个restTemplate直接使用不是更好?

RestTemplate customization

There are three main approaches to RestTemplate customization, depending on how broadly you want the customizations to apply.

To make the scope of any customizations as narrow as possible, inject the auto-configured RestTemplateBuilder and then call its methods as required. Each method call returns a new RestTemplateBuilder instance so the customizations will only affect this use of the builder.

RestTemplateBuilder被设计成不是单例模式,目的就是为了接受不同的特有配置;

To make an application-wide, additive customization a RestTemplateCustomizer bean can be used. All such beans are automatically registered with the auto-configured RestTemplateBuilder and will be applied to any templates that are built with it.

可以通过RestTemplateCustomizer bean 自定义全局适配的规则,然后这些自定义的 bean 会自动注册到 RestTemplateBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static class ProxyCustomizer implements RestTemplateCustomizer {

@Override
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost("proxy.example.com");
HttpClient httpClient = HttpClientBuilder.create()
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {

@Override
public HttpHost determineProxy(HttpHost target,
HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}

}).build();
restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(httpClient));
}

}

疑问,ProxyCustomizer 是怎么加入 Spring 容器的?供RestTemplateBuilder使用的?

34. Validation

35. Sending email

36. Distributed Transactions with JTA

37. Hazelcast

38. Spring Integration

39. Spring Session

Spring Boot provides Spring Session auto-configuration for a wide range of stores:

  • JDBC
  • MongoDB
  • Redis
  • Hazelcast
  • HashMap
1
spring.session.store-type=jdbc

管理 Http Session,

http://projects.spring.io/spring-session/
http://www.cnblogs.com/mengmeng89012/p/5519698.html

40. Monitoring and management over JMX

41. Testing

spring-boot-test contains core items 和 spring-boot-test-autoconfigure supports auto-configuration for tests.

Most developers will just use the spring-boot-starter-test ‘Starter’ which imports both Spring Boot test modules as well has JUnit, AssertJ, Hamcrest and a number of other useful libraries.

Test scope dependencies

If you use the spring-boot-starter-test ‘Starter’ (in the test scope), you will find the following provided libraries:

JUnit — The de-facto standard for unit testing Java applications.
Spring Test & Spring Boot Test — Utilities and integration test support for Spring Boot applications.
AssertJ — A fluent assertion library.
Hamcrest — A library of matcher objects (also known as constraints or predicates).
Mockito — A Java mocking framework.
JSONassert — An assertion library for JSON.
JsonPath — XPath for JSON.

Testing Spring applications

One of the major advantages of dependency injection is that it should make your code easier to unit test. You can simply instantiate objects using the new operator without even involving Spring. You can also use mock objects instead of real dependencies.

If you have not used the spring-test module before you should start by reading the relevant section of the Spring Framework reference documentation.

Testing Spring Boot applications

Spring Boot provides a @SpringBootTest annotation which can be used as an alternative to the standard spring-test @ContextConfiguration annotation when you need Spring Boot features.

Detecting test configuration
Excluding test configuration

Spring Boot provides @TestComponent and @TestConfiguration annotations that can be used on classes in src/test/java to indicate that they should not be picked up by scanning.

当被注释为@TestComponent@TestConfiguration的实例,表示只会在测试的时候被加载。

Working with random ports

If you need to start a full running server for tests, we recommend that you use random ports. If you use @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) an available port will be picked at random each time your test runs.

如果你需要启动 web server 测试,建议使用@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)为每一次启动开启一个任意的端口。

Mocking and spying beans

It’s sometimes necessary to mock certain components within your application context when running tests. For example, you may have a facade over some remote service that’s unavailable during development. Mocking can also be useful when you want to simulate failures that might be hard to trigger in a real environment.

一段话就道出了为什么我们要使用 mock 的原因,比如,你有一个接口依赖远程实现,但是远程实现并未完成,你就需要 mock。

Spring Boot includes a @MockBean annotation that can be used to define a Mockito mock for a bean inside your ApplicationContext. You can use the annotation to add new beans, or replace a single existing bean definition. The annotation can be used directly on test classes, on fields within your test, or on @Configuration classes and fields. When used on a field, the instance of the created mock will also be injected. Mock beans are automatically reset after each test method.

通过@MockBean注解可以在ApplicationContext中定义一个通过 Mockito mock 的 bean;你可以使用该注解添加新的 beans,或者替换一个存在的 bean。该注解可以直接使用再你的测试类,a fileds 或者是 @Configuration classes and fields. 当你使用 field,所创建的 mock 实例将会被注入。Mock beans 在每一次测试完成后将会被自动的重置。

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
import org.junit.*;
import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.context.*;
import org.springframework.boot.test.mock.mockito.*;
import org.springframework.test.context.junit4.*;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {

@MockBean
private RemoteService remoteService;

@Autowired
private Reverser reverser;

@Test
public void exampleTest() {
// RemoteService has been injected into the reverser bean
given(this.remoteService.someCall()).willReturn("mock");
String reverse = reverser.reverseSomeCall();
assertThat(reverse).isEqualTo("kcom");
}

}

这里通过@MockBean注入 RemoteService

Additionally you can also use @SpyBean to wrap any existing bean with a Mockito spy. See the Javadoc for full details.

Auto-configured tests

Spring Boot’s auto-configuration system works well for applications, but can sometimes be a little too much for tests. It’s often helpful to load only the parts of the configuration that are required to test a ‘slice’ of your application.

有时候我们只需要加载配置中的一部分来完成一部分功能的验证即可,

For example, you might want to test that Spring MVC controllers are mapping URLs correctly, and you don’t want to involve database calls in those tests; or you might be wanting to test JPA entities, and you’re not interested in web layer when those tests run.

比如,你可能指向验证 controllers 是否正确映射了所有的 URLs,你并不想把数据库的调用给加载进来。

The spring-boot-test-autoconfigure module includes a number of annotations that can be used to automatically configure such ‘slices’. Each of them works in a similar way, providing a @…​Test annotation that loads the ApplicationContext and one or more @AutoConfigure…​ annotations that can be used to customize auto-configuration settings.

spring-boot-test-autoconfigure可以帮助你完成上述请求…

Auto-configured JSON tests

To test that Object JSON serialization and deserialization is working as expected you can use the @JsonTest annotation.

@JsonTest 专门用来测试 JSON 的序列化和反序列化行为的。

@JsonTest will auto-configure Jackson ObjectMapper, any @JsonComponent beans and any Jackson Modules.

@JsonTest将会自动的配置 Jackson ObjectMapper,任何的@JsonComponent以及Jackson Modules

The JacksonTester, GsonTester and BasicJsonTester classes can be used for Jackson, Gson and Strings respectively.

下面这个例子将会使用JacksonTester来做测试

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
@RunWith(SpringRunner.class)
@JsonTest
public class MyJsonTests {

@Autowired
private JacksonTester<VehicleDetails> json;

@Test
public void testSerialize() throws Exception {
VehicleDetails details = new VehicleDetails("Honda", "Civic");
// Assert against a `.json` file in the same package as the test
assertThat(this.json.write(details)).isEqualToJson("expected.json");
// Or use JSON path based assertions
assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make")
.isEqualTo("Honda");
}

@Test
public void testDeserialize() throws Exception {
String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
assertThat(this.json.parse(content))
.isEqualTo(new VehicleDetails("Ford", "Focus"));
assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
}

}

Auto-configured Spring MVC tests

To test Spring MVC controllers are working as expected you can use the @WebMvcTest annotation. @WebMvcTest will auto-configure the Spring MVC infrastructure and limit scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver. Regular @Component beans will not be scanned when using this annotation.

使用 @WebMvcTest 将会自动的加载 MVC 相应的 controllers,但是不会加载 @Component bean

Auto-configured Data JPA tests

@DataJpaTest can be used if you want to test JPA applications

Auto-configured JDBC tests

@JdbcTest is similar to @DataJpaTest but for pure jdbc-related tests. By default it will also configure an in-memory embedded database and a JdbcTemplate,

JDBC tests are transactional and rollback at the end of each test by default

@JdbcTest声明的测试用例默认是在事务环境下,执行完成以后便回滚。

当然,你也可以手动设计为不会滚,见如下测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class ExampleNonTransactionalTests {

}

If you prefer your test to run against a real database, you can use the @AutoConfigureTestDatabase annotation the same way as for DataJpaTest.

Auto-configured Data MongoDB tests
Auto-configured REST clients

The @RestClientTest annotation can be used if you want to test REST clients. By default it will auto-configure Jackson and GSON support, configure a RestTemplateBuilder and add support for MockRestServiceServer, The specific beans that you want to test should be specified using value or components attribute of @RestClientTest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RunWith(SpringRunner.class)
@RestClientTest(RemoteVehicleDetailsService.class)
public class ExampleRestClientTest {

@Autowired
private RemoteVehicleDetailsService service; // 想要测试的 bean

@Autowired
private MockRestServiceServer server;

@Test
public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
throws Exception {
this.server.expect(requestTo("/greet/details"))
.andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService(); // ? 被封装了?提供了 callResetSErvice() 方法?
assertThat(greeting).isEqualTo("hello");
}

}
Auto-configured Spring REST Docs tests

The @AutoConfigureRestDocs annotation can be used if you want to use Spring REST Docs in your tests. It will automatically configure MockMvc to use Spring REST Docs and remove the need for Spring REST Docs’ JUnit rule.

Spring REST Docs? 什么东西?难道要测试 REST 文档?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@AutoConfigureRestDocs
public class UserDocumentationTests {

@Autowired
private MockMvc mvc;

@Test
public void listUsers() throws Exception {
this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andDo(document("list-users"));
}

}

@AutoConfigureRestDocs can be used to override the default output directory (target/generated-snippets if you are using Maven or build/generated-snippets if you are using Gradle). It can also be used to configure the host, scheme, and port that will appear in any documented URIs. If you require more control over Spring REST Docs’ configuration a RestDocsMockMvcConfigurationCustomizer bean can be used:

1
2
3
4
5
6
7
8
9
10
@TestConfiguration
static class CustomizationConfiguration
implements RestDocsMockMvcConfigurationCustomizer {

@Override
public void customize(MockMvcRestDocumentationConfigurer configurer) {
configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
}

}

If you want to make use of Spring REST Docs’ support for a parameterized output directory, you can create a RestDocumentationResultHandler bean. The auto-configuration will call alwaysDo with this result handler, thereby causing each MockMvc call to automatically generate the default snippets:

1
2
3
4
5
6
7
8
9
@TestConfiguration
static class ResultHandlerConfiguration {

@Bean
public RestDocumentationResultHandler restDocumentation() {
return MockMvcRestDocumentation.document("{method-name}");
}

}

Ok, 通读完所有文档以后,明白了,是为了测试 REST 接口文档的规范、格式等;可以单独针对某一个方法进行测试;

Using Spock to test Spring Boot applications

Test utilities

ConfigFileApplicationContextInitializer

ConfigFileApplicationContextInitializer is an ApplicationContextInitializer that can apply to your tests to load Spring Boot application.properties files. You can use this when you don’t need the full features provided by @SpringBootTest.

1
2
@ContextConfiguration(classes = Config.class,
initializers = ConfigFileApplicationContextInitializer.class)
EnvironmentTestUtils

EnvironmentTestUtils allows you to quickly add properties to a ConfigurableEnvironment or ConfigurableApplicationContext. Simply call it with key=value strings:

1
EnvironmentTestUtils.addEnvironment(env, "org=Spring", "name=Boot");
OutputCapture

OutputCapture is a JUnit Rule that you can use to capture System.out and System.err output. Simply declare the capture as a @Rule then use toString() for assertions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.rule.OutputCapture;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

public class MyTest {

@Rule
public OutputCapture capture = new OutputCapture();

@Test
public void testName() throws Exception {
System.out.println("Hello World!");
assertThat(capture.toString(), containsString("World"));
}

}

重点看看这个玩意儿org.hamcrest.Matchers,正则表达式的工具类。

TestRestTemplate

TestRestTemplate is a convenience alternative to Spring’s RestTemplate that is useful in integration tests. You can get a vanilla template or one that sends Basic HTTP authentication (with a username and password). In either case the template will behave in a test-friendly way: not following redirects (so you can assert the response location), ignoring cookies (so the template is stateless), and not throwing exceptions on server-side errors. It is recommended, but not mandatory, to use Apache HTTP Client (version 4.3.2 or better), and if you have that on your classpath the TestRestTemplate will respond by configuring the client appropriately.

1
2
3
4
5
6
7
8
9
10
11
public class MyTest {

private TestRestTemplate template = new TestRestTemplate();

@Test
public void testRequest() throws Exception {
HttpHeaders headers = template.getForEntity("http://myhost.com", String.class).getHeaders();
assertThat(headers.getLocation().toString(), containsString("myotherhost"));
}

}

If you are using the @SpringBootTest annotation with WebEnvironment.RANDOM_PORT or WebEnvironment.DEFINED_PORT, you can just inject a fully configured TestRestTemplate and start using it. If necessary, additional customizations can be applied via the RestTemplateBuilder bean:

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
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {

@Autowired
private TestRestTemplate template;

@Test
public void testRequest() throws Exception {
HttpHeaders headers = template.getForEntity("http://myhost.com", String.class).getHeaders();
assertThat(headers.getLocation().toString(), containsString("myotherhost"));
}

@TestConfiguration
static class Config {

@Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.additionalMessageConverters(...)
.customizers(...);
}

}

}

通过动态注入TestRestTemplate可以自动获取端口?

WebSockets

Spring Boot provides WebSockets auto-configuration for embedded Tomcat (8 and 7), Jetty 9 and Undertow.

如果是通过嵌入式的 Tomcat,Spring Boot 将自动配置提供 WebSockets。

Spring Framework provides rich WebSocket support that can be easily accessed via the spring-boot-starter-websocket module.

If you’re deploying a war file to a standalone container, Spring Boot assumes that the container will be responsible for the configuration of its WebSocket support.

WebServices

The Spring Web Services features can be easily accessed via the spring-boot-starter-webservices module.

Creating your own auto-configuration

Auto-configuration can be associated to a “starter” that provides the auto-configuration code as well as the typical libraries that you would use with it. We will first cover what you need to know to build your own auto-configuration and we will move on to the typical steps required to create a custom starter.

搞个半天,原来 auto-configuration 就是指的 starter

A demo project is available to showcase how you can create a starter step by step.

Understanding auto-configured beans

Under the hood, auto-configuration is implemented with standard @Configuration classes.

Locating auto-configuration candidates

Spring Boot checks for the presence of a META-INF/spring.factories file within your published jar. The file should list your configuration classes under the EnableAutoConfiguration key.

Condition annotations
Class conditions
Bean conditions
Property conditions
Resource conditions
Web application conditions
SpEL expression conditions
Creating your own starter

Part V Spring Boot Actuator: Production-ready features