web.md 96.6 KB
Newer Older
1 2 3 4
# 万维网

Spring 启动非常适合于 Web 应用程序开发。你可以使用嵌入式 Tomcat、 Jetty、 Undertow 或 Netty 创建一个自包含的 HTTP 服务器。大多数 Web 应用程序使用`spring-boot-starter-web`模块来快速启动和运行。你还可以选择使用`spring-boot-starter-webflux`模块构建反应性 Web 应用程序。

茶陵後's avatar
茶陵後 已提交
5
如果你还没有开发 Spring 启动 Web 应用程序,那么可以按照 *[开始](getting-started.html#getting-started.first-application)* 部分中的“Hello World!”示例进行操作。
6 7 8 9 10 11 12 13 14

## 1. Servlet Web 应用程序

如果你想构建基于 Servlet 的 Web 应用程序,可以利用 Spring Boot 的自动配置来实现 Spring MVC 或 Jersey。

### 1.1.“ Spring Web MVC 框架”

[Spring Web MVC framework](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc)(通常被称为“ Spring MVC”)是一个丰富的“模型视图控制器”Web 框架。 Spring MVC 允许你创建特殊的`@Controller``@RestController`bean 来处理传入的 HTTP 请求。通过使用`@RequestMapping`注释,将控制器中的方法映射到 HTTP。

茶陵後's avatar
茶陵後 已提交
15
下面的代码显示了提供 JSON 数据的典型`@RestController`:
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

```
import java.util.List;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class MyRestController {

    private final UserRepository userRepository;

    private final CustomerRepository customerRepository;

    public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
        this.userRepository = userRepository;
        this.customerRepository = customerRepository;
    }

    @GetMapping("/{user}")
    public User getUser(@PathVariable Long userId) {
        return this.userRepository.findById(userId).get();
    }

    @GetMapping("/{user}/customers")
    public List<Customer> getUserCustomers(@PathVariable Long userId) {
        return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
    }

    @DeleteMapping("/{user}")
    public void deleteUser(@PathVariable Long userId) {
        this.userRepository.deleteById(userId);
    }

}

```

“WebMVC.FN”是一种功能变体,它将路由配置与请求的实际处理分离开来,如下例所示:

```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}

```

```
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

@Component
public class MyUserHandler {

    public ServerResponse getUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

}

```

Spring MVC 是核心 Spring 框架的一部分,详细信息可在[参考文献](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc)中获得。在[spring.io/guides](https://spring.io/guides)上也有几个涵盖 Spring MVC 的指南。

|   |你可以定义任意多的`RouterFunction`bean 来模块化路由器的定义。如果需要应用优先级,可以订购<br/>bean。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.1.1. Spring MVC 自动配置

Spring Boot 为 Spring MVC 提供了自动配置,该配置在大多数应用程序中都能很好地工作。

自动配置在 Spring 的默认值之上添加了以下功能:

* 包含`ContentNegotiatingViewResolver``BeanNameViewResolver`bean。

* 对服务静态资源的支持,包括对 WebJAR 的支持(覆盖[在本文的后面部分](features.html#web.servlet.spring-mvc.static-content))。

* 自动注册`Converter``GenericConverter``Formatter`bean。

* 支持`HttpMessageConverters`(覆盖[在本文的后面部分](features.html#web.servlet.spring-mvc.message-converters))。

* 自动注册`MessageCodesResolver`(覆盖[在本文的后面部分](features.html#web.servlet.spring-mvc.message-codes))。

* 静态`index.html`支持。

* 自动使用`ConfigurableWebBindingInitializer` Bean(覆盖[在本文的后面部分](features.html#web.servlet.spring-mvc.binding-initializer))。

如果你希望保留那些 Spring 引导 MVC 自定义并使更多[MVC 定制](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc)(拦截器、格式化程序、视图控制器和其他功能),则可以添加你自己的`@Configuration`类型的`WebMvcConfigurer`**没有**`@EnableWebMvc`类。

茶陵後's avatar
茶陵後 已提交
143
如果希望提供`RequestMappingHandlerMapping``RequestMappingHandlerAdapter``ExceptionHandlerExceptionResolver`的自定义实例,并且仍然保持 Spring 引导 MVC 自定义,则可以声明类型`WebMvcRegistrations`的 Bean 并使用它来提供这些组件的自定义实例。
144

茶陵後's avatar
茶陵後 已提交
145
如果你想完全控制 Spring MVC,你可以添加你自己的`@Configuration`,并使用`@EnableWebMvc`进行注释,或者也可以添加你自己的`@Configuration`-注释`DelegatingWebMvcConfiguration`,如`@EnableWebMvc`的 Javadoc 中所描述的。
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231

|   |Spring MVC 使用不同的`ConversionService`来转换来自`application.properties``application.yaml`文件的值,<br/>表示`Period``Duration``DataSize`转换器不可用,并且`@DurationUnit``@DataSizeUnit`注释将被忽略。<br/><br/>如果你想定制 Spring MVC 使用的`ConversionService`,你可以提供带有`WebMvcConfigurer` Bean 的`addFormatters`方法。<br/>从该方法你可以注册任何你喜欢的转换器,或者你可以将其委托给`ApplicationConversionService`上可用的静态方法。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.1.2.HtpMessageConverters

Spring MVC 使用`HttpMessageConverter`接口来转换 HTTP 请求和响应。合理的默认值是开箱即用的。例如,对象可以自动转换为 JSON(通过使用 Jackson 库)或 XML(通过使用 JacksonXML 扩展(如果可用的话),或者通过使用 JAXB(如果 JacksonXML 扩展不可用的话)。默认情况下,字符串编码为`UTF-8`

如果需要添加或自定义转换器,可以使用 Spring boot 的`HttpMessageConverters`类,如以下清单所示:

```
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {

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

}

```

上下文中存在的任何`HttpMessageConverter` Bean 都被添加到转换器列表中。你也可以用同样的方法覆盖默认的转换器。

#### 1.1.3.自定义 JSON 序列化器和反序列化器

如果使用 Jackson 序列化和反序列化 JSON 数据,则可能需要编写自己的`JsonSerializer``JsonDeserializer`类。自定义序列化器通常是[通过模块在 Jackson 中注册](https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers),但是 Spring Boot 提供了一种替代的`@JsonComponent`注释,使得直接注册 Spring bean 更加容易。

你可以直接在`JsonSerializer``JsonDeserializer``KeyDeserializer`实现上使用`@JsonComponent`注释。你也可以在包含序列化器/反序列化器作为内部类的类上使用它,如以下示例所示:

```
import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import org.springframework.boot.jackson.JsonComponent;

@JsonComponent
public class MyJsonComponent {

    public static class Serializer extends JsonSerializer<MyObject> {

        @Override
        public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
            jgen.writeStringField("name", value.getName());
            jgen.writeNumberField("age", value.getAge());
        }

    }

    public static class Deserializer extends JsonDeserializer<MyObject> {

        @Override
        public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            ObjectCodec codec = jsonParser.getCodec();
            JsonNode tree = codec.readTree(jsonParser);
            String name = tree.get("name").textValue();
            int age = tree.get("age").intValue();
            return new MyObject(name, age);
        }

    }

}

```

`ApplicationContext`中的所有`@JsonComponent`bean 都会自动注册到 Jackson 中。由于`@JsonComponent`是用`@Component`进行元注释的,因此通常的组件扫描规则适用。

茶陵後's avatar
茶陵後 已提交
232
Spring Boot 还提供了[`JsonObjectSerializer`](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot/SRC/main/java/org/springframework/Jackson/jsonobjectserializer.java.java)和[<gtr="149"/>](https://github.com/[ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring- Spring-boot-project-project/[参见[`JsonObjectSerializer`](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/Jackson/jsonobjectserializer.html)和[`JsonObjectDeserializer`](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/Jackson/jsonobjeserializer.html)中的 JavaDoc 以获取详细信息。
233

茶陵後's avatar
茶陵後 已提交
234
上面的示例可以重写为使用`JsonObjectSerializer`/`JsonObjectDeserializer`如下:
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279

```
import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;

import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonObjectDeserializer;
import org.springframework.boot.jackson.JsonObjectSerializer;

@JsonComponent
public class MyJsonComponent {

    public static class Serializer extends JsonObjectSerializer<MyObject> {

        @Override
        protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
                throws IOException {
            jgen.writeStringField("name", value.getName());
            jgen.writeNumberField("age", value.getAge());
        }

    }

    public static class Deserializer extends JsonObjectDeserializer<MyObject> {

        @Override
        protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
                JsonNode tree) throws IOException {
            String name = nullSafeValue(tree.get("name"), String.class);
            int age = nullSafeValue(tree.get("age"), Integer.class);
            return new MyObject(name, age);
        }

    }

}

```

茶陵後's avatar
茶陵後 已提交
280
#### 1.1.4.Messenger EcodesResolver
281

茶陵後's avatar
茶陵後 已提交
282
Spring MVC 具有用于生成用于从绑定错误呈现错误消息的错误代码的策略:。如果你设置`spring.mvc.message-codes-resolver-format`属性`PREFIX_ERROR_CODE``POSTFIX_ERROR_CODE`, Spring boot 将为你创建一个(参见[`DefaultMessageCodesResolver.Format`]中的枚举(https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/defalidation/ressMessagecover.format.html))。
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307

#### 1.1.5.静态内容

默认情况下, Spring 引导从 Classpath 中的一个名为`/static`(或`/public``/resources``/META-INF/resources`)的目录或从`ServletContext`的根目录提供静态内容。它使用 Spring MVC 中的`ResourceHttpRequestHandler`,这样你就可以通过添加自己的`WebMvcConfigurer`并重写`addResourceHandlers`方法来修改该行为。

在独立的 Web 应用程序中,来自容器的默认 Servlet 也被启用,并充当后备,如果 Spring 决定不处理它,则从`ServletContext`的根目录中提供内容。在大多数情况下,这种情况不会发生(除非你修改了默认的 MVC 配置),因为 Spring 始终可以通过`DispatcherServlet`处理请求。

默认情况下,资源映射在`/**`上,但你可以使用`spring.mvc.static-path-pattern`属性对其进行调优。例如,将所有资源重新定位到`/resources/**`可以通过以下方式实现:

属性

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

Yaml

```
spring:
  mvc:
    static-path-pattern: "/resources/**"
```

你还可以通过使用`spring.web.resources.static-locations`属性(用目录位置列表替换默认值)来定制静态资源位置。根 Servlet 上下文路径`"/"`也会自动添加为位置。

茶陵後's avatar
茶陵後 已提交
308
除了前面提到的“标准”静态资源位置之外,[Webjars 内容](https://www.webjars.org/)还有一个特殊情况。路径在`/webjars/**`中的任何资源,如果以 WebJARS 格式打包,都将从 jar 文件中得到服务。
309

茶陵後's avatar
茶陵後 已提交
310
|   |如果你的应用程序被打包为 jar,请不要使用`src/main/webapp`目录。<br/>虽然这个目录是一个通用的标准,但是它在 WAR 打包中是**只有**的,如果你生成 jar,大多数构建工具都会默默地忽略它。|
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Spring 启动还支持由 Spring MVC 提供的高级资源处理功能,允许使用诸如破坏缓存的静态资源或使用用于 WebJAR 的与版本无关的 URL 的情况。

要为 WebJAR 使用与版本无关的 URL,请添加`webjars-locator-core`依赖项。然后申报你的 Webjar。以 jQuery 为例,添加`"/webjars/jquery/jquery.min.js"`会导致`"/webjars/jquery/x.y.z/jquery.min.js"`,其中`x.y.z`是 WebJAR 版本。

|   |如果使用 JBoss,则需要声明`webjars-locator-jboss-vfs`依赖项,而不是`webjars-locator-core`<br/>否则,所有 WebJAR 解析为`404`。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|

为了使用缓存破坏,以下配置为所有静态资源配置了一个缓存破坏解决方案,有效地在 URL 中添加了内容散列,例如`<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>`:

属性

```
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
```

Yaml

```
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: "/**"
```

茶陵後's avatar
茶陵後 已提交
342
|   |资源的链接在运行时在模板中被重写,由于`ResourceUrlEncodingFilter`是为 ThymeLeaf 和 Freemarker 自动配置的。<br/>在使用 JSP 时,你应该手动声明此过滤器。<br/>其他模板引擎目前不能自动支持,但可以与自定义模板宏/助手一起使用[`ResourceUrlProvider`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javc-api/org/spramework/spramework/ Servlet/resource/resourceurceurlprovider.html)。|
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

例如,当使用 JavaScript 模块加载程序动态加载资源时,不能选择重命名文件。这就是为什么其他策略也得到了支持,并且可以结合在一起。“固定”策略在不更改文件名的情况下在 URL 中添加静态版本字符串,如下例所示:

属性

```
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/js/lib/
spring.web.resources.chain.strategy.fixed.version=v12
```

Yaml

```
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: "/**"
          fixed:
            enabled: true
            paths: "/js/lib/"
            version: "v12"
```

茶陵後's avatar
茶陵後 已提交
374
通过这种配置,位于`"/js/lib/"`下的 JavaScript 模块使用固定的版本控制策略(`"/v12/js/lib/mymodule.js"`),而其他资源仍然使用 Content One(`<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>`)。
375

茶陵後's avatar
茶陵後 已提交
376
参见[`Web属性.Resources`](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autofigure/SRC/main/java/org/springframework/boot/autofigure/web/webproperties.java)以获得更多支持的选项。
377

茶陵後's avatar
茶陵後 已提交
378
|   |这个特性已经在一个专用的[blog post](https://spring.io/blog/2014/07/24/spring-framework-4-1-handling-static-web-resources)和 Spring 框架的[参考文献](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc-config-static-resources)中进行了详细的描述。|
379 380 381 382 383 384 385 386
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.1.6.欢迎页面

Spring 启动支持静态和模板化欢迎页面。它首先在配置的静态内容位置中查找`index.html`文件。如果没有找到一个,那么它将查找`index`模板。如果找到其中之一,它将自动用作应用程序的欢迎页面。

#### 1.1.7.路径匹配和内容协商

茶陵後's avatar
茶陵後 已提交
387
Spring MVC 可以通过查看请求路径并将其与你的应用程序中定义的映射(例如,控制器方法上的`@GetMapping`注释)匹配,将传入的 HTTP 请求映射到处理程序。
388

茶陵後's avatar
茶陵後 已提交
389
Spring 引导默认情况下选择禁用后缀模式匹配,这意味着像`"GET /projects/spring-boot.json"`这样的请求将不会匹配到`@GetMapping("/projects/spring-boot")`映射。这被认为是[best practice for Spring MVC applications](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc-ann-requestmapping-suffix-pattern-match)。这个特性在过去主要用于 HTTP 客户机,因为 HTTP 客户机没有发送正确的“接受”请求头;我们需要确保向客户机发送正确的内容类型。如今,内容协商更可靠了。
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503

还有其他方法来处理不能始终发送正确的“接受”请求头的 HTTP 客户机。与使用后缀匹配不同,我们可以使用一个查询参数来确保像`"GET /projects/spring-boot?format=json"`这样的请求将被映射到`@GetMapping("/projects/spring-boot")`:

属性

```
spring.mvc.contentnegotiation.favor-parameter=true
```

Yaml

```
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
```

或者,如果你更喜欢使用不同的参数名:

属性

```
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
```

Yaml

```
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: "myparam"
```

大多数标准媒体类型都支持开箱即用,但你也可以定义新的媒体类型:

属性

```
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
```

Yaml

```
spring:
  mvc:
    contentnegotiation:
      media-types:
        markdown: "text/markdown"
```

后缀模式匹配不受欢迎,将在未来的版本中删除。如果你理解这些警告,并且仍然希望你的应用程序使用后缀模式匹配,则需要进行以下配置:

属性

```
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-suffix-pattern=true
```

Yaml

```
spring:
  mvc:
    contentnegotiation:
      favor-path-extension: true
    pathmatch:
      use-suffix-pattern: true
```

或者,与其打开所有后缀模式,不如只支持已注册的后缀模式更安全:

属性

```
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-registered-suffix-pattern=true
```

Yaml

```
spring:
  mvc:
    contentnegotiation:
      favor-path-extension: true
    pathmatch:
      use-registered-suffix-pattern: true
```

在 Spring Framework5.3 中, Spring MVC 支持几种实现策略,用于将请求路径匹配到控制器处理程序。它以前只支持`AntPathMatcher`策略,但现在也提供`PathPatternParser`策略。 Spring 现在的启动提供了一种可在新策略中选择和 OPT 的配置属性:

属性

```
spring.mvc.pathmatch.matching-strategy=path-pattern-parser
```

Yaml

```
spring:
  mvc:
    pathmatch:
      matching-strategy: "path-pattern-parser"
```

有关为什么应该考虑这个新实现的更多详细信息,请参见[专门的博客文章](https://spring.io/blog/2020/06/30/url-matching-with-pathpattern-in-spring-mvc)

茶陵後's avatar
茶陵後 已提交
504
|   |`PathPatternParser`是一个优化的实现,但限制了[一些路径模式变体](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc-ann-requestmapping-uri-templates)的使用,并且与后缀模式匹配(`spring.mvc.pathmatch.use-suffix-pattern``spring.mvc.pathmatch.use-registered-suffix-pattern`)或映射带有 Servlet 前缀(`spring.mvc.servlet.path`)的`DispatcherServlet`不兼容。|
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.1.8.配置 WebBindingInitializer

Spring MVC 使用`WebBindingInitializer`对特定请求初始化`WebDataBinder`。如果你创建自己的`ConfigurableWebBindingInitializer``@Bean`, Spring boot 会自动配置 Spring MVC 来使用它。

#### 1.1.9.模板引擎

与 REST Web 服务一样,你也可以使用 Spring MVC 来提供动态 HTML 内容。 Spring MVC 支持各种模板化技术,包括 ThymeLeaf、FreeMarker 和 JSP。此外,许多其他模板引擎还包括它们自己的 Spring MVC 集成。

Spring 启动包括对以下模板引擎的自动配置支持:

* [FreeMarker](https://freemarker.apache.org/docs/)

* [Groovy](https://docs.groovy-lang.org/docs/next/html/documentation/template-engines.html#_the_markuptemplateengine)

* [Thymeleaf](https://www.thymeleaf.org)

* [Mustache](https://mustache.github.io/)

|   |如果可能的话,应该避免使用 JSP。<br/>在使用嵌入式 Servlet 容器时,有几个[已知限制](#web.servlet.embedded-container.jsp-limitations)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

茶陵後's avatar
茶陵後 已提交
528
当你使用这些模板引擎中的一个具有默认配置时,你的模板将自动从`src/main/resources/templates`中提取。
529

茶陵後's avatar
茶陵後 已提交
530
|   |取决于你如何运行你的应用程序,你的 IDE 可能会以不同的方式对 Classpath 进行排序。<br/>在 IDE 中从其主方法运行你的应用程序会导致与使用 Maven 或 Gradle 运行你的应用程序时不同的排序。或者从其打包的 jar。<br/>这可能会导致 Spring 引导无法找到预期的模板。<br/>如果你有这个问题,你可以重新排序 IDE 中的 Classpath,以便首先放置模块的类和资源。|
531 532 533 534
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.1.10.错误处理

茶陵後's avatar
茶陵後 已提交
535
默认情况下, Spring Boot 提供了一个`/error`映射,该映射以合理的方式处理所有错误,并将其注册为 Servlet 容器中的“全局”错误页。对于机器客户机,它生成一个 JSON 响应,其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户机,有一个“WhiteLabel”错误视图,该视图以 HTML 格式呈现相同的数据(要对其进行自定义,请添加一个`View`,将其解析为`error`)。
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659

如果你想定制默认的错误处理行为,那么可以设置许多`server.error`属性。参见附录中的[“服务器属性”](application-properties.html#appendix.application-properties.server)部分。

要完全替换缺省行为,可以实现`ErrorController`并注册该类型的 Bean 定义,或者添加`ErrorAttributes`类型的 Bean 来使用现有机制但替换内容。

|   |`BasicErrorController`可以用作自定义`ErrorController`的基类,<br/>如果你想为新的内容类型添加一个处理程序,这一点特别有用(默认情况是专门处理`text/html`,并为其他所有内容提供一个后备),<br/>这样做,扩展`BasicErrorController`,添加一个带有`@RequestMapping`属性的公共方法,并创建新类型的 Bean。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

还可以定义一个用`@ControllerAdvice`注释的类,以定制 JSON 文档,从而返回特定的控制器和/或异常类型,如以下示例所示:

```
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {

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

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        HttpStatus status = HttpStatus.resolve(code);
        return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
    }

}

```

在前面的示例中,如果`YourException`由在与`SomeController`相同的包中定义的控制器抛出,则使用`CustomErrorType`POJO 的 JSON 表示,而不是`ErrorAttributes`表示。

在某些情况下,在控制器级别处理的错误不会被[度量基础设施](actuator.html#actuator.metrics.supported.spring-mvc)记录。通过将已处理的异常设置为请求属性,应用程序可以确保将此类异常与请求度量一起记录:

```
import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Controller
public class MyController {

    @ExceptionHandler(CustomException.class)
    String handleCustomException(HttpServletRequest request, CustomException ex) {
        request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
        return "errorView";
    }

}

```

##### 自定义错误页面

如果你想为给定的状态代码显示自定义 HTML 错误页,那么可以将文件添加到`/error`目录。错误页可以是静态 HTML(即添加到任何静态资源目录下),也可以通过使用模板构建。文件的名称应该是确切的状态代码或系列掩码。

例如,要将`404`映射到静态 HTML 文件,你的目录结构如下:

```
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>
```

要使用 freemarker 模板映射所有`5xx`错误,你的目录结构如下:

```
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.ftlh
             +- <other templates>
```

对于更复杂的映射,还可以添加实现`ErrorViewResolver`接口的 bean,如下例所示:

```
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;

public class MyErrorViewResolver implements ErrorViewResolver {

    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        // Use the request or status to optionally return a ModelAndView
        if (status == HttpStatus.INSUFFICIENT_STORAGE) {
            // We could add custom model values here
            new ModelAndView("myview");
        }
        return null;
    }

}

```

茶陵後's avatar
茶陵後 已提交
660
你也可以使用常规的 Spring MVC 特性,例如[`@ExceptionHandler`方法](https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/Reference/html/web.html#MVC-ExceptionHandlers)和[`@ControllerAdvice`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/reference/html/web.html-controller-mover-advc-advice)。然后,`ErrorController`将获取任何未处理的异常。
661

茶陵後's avatar
茶陵後 已提交
662
##### 映射 Spring MVC#### 之外的错误页面
663

茶陵後's avatar
茶陵後 已提交
664
对于不使用 Spring MVC 的应用程序,可以使用`ErrorPageRegistrar`接口直接注册`ErrorPages`。这个抽象可以直接与底层嵌入的 Servlet 容器一起工作,并且即使你没有 Spring MVC也可以工作。
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720

```
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {

    @Bean
    public ErrorPageRegistrar errorPageRegistrar() {
        return this::registerErrorPages;
    }

    private void registerErrorPages(ErrorPageRegistry registry) {
        registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
    }

}

```

|   |如果你注册了一个`ErrorPage`,其路径最终由`Filter`处理(这在一些非 Spring Web 框架中很常见,例如 Jersey 和 Wicket),那么`Filter`必须显式地注册为`ERROR`调度器,如以下示例所示:|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

```
import java.util.EnumSet;

import javax.servlet.DispatcherType;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter() {
        FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
        // ...
        registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
        return registration;
    }

}

```

请注意,默认的`FilterRegistrationBean`不包括`ERROR`Dispatcher 类型。

##### WAR 部署中的错误处理

茶陵後's avatar
茶陵後 已提交
721
当部署到 Servlet 容器时, Spring 引导使用其错误页过滤器将具有错误状态的请求转发到适当的错误页。这是必要的,因为 Servlet 规范不提供用于注册错误页的 API。根据部署 WAR 文件的容器和应用程序使用的技术,可能需要进行一些额外的配置。
722

茶陵後's avatar
茶陵後 已提交
723
如果响应尚未提交,则错误页筛选器只能将请求转发到正确的错误页。默认情况下,WebSphere Application Server8.0 及以后版本在成功完成 Servlet 的服务方法后提交响应。你应该通过将`com.ibm.ws.webcontainer.invokeFlushAfterService`设置为`false`来禁用此行为。
724

茶陵後's avatar
茶陵後 已提交
725
如果你正在使用 Spring 安全,并且希望访问错误页中的主体,则必须配置 Spring Security 的筛选器,以便在错误分派时调用该筛选器。为此,将`spring.security.filter.dispatcher-types`属性设置为`async, error, forward, request`
726 727 728

#### 1.1.11.CORS 支持

茶陵後's avatar
茶陵後 已提交
729
[跨源资源共享](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)是一个[W3C 规范](https://www.w3.org/TR/cors/)[大多数浏览器](https://caniuse.com/#feat=cors)实现的[W3C 规范](https://www.w3.org/TR/cors/),它允许你以一种灵活的方式指定授权哪种类型的跨域请求,而不是使用一些安全性较低、功能较弱的方法,例如 IFRAME 或 JSONP。
730

茶陵後's avatar
茶陵後 已提交
731
在版本 4.2 中, Spring MVC[支持 CORS](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc-cors)。在 Spring 引导应用程序中使用[控制器方法 CORS 配置](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc-cors-controller)[`@CrossOrigin`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/bind/annotation/crossorigin.html)注释不需要任何特定的配置。[全局 CORS 配置](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web.html#mvc-cors-global)可以通过使用定制的`WebMvcConfigurer`[gt116]注册一个`addCorsMappings(CorsRegistry)`方法来定义,如下例所示:
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759

```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {

            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**");
            }

        };
    }

}

```

### 1.2.JAX-RS 和泽西岛

茶陵後's avatar
茶陵後 已提交
760
如果你更喜欢用于 REST 端点的 JAX-RS 编程模型,那么你可以使用其中一个可用的实现,而不是 Spring MVC。和在开箱即用的情况下工作得很好。CXF 要求你在应用程序上下文中将其`Servlet``Filter`注册为`@Bean`。Jersey 有一些本机的 Spring 支持,因此我们还在 Spring 启动中为它提供了自动配置支持,以及一个启动器。
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779

要开始使用 Jersey,将`spring-boot-starter-jersey`作为依赖项,然后需要一个类型为`ResourceConfig``@Bean`,在其中注册所有端点,如以下示例所示:

```
import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.stereotype.Component;

@Component
public class MyJerseyConfig extends ResourceConfig {

    public MyJerseyConfig() {
        register(MyEndpoint.class);
    }

}

```

茶陵後's avatar
茶陵後 已提交
780
|   |Jersey 对扫描可执行文档的支持相当有限,例如,当运行可执行 WAR 文件时,它不能扫描在[fully executable jar file](deployment.html#deployment.installing)`WEB-INF/classes`中找到的包中的端点。<br/>为了避免这种限制,不应使用`packages`方法,端点应该使用`register`方法单独注册,如前面的示例所示。|
781 782 783 784
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

对于更高级的定制,你还可以注册任意数量的 bean 来实现`ResourceConfigCustomizer`

茶陵後's avatar
茶陵後 已提交
785
所有注册的端点都应该是`@Components`,带有 HTTP 资源注释(`@GET`等),如下例所示:
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805

```
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.springframework.stereotype.Component;

@Component
@Path("/hello")
public class MyEndpoint {

    @GET
    public String message() {
        return "Hello";
    }

}

```

茶陵後's avatar
茶陵後 已提交
806
由于`Endpoint`是一个 Spring `@Component`,其生命周期由 Spring 管理,你可以使用`@Autowired`注释来注入依赖项,并使用`@Value`注释来注入外部配置。默认情况下,泽西 Servlet 被注册并映射到`/*`。可以通过将`@ApplicationPath`添加到`ResourceConfig`来更改映射。
807

茶陵後's avatar
茶陵後 已提交
808
默认情况下,在`ServletRegistrationBean`类型的`@Bean`中将泽西设置为 Servlet,名称为`jerseyServletRegistration`。默认情况下, Servlet 是惰性初始化的,但是你可以通过设置`spring.jersey.servlet.load-on-startup`来定制这种行为。你可以通过创建自己的同名应用程序来禁用或重写该 Bean。还可以通过设置`spring.jersey.type=filter`(在这种情况下,要替换或覆盖的`@Bean``jerseyFilterRegistration`)来使用过滤器而不是 Servlet。过滤器有一个`@Order`,你可以用`spring.jersey.filter.order`设置它。当使用 Jersey 作为过滤器时,必须有一个 Servlet 来处理未被 Jersey 截获的任何请求。如果你的应用程序不包含这样的 Servlet,那么你可能希望通过将`server.servlet.register-default-servlet`设置为`true`来启用缺省 Servlet。 Servlet 和过滤器注册都可以通过使用`spring.jersey.init.*`来指定属性的映射来给定 init 参数。
809 810 811 812 813 814 815 816 817 818 819

### 1.3.嵌入式 Servlet 容器支持

对于 Servlet 应用程序, Spring 引导包括对嵌入式[Tomcat](https://tomcat.apache.org/)[Jetty](https://www.eclipse.org/jetty/)[Undertow](https://github.com/undertow-io/undertow)服务器的支持。大多数开发人员使用适当的“启动器”来获得完全配置的实例。默认情况下,嵌入式服务器侦听端口`8080`上的 HTTP 请求。

#### 1.3.1.servlet、过滤器和侦听器

当使用嵌入式 Servlet 容器时,可以通过使用 Spring bean 或通过扫描 Servlet 组件来注册来自 Servlet 规范的 servlet、过滤器和所有侦听器(例如`HttpSessionListener`)。

##### 将 servlet、过滤器和侦听器注册为 Spring bean#####

茶陵後's avatar
茶陵後 已提交
820
任何`Servlet``Filter`,或 Servlet `*Listener`实例中的 Spring  Bean 是在嵌入式容器中注册的。如果你希望在配置过程中引用来自`application.properties`的值,那么这将非常方便。
821 822 823 824 825 826 827

默认情况下,如果上下文只包含一个 Servlet,则将其映射到`/`。在多个 Servlet bean 的情况下, Bean 名称被用作路径前缀。过滤器映射到`/*`

如果基于约定的映射不够灵活,则可以使用`ServletRegistrationBean``FilterRegistrationBean``ServletListenerRegistrationBean`类来完成控制。

通常情况下,保持过滤豆的无序状态是安全的。如果需要特定的顺序,你应该用`@Order`注释`Filter`,或者使其实现`Ordered`。不能通过用`@Order`注释其 Bean 方法来配置`Filter`的顺序。如果不能将`Filter`类更改为添加`@Order`或实现`Ordered`,则必须为`Filter`定义一个`FilterRegistrationBean`,并使用`setOrder(int)`方法设置注册 Bean 的顺序。避免配置读取请求主体`Ordered.HIGHEST_PRECEDENCE`的筛选器,因为它可能与应用程序的字符编码配置不一致。如果 Servlet 过滤器包装了该请求,则应将其配置为小于或等于`OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER`的顺序。

茶陵後's avatar
茶陵後 已提交
828
|   |要查看应用程序中每个`Filter`的顺序,请启用`web`[伐木组](features.html#features.logging.log-groups)`logging.level.web=debug`)的调试级别日志。<br/>将在启动时记录已注册过滤器的详细信息,包括它们的顺序和 URL 模式。|
829 830
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

茶陵後's avatar
茶陵後 已提交
831
|   |注册`Filter`bean 时要小心,因为它们在应用程序生命周期的很早就被初始化了。<br/>如果需要注册一个与其他 bean 交互的`Filter`,请考虑使用一个[`DelegatingFilterProxyRegistrationBean`](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/web/ Servlet/delegatingfilterbean.html)来代替。|
832 833 834 835
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.3.2. Servlet 上下文初始化

茶陵後's avatar
茶陵後 已提交
836
嵌入式 Servlet 容器不直接执行 Servlet 3.0+`javax.servlet.ServletContainerInitializer`接口或 Spring 的`org.springframework.web.WebApplicationInitializer`接口。这是一种有意的设计决策,旨在降低设计用于在 WAR 内部运行的第三方库可能会破坏 Spring 引导应用程序的风险。
837

茶陵後's avatar
茶陵後 已提交
838
如果需要在 Spring 引导应用程序中执行 Servlet 上下文初始化,则应该注册一个实现`org.springframework.boot.web.servlet.ServletContextInitializer`接口的 Bean。单一的`onStartup`方法提供了对`ServletContext`的访问,并且如果需要,可以很容易地用作现有`WebApplicationInitializer`的适配器。
839 840 841 842 843

##### 扫描 servlet、过滤器和侦听器 ####

当使用嵌入式容器时,使用`@WebServlet``@WebFilter``@WebListener`注释的类的自动注册可以通过使用`@ServletComponentScan`来启用。

茶陵後's avatar
茶陵後 已提交
844
|   |`@ServletComponentScan`在独立容器中没有任何作用,相反,在独立容器中使用的是容器的内置发现机制。|
845 846 847 848
|---|--------------------------------------------------------------------------------------------------------------------------------------|

#### 1.3.3.ServletWebServerApplicationContext

茶陵後's avatar
茶陵後 已提交
849
在引擎盖下, Spring 引导使用不同类型的`ApplicationContext`用于嵌入式 Servlet 容器支持。`ServletWebServerApplicationContext``WebApplicationContext`的一种特殊类型,它通过搜索单个`ServletWebServerFactory` Bean 来引导自身。通常`TomcatServletWebServerFactory``JettyServletWebServerFactory``UndertowServletWebServerFactory`已被自动配置。
850 851 852 853 854 855

|   |你通常不需要了解这些实现类。<br/>大多数应用程序都是自动配置的,并且适当的`ApplicationContext``ServletWebServerFactory`是代表你创建的。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 1.3.4.自定义嵌入式 Servlet 容器

茶陵後's avatar
茶陵後 已提交
856
可通过使用 Spring `Environment`属性来配置公共 Servlet 容器设置。通常,你会在`application.properties``application.yaml`文件中定义属性。
857 858 859

常见的服务器设置包括:

茶陵後's avatar
茶陵後 已提交
860
* 网络设置:用于接收 HTTP 请求(`server.port`)的侦听端口,绑定到`server.address`的接口地址,以此类推。
861

茶陵後's avatar
茶陵後 已提交
862
* 会话设置:会话是否持久(`server.servlet.session.persistent`)、会话超时(`server.servlet.session.timeout`)、会话数据的位置(`server.servlet.session.store-dir`)和会话-cookie 配置(`server.servlet.session.cookie.*`)。
863

茶陵後's avatar
茶陵後 已提交
864
* 错误管理:错误页面的位置(`server.error.path`)等等。
865 866 867 868 869

* [SSL](howto.html#howto.webserver.configure-ssl)

* [HTTP 压缩](howto.html#howto.webserver.enable-response-compression)

茶陵後's avatar
茶陵後 已提交
870
Spring 引导尝试尽可能多地公开公共设置,但这并不总是可能的。对于这些情况,专用的名称空间提供了特定于服务器的定制(参见`server.tomcat``server.undertow`)。例如,[访问日志](howto.html#howto.webserver.configure-access-logs)可以配置具有嵌入式 Servlet 容器的特定特征。
871

茶陵後's avatar
茶陵後 已提交
872
|   |参见[`Server属性`](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autofigure/SRC/main/java/org/springframework/boot/autofigure/web/serverproperties.java)类的完整列表。|
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### Samesite 饼干

Web 浏览器可以使用`SameSite`cookie 属性来控制跨站点请求中是否以及如何提交 cookie。该属性对于现代 Web 浏览器特别相关,因为它们已经开始更改缺省属性时使用的默认值。

如果要更改会话 cookie 的`SameSite`属性,可以使用`server.servlet.session.cookie.same-site`属性。自动配置的 Tomcat、 Jetty 和 Undertow 服务器支持此属性。它还用于配置基于`SessionRepository`bean 的 Spring 会话 Servlet。

例如,如果你希望你的会话 cookie 具有`SameSite``None`属性,则可以将以下内容添加到你的`application.properties``application.yaml`文件中:

属性

```
server.servlet.session.cookie.same-site=none
```

Yaml

```
server:
  servlet:
    session:
      cookie:
        same-site: "none"
```

茶陵後's avatar
茶陵後 已提交
899
如果要更改添加到`HttpServletResponse`中的其他 cookie 上的`SameSite`属性,可以使用`CookieSameSiteSupplier``CookieSameSiteSupplier`传递了一个`Cookie`,并可能返回一个`SameSite`值,或`null`
900

茶陵後's avatar
茶陵後 已提交
901
有许多方便的工厂和过滤方法,你可以使用它们来快速匹配特定的 Cookie。例如,添加以下 Bean 将自动为所有名称与正则表达式`myapp.*`匹配的 cookie 应用`SameSite``SameSite`
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921

```
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {

    @Bean
    public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
        return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
    }

}

```

##### 程序化定制

茶陵後's avatar
茶陵後 已提交
922
如果需要以编程方式配置你的嵌入式 Servlet 容器,则可以注册一个实现`WebServerFactoryCustomizer`接口的 Spring  Bean。`WebServerFactoryCustomizer`提供对`ConfigurableServletWebServerFactory`的访问,其中包括许多自定义 setter 方法。下面的示例以编程方式显示了端口的设置:
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940

```
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}

```

茶陵後's avatar
茶陵後 已提交
941
`TomcatServletWebServerFactory``JettyServletWebServerFactory``UndertowServletWebServerFactory``ConfigurableServletWebServerFactory`的专用变体,它们分别具有用于 Tomcat、 Jetty 和 Undertow 的附加自定义 setter 方法。下面的示例展示了如何定制`TomcatServletWebServerFactory`,从而提供对 Tomcat 特定配置选项的访问:
942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978

```
import java.time.Duration;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(TomcatServletWebServerFactory server) {
        server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
    }

}

```

##### 直接自定义配置 WebServerFactory#####

对于需要从`ServletWebServerFactory`扩展的更高级的用例,你可以自己公开这种类型的 Bean。

为许多配置选项提供了设置器。如果你需要做一些更奇特的事情,还提供了几种受保护的方法“挂钩”。有关详细信息,请参见[源代码文档](https://docs.spring.io/spring-boot/docs/2.6.4/api/org/springframework/boot/web/servlet/server/ConfigurableServletWebServerFactory.html)

|   |自动配置的 Customizer 仍然应用于你的自定义工厂,因此请小心使用该选项。|
|---|---------------------------------------------------------------------------------------------------|

#### 1.3.5.JSP 限制

当运行使用嵌入式 Servlet 容器(并打包为可执行归档文件)的 Spring 引导应用程序时,JSP 支持中存在一些限制。

* 对于 Jetty 和 Tomcat,如果使用战争包装,它应该可以工作。当使用`java -jar`启动时,可执行 WAR 将工作,并且还可以部署到任何标准容器。在使用可执行文件 jar 时,不支持 JSP。

* Undertow 不支持 JSP。

茶陵後's avatar
茶陵後 已提交
979
* 创建自定义`error.jsp`页面不会覆盖[错误处理](#web.servlet.spring-mvc.error-handling)的默认视图。应该使用[自定义错误页面](#web.servlet.spring-mvc.error-handling.error-pages)
980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096

## 2. 反应式 Web 应用程序

Spring 启动通过为 Spring WebFlux 提供自动配置简化了反应性 Web 应用程序的开发。

### 2.1.“ Spring WebFlux 框架”

Spring WebFlux 是在 Spring Framework5.0 中引入的新的反应性 Web 框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步和非阻塞的,并且通过[反应堆项目](https://projectreactor.io/)实现了[反应流](https://www.reactive-streams.org/)规范。

Spring WebFlux 有两种风格:基于功能的和基于注释的。基于注释的模型非常接近 Spring MVC 模型,如以下示例所示:

```
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class MyRestController {

    private final UserRepository userRepository;

    private final CustomerRepository customerRepository;

    public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
        this.userRepository = userRepository;
        this.customerRepository = customerRepository;
    }

    @GetMapping("/{user}")
    public Mono<User> getUser(@PathVariable Long userId) {
        return this.userRepository.findById(userId);
    }

    @GetMapping("/{user}/customers")
    public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
        return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
    }

    @DeleteMapping("/{user}")
    public void deleteUser(@PathVariable Long userId) {
        this.userRepository.deleteById(userId);
    }

}

```

“WebFlux.FN”是一种功能变体,它将路由配置与请求的实际处理分离开来,如下例所示:

```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}

```

```
import reactor.core.publisher.Mono;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

@Component
public class MyUserHandler {

    public Mono<ServerResponse> getUser(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> deleteUser(ServerRequest request) {
        ...
    }

}

```

WebFlux 是 Spring 框架的一部分,其[参考文献](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web-reactive.html#webflux-fn)中提供了详细信息。

|   |你可以定义任意多的`RouterFunction`bean 来模块化路由器的定义。如果需要应用优先级,可以订购<br/>bean。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|

要开始,将`spring-boot-starter-webflux`模块添加到应用程序中。

茶陵後's avatar
茶陵後 已提交
1097
|   |在应用程序中同时添加`spring-boot-starter-web``spring-boot-starter-webflux`模块会导致 Spring 引导自动配置 Spring MVC,不是 WebFlux.<br/>之所以选择这种行为,是因为许多 Spring 开发人员将`spring-boot-starter-webflux`添加到他们的 Spring MVC 应用程序中,以使用反应式`WebClient`<br/>你仍然可以通过将选择的应用程序类型设置为`SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)`来强制执行你的选择。|
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

“WebFlux.FN”是一种功能变体,它将路由配置与请求的实际处理分离开来,如下例所示:

```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}

```

```
import reactor.core.publisher.Mono;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

@Component
public class MyUserHandler {

    public Mono<ServerResponse> getUser(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> deleteUser(ServerRequest request) {
        ...
    }

}

```

WebFlux 是 Spring 框架的一部分,其[参考文献](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web-reactive.html#webflux-fn)中提供了详细信息。

|   |你可以定义任意多的`RouterFunction`bean 来模块化路由器的定义。如果需要应用优先级,可以订购<br/>bean。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|

要开始,将`spring-boot-starter-webflux`模块添加到应用程序中。

|   |在应用程序中同时添加`spring-boot-starter-web``spring-boot-starter-webflux`模块将导致 Spring 引导自动配置 Spring MVC,不是 WebFlux.<br/>之所以选择这种行为,是因为许多 Spring 开发人员将`spring-boot-starter-webflux`添加到他们的 Spring MVC 应用程序中,以使用反应式`WebClient`<br/>你仍然可以通过将选择的应用程序类型设置为`SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)`来强制执行你的选择。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 2.1.1. Spring WebFlux 自动配置

茶陵後's avatar
茶陵後 已提交
1169
Spring 启动为 Spring WebFlux 提供了自动配置,该配置在大多数应用程序中都能很好地工作。
1170 1171 1172 1173 1174 1175 1176

自动配置在 Spring 的默认值之上添加了以下功能:

*`HttpMessageReader``HttpMessageWriter`实例配置编解码器(描述[在本文的后面部分](#web.reactive.webflux.httpcodecs))。

* 对服务静态资源的支持,包括对 WebJAR 的支持(描述[在本文的后面部分](#web.servlet.spring-mvc.static-content))。

茶陵後's avatar
茶陵後 已提交
1177
如果你希望保留 Spring boot WebFlux 特性并且希望添加额外的[WebFlux 配置](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web-reactive.html#webflux-config),则可以添加你自己的`@Configuration`类型的类别`WebFluxConfigurer`但是**没有**`@EnableWebFlux`
1178 1179 1180 1181 1182

如果你想完全控制 Spring WebFlux,那么可以添加你自己的`@Configuration`,并使用`@EnableWebFlux`进行注释。

#### 2.1.2.带有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器

茶陵後's avatar
茶陵後 已提交
1183
Spring WebFlux 使用`HttpMessageReader``HttpMessageWriter`接口来转换 HTTP 请求和响应。通过查看 Classpath 中可用的库,将`CodecConfigurer`配置为具有合理的默认值。
1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214

Spring Boot 为编解码器提供了专用的配置属性,`spring.codec.*`。它还通过使用`CodecCustomizer`实例应用进一步的定制。例如,`spring.jackson.*`配置键被应用于 Jackson 编解码器。

如果需要添加或自定义编解码器,可以创建自定义`CodecCustomizer`组件,如下例所示:

```
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;

@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {

    @Bean
    public CodecCustomizer myCodecCustomizer() {
        return (configurer) -> {
            configurer.registerDefaults(false);
            configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
            // ...
        };
    }

}

```

你还可以利用[Boot 的自定义 JSON 序列化器和反序列化器](#web.servlet.spring-mvc.json)

#### 2.1.3.静态内容

茶陵後's avatar
茶陵後 已提交
1215
默认情况下, Spring 引导从 Classpath 中的一个名为`/static`(或`/public``/resources``/META-INF/resources`)的目录中提供静态内容。它使用 Spring WebFlux 中的`ResourceWebHandler`,以便你可以通过添加自己的`WebFluxConfigurer`并重写`addResourceHandlers`方法来修改该行为。
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234

默认情况下,资源映射在`/**`上,但是你可以通过设置`spring.webflux.static-path-pattern`属性对其进行调优。例如,将所有资源重新定位到`/resources/**`可以通过以下方式实现:

属性

```
spring.webflux.static-path-pattern=/resources/**
```

Yaml

```
spring:
  webflux:
    static-path-pattern: "/resources/**"
```

你还可以使用`spring.web.resources.static-locations`自定义静态资源位置。这样做可以用目录位置列表替换默认值。如果你这样做,默认的欢迎页面检测将切换到你的自定义位置。因此,如果在启动时的任何位置都有`index.html`,那么它就是应用程序的主页。

茶陵後's avatar
茶陵後 已提交
1235
除了前面列出的“标准”静态资源位置之外,[Webjars 内容](https://www.webjars.org/)还有一个特殊情况。路径在`/webjars/**`中的任何资源,如果以 WebJARS 格式打包,都将从 jar 文件中得到服务。
1236 1237 1238 1239 1240 1241 1242 1243 1244 1245

|   |Spring WebFlux 应用程序不严格依赖于 Servlet API,因此它们不能作为 WAR 文件部署,也不使用`src/main/webapp`目录。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 2.1.4.欢迎页面

Spring 启动支持静态和模板化欢迎页面。它首先在配置的静态内容位置中查找`index.html`文件。如果没有找到一个,那么它将查找`index`模板。如果找到其中之一,它将自动用作应用程序的欢迎页面。

#### 2.1.5.模板引擎

茶陵後's avatar
茶陵後 已提交
1246
与 REST Web 服务一样,你也可以使用 Spring WebFlux 来服务动态 HTML 内容。 Spring WebFlux 支持各种模板化技术,包括胸腺叶、自由标记和胡子。
1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259

Spring 启动包括对以下模板引擎的自动配置支持:

* [FreeMarker](https://freemarker.apache.org/docs/)

* [Thymeleaf](https://www.thymeleaf.org)

* [Mustache](https://mustache.github.io/)

当你使用这些模板引擎中的一个具有默认配置时,你的模板将从`src/main/resources/templates`中自动拾取。

#### 2.1.6.错误处理

茶陵後's avatar
茶陵後 已提交
1260
Spring Boot 提供了一个`WebExceptionHandler`,它以合理的方式处理所有错误。它在处理顺序中的位置紧随 WebFlux 提供的处理程序之前,后者被认为是最后一个处理程序。对于机器客户机,它生成一个 JSON 响应,其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户端,有一个“WhiteLabel”错误处理程序,它以 HTML 格式呈现相同的数据。你也可以提供自己的 HTML 模板来显示错误(参见[下一节](#web.reactive.webflux.error-handling.error-pages))。
1261

茶陵後's avatar
茶陵後 已提交
1262
定制此功能的第一步通常涉及使用现有机制,但要替换或增加错误内容。为此,你可以添加类型`ErrorAttributes`的 Bean。
1263

茶陵後's avatar
茶陵後 已提交
1264
要更改错误处理行为,可以实现`ErrorWebExceptionHandler`并注册该类型的 Bean 定义。因为`ErrorWebExceptionHandler`是相当低级的, Spring boot 还提供了一个方便的`AbstractErrorWebExceptionHandler`,让你以 WebFlux 功能方式处理错误,如以下示例所示:
1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308

```
import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder;

@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources,
            ApplicationContext applicationContext) {
        super(errorAttributes, resources, applicationContext);
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
    }

    private boolean acceptsXml(ServerRequest request) {
        return request.headers().accept().contains(MediaType.APPLICATION_XML);
    }

    public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
        BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
        // ... additional builder calls
        return builder.build();
    }

}

```

茶陵後's avatar
茶陵後 已提交
1309
为了获得更完整的图片,你还可以直接重写子类`DefaultErrorWebExceptionHandler`并覆盖特定的方法。
1310

茶陵後's avatar
茶陵後 已提交
1311
在某些情况下,在控制器或处理程序函数级别处理的错误不会被[度量基础设施](actuator.html#actuator.metrics.supported.spring-webflux)记录。通过将已处理的异常设置为请求属性,应用程序可以确保将此类异常与请求度量一起记录:
1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373

```
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.reactive.result.view.Rendering;
import org.springframework.web.server.ServerWebExchange;

@Controller
public class MyExceptionHandlingController {

    @GetMapping("/profile")
    public Rendering userProfile() {
        // ...
        throw new IllegalStateException();
    }

    @ExceptionHandler(IllegalStateException.class)
    public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) {
        exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
        return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build();
    }

}

```

##### 自定义错误页面

如果你想为给定的状态代码显示自定义 HTML 错误页,那么可以将文件添加到`/error`目录。错误页可以是静态 HTML(即添加到任何静态资源目录下),也可以是用模板构建的。文件的名称应该是确切的状态代码或系列掩码。

例如,要将`404`映射到静态 HTML 文件,你的目录结构如下:

```
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>
```

要使用 mustache 模板映射所有`5xx`错误,你的目录结构如下:

```
src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.mustache
             +- <other templates>
```

#### 2.1.7.网页过滤器

茶陵後's avatar
茶陵後 已提交
1374
Spring WebFlux 提供了一个`WebFilter`接口,其可以实现为过滤 HTTP 请求-响应交换。`WebFilter`在应用程序上下文中找到的 bean 将自动用于过滤每个交换。
1375 1376 1377 1378 1379 1380

在过滤器的顺序很重要的地方,它们可以实现`Ordered`或用`@Order`进行注释。 Spring 引导自动配置可以为你配置 Web 过滤器。当它这样做时,将使用下表所示的订单:

|网页过滤器|             Order              |
|---------------------------------------|--------------------------------|
|`MetricsWebFilter`|`Ordered.HIGHEST_PRECEDENCE + 1`|
茶陵後's avatar
茶陵後 已提交
1381
|`WebFilterChainProxy`( Spring 证券)|             `-100`             |
1382 1383 1384 1385 1386 1387 1388 1389
|`HttpTraceWebFilter`|`Ordered.LOWEST_PRECEDENCE - 10`|

### 2.2.嵌入式反应式服务器支持

Spring 引导包括对以下嵌入式反应性 Web 服务器的支持:Reactor Netty、 Tomcat、 Jetty 和 Undertow。大多数开发人员使用适当的“启动器”来获得完全配置的实例。默认情况下,嵌入式服务器监听端口 8080 上的 HTTP 请求。

### 2.3.反应式服务器资源配置

茶陵後's avatar
茶陵後 已提交
1390
当自动配置反应堆网络或 Jetty 服务器时, Spring 引导将创建特定的 bean,这些 bean 将向服务器实例提供 HTTP 资源:`ReactorResourceFactory``JettyResourceFactory`
1391

茶陵後's avatar
茶陵後 已提交
1392
默认情况下,这些资源也将与 Reactor Netty 和 Jetty 客户端共享,以获得最佳性能,给定:
1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403

* 同样的技术也用于服务器和客户端。

* 客户机实例是使用由 Spring 引导自动配置的`WebClient.Builder` Bean 来构建的。

开发人员可以通过提供自定义`ReactorResourceFactory``JettyResourceFactory` Bean 来覆盖 Jetty 和反应器网络的资源配置-这将同时应用于客户机和服务器。

你可以在[WebClient 运行时部分](io.html#io.rest-client.webclient.runtime)中了解有关客户端资源配置的更多信息。

## 3. 优雅的关机

茶陵後's avatar
茶陵後 已提交
1404
所有四个嵌入式 Web 服务器( Jetty、Reactor Netty、 Tomcat 和 Undertow)以及反应式和基于 Servlet 的 Web 应用程序都支持优雅的关闭。它作为关闭应用程序上下文的一部分出现,并在停止`SmartLifecycle`bean 的最早阶段执行。此停止处理使用超时,超时提供了一个宽限期,在此期间将允许完成现有的请求,但不允许新的请求。不允许新请求的确切方式取决于所使用的 Web 服务器。 Jetty、反应堆网络和 Tomcat 将在网络层停止接受请求。 Undertow 将接受请求,但立即响应具有服务不可用的(503)响应。
1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444

|   |使用 Tomcat 的优雅关机需要 Tomcat 9.0.33 或更高版本。|
|---|--------------------------------------------------------------|

要启用优雅的关机,请配置`server.shutdown`属性,如以下示例所示:

属性

```
server.shutdown=graceful
```

Yaml

```
server:
  shutdown: "graceful"
```

要配置超时周期,请配置`spring.lifecycle.timeout-per-shutdown-phase`属性,如以下示例所示:

属性

```
spring.lifecycle.timeout-per-shutdown-phase=20s
```

Yaml

```
spring:
  lifecycle:
    timeout-per-shutdown-phase: "20s"
```

|   |如果 IDE 没有发送适当的`SIGTERM`信号,那么在 IDE 中使用 Graceful Shutdown 可能无法正常工作。<br/>有关更多详细信息,请参见 IDE 的文档。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 4. Spring Security

茶陵後's avatar
茶陵後 已提交
1445
如果[Spring Security](https://spring.io/projects/spring-security)在 Classpath 上,那么默认情况下 Web 应用程序是安全的。 Spring 启动依赖于 Spring 安全性的内容协商策略来确定是否使用`httpBasic``formLogin`。要向 Web 应用程序添加方法级安全性,还可以添加带有所需设置的`@EnableGlobalMethodSecurity`。其他信息可以在[Spring Security Reference Guide](https://docs.spring.io/spring-security/reference/5.6.2/servlet/authorization/method-security.html)中找到。
1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459

默认的`UserDetailsService`只有一个用户。用户名是`user`,密码是随机的,并在应用程序启动时在信息级别打印,如以下示例所示:

```
Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35
```

|   |如果你微调了日志配置,请确保将`org.springframework.boot.autoconfigure.security`类别设置为 log`INFO`-level 消息。<br/>否则,将不打印默认密码。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

你可以通过提供`spring.security.user.name``spring.security.user.password`来更改用户名和密码。

默认情况下,在 Web 应用程序中获得的基本特性是:

茶陵後's avatar
茶陵後 已提交
1460
* a`UserDetailsService`(或`ReactiveUserDetailsService`在 WebFlux 应用程序的情况下) Bean 具有内存存储和单个用户生成的密码(参见[`Security属性.User`](https://DOCS. Spring.io/ Spring-boot/DOCS/2.6.4/api/org/springframework/boot/autoformit/autofigure/security/securityproperties.user.html)用于用户的属性)。
1461

茶陵後's avatar
茶陵後 已提交
1462
* 基于表单的登录或 HTTP 基本安全性(取决于请求中的头)用于整个应用程序(如果致动器在 Classpath 上,则包括致动器端点)。
1463 1464 1465

* 用于发布身份验证事件的`DefaultAuthenticationEventPublisher`

茶陵後's avatar
茶陵後 已提交
1466
你可以通过为它添加 Bean 来提供不同的`AuthenticationEventPublisher`
1467 1468 1469

### 4.1.MVC 安全

茶陵後's avatar
茶陵後 已提交
1470
默认的安全配置是在`SecurityAutoConfiguration``UserDetailsServiceAutoConfiguration`中实现的。`SecurityAutoConfiguration`用于 Web 安全的 imports`SpringBootWebSecurityConfiguration``UserDetailsServiceAutoConfiguration`配置身份验证,这在非 Web 应用程序中也是相关的。要完全关闭默认的 Web 应用程序安全配置,或者合并多个 Spring 安全组件,例如 OAuth2 客户机和资源服务器,请添加类型`SecurityFilterChain`的 Bean(这样做不会禁用`UserDetailsService`配置或执行器的安全性)。
1471 1472 1473

要同时关闭`UserDetailsService`配置,可以添加类型为`UserDetailsService``AuthenticationProvider``AuthenticationManager`的 Bean。

茶陵後's avatar
茶陵後 已提交
1474
可以通过添加自定义`SecurityFilterChain``WebSecurityConfigurerAdapter`来覆盖访问规则 Bean。 Spring Boot 提供了方便的方法,这些方法可用于重写执行器端点和静态资源的访问规则。`EndpointRequest`可用于创建`RequestMatcher`这是基于`management.endpoints.web.base-path`属性的。`PathRequest`可用于为常用位置中的资源创建`RequestMatcher`
1475 1476 1477

### 4.2.WebFlux 安全性

茶陵後's avatar
茶陵後 已提交
1478
与 Spring MVC 应用程序类似,你可以通过添加`spring-boot-starter-security`依赖项来保护你的 WebFlux 应用程序。默认的安全配置是在`ReactiveSecurityAutoConfiguration``UserDetailsServiceAutoConfiguration`中实现的。`ReactiveSecurityAutoConfiguration`导入`WebFluxSecurityConfiguration`用于 Web 安全和`UserDetailsServiceAutoConfiguration`配置身份验证,这在非 Web 应用程序中也是相关的。要完全关闭默认的 Web 应用程序安全配置,可以添加类型`WebFilterChainProxy`的 Bean(这样做不会禁用`UserDetailsService`配置或执行器的安全性)。
1479

茶陵後's avatar
茶陵後 已提交
1480
要同时关闭`UserDetailsService`配置,可以添加类型`ReactiveUserDetailsService``ReactiveAuthenticationManager`的 Bean。
1481

茶陵後's avatar
茶陵後 已提交
1482
Spring 使用诸如 OAuth2 客户端和资源服务器的多个安全组件的访问规则可以通过添加自定义 Bean 来配置。 Spring Boot 提供了方便的方法,这些方法可以用来覆盖执行器端点和静态资源的访问规则。`EndpointRequest`可以用来创建一个`ServerWebExchangeMatcher`,即基于`management.endpoints.web.base-path`的属性。
1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517

`PathRequest`可用于为常用位置中的资源创建`ServerWebExchangeMatcher`

例如,你可以通过添加以下内容来定制你的安全配置:

```
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.authorizeExchange((spec) -> {
            spec.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
            spec.pathMatchers("/foo", "/bar").authenticated();
        });
        http.formLogin();
        return http.build();
    }

}

```

### 4.3.OAuth2

[OAuth2](https://oauth.net/2/)是 Spring 支持的一个广泛使用的授权框架。

#### 4.3.1.客户

茶陵後's avatar
茶陵後 已提交
1518
如果你的 Classpath 上有`spring-security-oauth2-client`,则可以利用一些自动配置来设置 OAuth2/Open ID Connect 客户端。此配置使用`OAuth2Client属性`下的属性。相同的性质适用于 Servlet 和反应性应用。
1519

茶陵後's avatar
茶陵後 已提交
1520
你可以在`spring.security.oauth2.client`前缀下注册多个 OAuth2 客户机和提供者,如以下示例所示:
1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588

属性

```
spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri=https://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization-code

spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri=https://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server/oauth/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server/oauth/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server/token_keys
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
```

Yaml

```
spring:
  security:
    oauth2:
      client:
        registration:
          my-client-1:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for user scope"
            provider: "my-oauth-provider"
            scope: "user"
            redirect-uri: "https://my-redirect-uri.com"
            client-authentication-method: "basic"
            authorization-grant-type: "authorization-code"

          my-client-2:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for email scope"
            provider: "my-oauth-provider"
            scope: "email"
            redirect-uri: "https://my-redirect-uri.com"
            client-authentication-method: "basic"
            authorization-grant-type: "authorization_code"

        provider:
          my-oauth-provider:
            authorization-uri: "https://my-auth-server/oauth/authorize"
            token-uri: "https://my-auth-server/oauth/token"
            user-info-uri: "https://my-auth-server/userinfo"
            user-info-authentication-method: "header"
            jwk-set-uri: "https://my-auth-server/token_keys"
            user-name-attribute: "name"
```

茶陵後's avatar
茶陵後 已提交
1589
对于支持[OpenID 连接发现](https://openid.net/specs/openid-connect-discovery-1_0.html)的 OpenID Connect 提供者,可以进一步简化配置。提供程序需要配置`issuer-uri`,这是它所断言的作为其发行者标识符的 URI。例如,如果提供的`issuer-uri`是“https://example.com”,那么将对“https://example.com/.well-nown/openid-configuration”进行`OpenID Provider Configuration Request`。预期结果是`OpenID Provider Configuration Response`。下面的示例展示了如何使用`issuer-uri`配置 OpenID Connect 提供程序:
1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635

属性

```
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
```

Yaml

```
spring:
  security:
    oauth2:
      client:
        provider:
          oidc-provider:
            issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"
```

默认情况下, Spring Security 的`OAuth2LoginAuthenticationFilter`只处理匹配`/login/oauth2/code/*`的 URL。如果希望自定义`redirect-uri`以使用不同的模式,则需要提供处理该自定义模式的配置。例如,对于 Servlet 应用程序,你可以添加你自己的类似于以下内容的`SecurityFilterChain`:

```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration(proxyBeanMethods = false)
public class MyOAuthClientConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
        http.oauth2Login().redirectionEndpoint().baseUri("custom-callback");
        return http.build();
    }

}

```

|   |Spring 引导自动配置由 Spring Security 用于管理客户端注册的`InMemoryOAuth2AuthorizedClientService`<br/>`InMemoryOAuth2AuthorizedClientService`的功能有限,我们建议仅将其用于开发环境。<br/>对于生产环境,可以考虑使用`JdbcOAuth2AuthorizedClientService`或创建自己的`OAuth2AuthorizedClientService`实现。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

##### OAuth2 共同提供者的客户端注册

茶陵後's avatar
茶陵後 已提交
1636
对于常见的 OAuth2 和 OpenID 提供者,包括 Google、GitHub、Facebook 和 OKTA,我们提供了一组提供者默认值(分别为`google``github``facebook``okta`)。
1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689

如果不需要自定义这些提供程序,可以将`provider`属性设置为需要推断默认值的属性。此外,如果客户端注册的键与缺省支持的提供者匹配, Spring 启动也会推断出这一点。

换句话说,以下示例中的两个配置使用了 Google Provider:

属性

```
spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password
```

Yaml

```
spring:
  security:
    oauth2:
      client:
        registration:
          my-client:
            client-id: "abcd"
            client-secret: "password"
            provider: "google"
          google:
            client-id: "abcd"
            client-secret: "password"
```

#### 4.3.2.资源服务器

如果你的 Classpath 上有`spring-security-oauth2-resource-server`,则 Spring 引导可以设置 OAuth2 资源服务器。对于 JWT 配置,需要指定一个 JWK 集 URI 或 OIDC Issuer URI,如以下示例所示:

属性

```
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys
```

Yaml

```
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: "https://example.com/oauth2/default/v1/keys"
```

茶陵後's avatar
茶陵後 已提交
1690
属性
1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736

```
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
```

Yaml

```
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"
```

|   |如果授权服务器不支持 JWK 设置的 URI,则可以使用用于验证 JWT 签名的公钥配置资源服务器。<br/>这可以使用`spring.security.oauth2.resourceserver.jwt.public-key-location`属性完成,值需要指向包含 PEM 编码的 X509 格式的公钥的文件。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

同样的性质也适用于 Servlet 和活性应用。

或者,你可以为 Servlet 应用程序定义自己的`JwtDecoder` Bean,或者为反应性应用程序定义`ReactiveJwtDecoder`

在使用不透明令牌而不是 JWTS 的情况下,你可以配置以下属性来通过内省来验证令牌:

Properties

```
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://example.com/check-token
spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id
spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret
```

Yaml

```
spring:
  security:
    oauth2:
      resourceserver:
        opaquetoken:
          introspection-uri: "https://example.com/check-token"
          client-id: "my-client-id"
          client-secret: "my-client-secret"
```

茶陵後's avatar
茶陵後 已提交
1737
同样,同样的性质也适用于 Servlet 和无反应的应用。
1738 1739 1740 1741 1742

或者,你可以为 Servlet 应用程序定义自己的`OpaqueTokenIntrospector` Bean,或者为反应性应用程序定义`ReactiveOpaqueTokenIntrospector`

#### 4.3.3.授权服务器

茶陵後's avatar
茶陵後 已提交
1743
Spring 目前,安全性不提供对实现 OAuth2.0 授权服务器的支持。然而,该功能可从[Spring Security OAuth](https://spring.io/projects/spring-security-oauth)项目中获得,该项目最终将完全被 Spring 安全性所取代。在此之前,你可以使用`spring-security-oauth2-autoconfigure`模块轻松地设置 OAuth2.0 授权服务器;有关说明,请参见其[文件](https://docs.spring.io/spring-security-oauth2-boot/)
1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815

### 4.4.SAML2.0

#### 4.4.1.依赖方

如果你的 Classpath 上有`spring-security-saml2-service-provider`,则可以利用一些自动配置来设置 SAML2.0 依赖方。此配置使用`Saml2RelyingPartyProperties`下的属性。

依赖方注册表示身份提供程序 IDP 和服务提供程序 SP 之间的配对配置。你可以在`spring.security.saml2.relyingparty`前缀下注册多个依赖方,如下例所示:

Properties

```
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.verification.credentials[0].certificate-location=path-to-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.entity-id=remote-idp-entity-id1
spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.sso-url=https://remoteidp1.sso.url

spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.verification.credentials[0].certificate-location=path-to-other-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.entity-id=remote-idp-entity-id2
spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.sso-url=https://remoteidp2.sso.url
```

Yaml

```
spring:
  security:
    saml2:
      relyingparty:
        registration:
          my-relying-party1:
            signing:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            decryption:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            identityprovider:
              verification:
                credentials:
                - certificate-location: "path-to-verification-cert"
              entity-id: "remote-idp-entity-id1"
              sso-url: "https://remoteidp1.sso.url"

          my-relying-party2:
            signing:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            decryption:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            identityprovider:
              verification:
                credentials:
                - certificate-location: "path-to-other-verification-cert"
              entity-id: "remote-idp-entity-id2"
              sso-url: "https://remoteidp2.sso.url"
```

## 5. Spring 届会议

茶陵後's avatar
茶陵後 已提交
1816
Spring 引导为广泛的数据存储范围提供[Spring Session](https://spring.io/projects/spring-session)自动配置。 Servlet 在构建 Web 应用程序时,可以自动配置以下存储:
1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835

* JDBC

* 雷迪斯

* 黑泽尔卡斯特

* MongoDB

Servlet 自动配置取代了使用`@Enable*HttpSession`的需要。

在构建反应式 Web 应用程序时,可以自动配置以下存储:

* 雷迪斯

* MongoDB

反应式自动配置取代了使用`@Enable*WebSession`的需要。

茶陵後's avatar
茶陵後 已提交
1836
如果在 Classpath 上存在单个 Spring 会话模块,则 Spring 引导自动使用该存储实现。如果有多个实现,则必须选择希望用于存储会话的[`StoreType`](https://github.com/ Spring-projects/ Spring-boot/tree/v2.6.4/ Spring-boot-project/ Spring-boot-autofigure/SRC/main/java/org/org/springframework/boot/autofigure/autofigure/session/storetype.java)。例如,要使用 JDBC 作为后端存储,你可以按以下方式配置应用程序:
1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871

Properties

```
spring.session.store-type=jdbc
```

Yaml

```
spring:
  session:
    store-type: "jdbc"
```

|   |通过将`store-type`设置为`none`,可以禁用 Spring 会话。|
|---|---------------------------------------------------------------------|

每个商店都有特定的附加设置。例如,可以自定义 JDBC 存储表的名称,如下例所示:

Properties

```
spring.session.jdbc.table-name=SESSIONS
```

Yaml

```
spring:
  session:
    jdbc:
      table-name: "SESSIONS"
```

茶陵後's avatar
茶陵後 已提交
1872
为了设置会话的超时,你可以使用`spring.session.timeout`属性。如果在 Servlet Web 应用程序中未设置该属性,则自动配置将返回到`server.servlet.session.timeout`的值。
1873 1874 1875 1876 1877 1878 1879

你可以使用`@Enable*HttpSession`( Servlet)或`@Enable*WebSession`(反应式)来控制 Spring 会话的配置。这将导致自动配置后退。 Spring 然后可以使用注释的属性而不是先前描述的配置属性来配置会话。

## 6. Spring 仇恨

如果你开发了一个使用超媒体的 RESTful API, Spring Boot 为 Spring Hateoas 提供了自动配置,这在大多数应用程序中都能很好地工作。自动配置取代了使用`@EnableHypermediaSupport`的需要,并注册了许多 bean,以方便构建基于超媒体的应用程序,包括`LinkDiscoverers`(用于客户端支持)和`ObjectMapper`,这些配置用于将响应正确地编组到所需的表示中。`ObjectMapper`是通过设置各种`spring.jackson.*`属性来定制的,或者,如果存在一个属性,则通过`Jackson2ObjectMapperBuilder`来定制 Bean。

茶陵後's avatar
茶陵後 已提交
1880
你可以通过使用`@EnableHypermediaSupport`来控制 Spring Hateoas 的配置。注意,这样做会禁用前面描述的`ObjectMapper`定制。
1881

茶陵後's avatar
茶陵後 已提交
1882
|   |`spring-boot-starter-hateoas`是特定于 Spring MVC 的,不应与 Spring WebFlux 合并。<br/>为了在 Spring WebFlux 中使用 Spring Hateoas,可以在`org.springframework.hateoas:spring-hateoas`以及`spring-boot-starter-webflux`上添加一个直接依赖项。|
1883 1884
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

茶陵後's avatar
茶陵後 已提交
1885
## 7. 接下来要读什么?
1886

茶陵後's avatar
茶陵後 已提交
1887
你现在应该对如何使用 Spring 引导来开发 Web 应用程序有了很好的了解。接下来的几个部分描述了 Spring 引导如何集成各种[数据技术](data.html#data)[消息传递系统](messaging.html#messaging)和其他 IO 功能。你可以根据应用程序的需求选择其中的任何一个。