Spring Boot对Servlet web应用的支持

https://docs.spring.io/spring-boot/docs/3.2.0/reference/htmlsingle/#web.servlet

Spring Web MVC 框架

Spring Web MVC 框架(通常称为“Spring MVC”)是一个功能丰富的“模型 视图 控制器”Web 框架。Spring MVC 允许你创建特殊的 @Controller@RestController bean 来处理传入的 HTTP 请求。控制器中的方法通过使用 @RequestMapping 注解映射到 HTTP。

以下代码展示了一个典型的 @RestController,它提供 JSON 数据:

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("/{userId}")
    public User getUser(@PathVariable Long userId) {
        return this.userRepository.findById(userId).get();
    }

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

    @DeleteMapping("/{userId}")
    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 框架的核心部分。

提示:可以根据需要定义任意数量的 RouterFunction bean 来模块化路由器的定义。如果需要应用优先级,可以对 bean 进行排序。

Spring MVC 自动配置

Spring Boot 为 Spring MVC 提供了自动配置,这对于大多数应用程序而言非常有用。它取代了使用 @EnableWebMvc 的需求,并且这两个不能一起使用。除了 Spring MVC 的默认设置外,自动配置还提供了以下功能:

  • 包含 ContentNegotiatingViewResolverBeanNameViewResolver bean。
  • 支持提供静态资源,包括支持 WebJars。
  • 自动注册 ConverterGenericConverterFormatter bean。
  • 支持 HttpMessageConverters
  • 自动注册 MessageCodesResolver。
  • 支持静态的 index.html
  • 自动使用 ConfigurableWebBindingInitializer bean。

如果你想保留这些 Spring Boot MVC 自定义,并进行更多 MVC 自定义(例如拦截器、格式化程序、视图控制器和其它功能),则可以添加你自己的类型为 WebMvcConfigurer@Configuration 类,但不使用 @EnableWebMvc

如果你想提供 RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver 的自定义实例,同时仍保留 Spring Boot MVC 的自定义设置,则可以声明一个类型为 WebMvcRegistrations 的 bean,并使用它来提供这些组件的自定义实例。这些自定义实例将接受 Spring MVC 的进一步初始化和配置。如果要参与(且希望)覆盖后续的处理,则应该使用 WebMvcConfigurer

如果你不想使用自动配置,并希望完全控制 Spring MVC,请添加你自己的带有 @EnableWebMvc 注解的 @Configuration。另外,也可以添加你自己的带有 @Configuration 注解的 DelegatingWebMvcConfiguration

Spring MVC 转换服务(Conversion Service)

Spring MVC 使用的 ConversionService 与用于从 application.propertiesapplication.yaml 文件中转换值的 ConversionService 不同。这意味着 PeriodDurationDataSize 转换器不可用,并且 @DurationUnit@DataSizeUnit 注解将被忽略。

如果你想自定义 Spring MVC 使用的 ConversionService,可以提供一个带有 addFormatters 方法的 WebMvcConfigurer bean。从该方法中,你可以注册任何你喜欢的转换器,或者可以委托给 ApplicationConversionService 上可用的静态方法。

还可以使用 spring.mvc.format.* 配置属性来定制转换。如果未进行配置,则使用以下默认值:
在这里插入图片描述

HttpMessageConverters

Spring MVC 使用 HttpMessageConverter 接口来转换 HTTP 请求和响应。它提供了合理的默认设置。例如,对象可以自动转换为 JSON(通过使用 Jackson 库)或 XML(如果可用,则使用 Jackson XML 扩展,否则使用 JAXB)。默认情况下,字符串以 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 都会被添加到转换器列表中。你也可以用相同的方式覆盖默认转换器。

MessageCodesResolver

Spring MVC 有一个用于从绑定错误中生成错误代码以呈现错误消息的策略:MessageCodesResolver。如果你设置了 spring.mvc.message-codes-resolver-format 属性为 PREFIX_ERROR_CODEPOSTFIX_ERROR_CODE,Spring Boot 将为您创建一个。

静态内容

默认情况下,Spring Boot 从类路径中的名为 /static(或 /public/resources/META-INF/resources)的目录或从 ServletContext 的根目录中提供静态内容。它使用 Spring MVC 的 ResourceHttpRequestHandler,因此你可以通过添加自己的 WebMvcConfigurer 并覆盖 addResourceHandlers 方法来修改该行为。

在独立的 Web 应用程序中,容器中的默认 servlet 未启用。可以通过使用 server.servlet.register-default-servlet 属性来启用它。

默认 servlet 作为回退机制,如果 Spring 决定不处理它,则从 ServletContext 的根目录提供内容。在大多数情况下,这不会发生(除非你修改了默认的 MVC 配置),因为 Spring 总是可以通过 DispatcherServlet 处理请求。

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

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

你还可以使用 spring.web.resources.static-locations 属性自定义静态资源位置(用目录位置的列表替换默认值)。Servlet 上下文的根路径,“/”,也将自动作为位置添加。

除了前面提到的“标准”静态资源位置外,还对 Webjars 内容进行了特殊处理。默认情况下,如果以 Webjars 格式打包,则任何路径为 /webjars/** 的资源都从 jar 文件中提供。可以使用 spring.mvc.webjars-path-pattern 属性自定义路径。

提示:如果你的应用程序被打包为 jar 文件,请不要使用 src/main/webapp 目录。尽管此目录是一个常见的标准,但它仅适用于 war 打包,并且如果你生成 jar 文件,大多数构建工具都会静默忽略它。

Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许使用诸如打破缓存的静态资源或使用 Webjars 的无版本 URL 等。

要使用 Webjars 的无版本 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。否则,所有 Webjars 都将解析为 404

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

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

注意:由于自动配置了 ResourceUrlEncodingFilter,因此可以在运行时在模板中重写资源链接,该过滤器适用于 Thymeleaf 和 FreeMarker。在使用 JSP 时,应手动声明此过滤器。其它模板引擎目前不自动支持,但可以通过自定义模板宏/帮助程序和 ResourceUrlProvider 的使用来支持。

当使用例如 JavaScript 模块加载器动态加载资源时,重命名文件不是一个选项。这就是为什么还支持其它策略,并且可以将它们组合在一起。“fixed”策略在 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

使用此配置,位于 “/js/lib/” 下的 JavaScript 模块使用固定的版本策略(“/v12/js/lib/mymodule.js”),而其它资源仍然使用内容版本(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>)。

欢迎页(Welcome Page)

Spring Boot 支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找 index.html 文件。如果找不到,则查找index 模板。如果找到其中任何一个,它将被自动用作应用程序的欢迎页面。

这仅作为应用程序定义的实际索引路由的回退。顺序由 HandlerMapping bean 的顺序定义,默认情况下如下:
在这里插入图片描述

自定义网站图标(Custom Favicon)

与其它静态资源一样,Spring Boot 检查配置的静态内容位置中是否存在 favicon.ico。如果存在这样的文件,它将被自动用作应用程序的网站图标。

路径匹配和内容协商

Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射(例如,Controller 方法上的 @GetMapping 注解)进行匹配,将传入的 HTTP 请求映射到处理程序。

Spring Boot 默认选择禁用后缀模式匹配,这意味着像 “GET /projects/spring-boot.json” 这样的请求将不会与 @GetMapping("/projects/spring-boot") 映射匹配。这被认为是 Spring MVC 应用程序的最佳实践。此功能在过去对于不发送适当的 “Accept” 请求头的 HTTP 客户端很有用;我们需要确保向客户端发送正确的内容类型。如今,内容协商更加可靠。

处理不始终发送正确的 “Accept” 请求头的 HTTP 客户端还有其它方法。我们不使用后缀匹配,而是可以使用查询参数来确保像 “GET /projects/spring-boot?format=json” 这样的请求会映射到 @GetMapping("/projects/spring-boot")

spring.mvc.contentnegotiation.favor-parameter=true

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

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

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

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

从 Spring Framework 5.3 开始,Spring MVC 支持两种将请求路径与控制器匹配的策略。默认情况下,Spring Boot 使用 PathPatternParser 策略。与 AntPathMatcher 策略相比,PathPatternParser 是一种经过优化的实现,但也有一些限制。PathPatternParser 限制了一些路径模式变体的使用。此外,它与配置 DispatcherServlet 的路径前缀(spring.mvc.servlet.path)不兼容。

可以使用 spring.mvc.pathmatch.matching-strategy 配置属性来配置此策略,如下所示:

spring.mvc.pathmatch.matching-strategy=ant-path-matcher

默认情况下,如果找不到处理请求的处理器,Spring MVC 将发送一个 404 Not Found 错误响应。要改为抛出 NoHandlerFoundException,请将 configprop:spring.mvc.throw-exception-if-no-handler-found 设置为 true。请注意,默认情况下,静态内容的提供会映射到 /**,因此会为所有请求提供处理程序。要抛出 NoHandlerFoundException,还必须将 spring.mvc.static-path-pattern 设置为更具体的值,例如 /resources/**,或者将 spring.web.resources.add-mappings 设置为 false 以完全禁用静态内容的提供。

ConfigurableWebBindingInitializer

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

模版引擎

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

Spring Boot 为以下模板引擎提供了自动配置支持:

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Mustache

提示:如果可能的话,应避免使用 JSP。在使用嵌入式 Servlet 容器时,JSP 存在一些已知的限制。

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

提示:根据你运行应用程序的方式,你的 IDE 可能会以不同的顺序排列类路径。从主方法在其 IDE 中运行应用程序时,类路径的顺序与使用 Maven 或 Gradle 运行应用程序或从打包的 jar 文件运行时的顺序不同。这可能导致 Spring Boot 无法找到预期的模板。如果你遇到这个问题,可以在 IDE 中重新排序类路径,将模块的类和资源放在前面。

错误处理

默认情况下,Spring Boot 提供了一个 /error 映射,它以合理的方式处理所有错误,并在 servlet 容器中注册为“global”错误页面。对于机器客户端,它会生成一个包含错误详细信息、HTTP 状态和异常消息的 JSON 响应。对于浏览器客户端,有一个“whitelabel”错误视图,它以 HTML 格式呈现相同的数据(要自定义它,请添加一个解析为errorView)。

如果你想要自定义默认的错误处理行为,有许多 server.error 属性可以设置。

要完全替换默认行为,你可以实现 ErrorController 并注册该类型的 bean 定义,或者添加一个类型为 ErrorAttributes 的 bean 以使用现有机制但替换其内容。

提示:可以使用 BasicErrorController 作为自定义 ErrorController 的基类。特别是当你想为新的内容类型添加处理程序时(默认情况下,特别处理 text/html 并为其它所有内容提供备用),这特别有用。要做到这一点,请扩展 BasicErrorController,添加一个带有 @RequestMapping 的公共方法,该方法具有 produces 属性,并创建你新类型的 bean。

从 Spring Framework 6.0 开始,支持FC 7807 Problem Details 。Spring MVC 可以使用 application/problem+json 媒体类型生成自定义错误消息,如下所示:

{
  "type": "https://example.org/problems/unknown-project",
  "title": "Unknown project",
  "status": 404,
  "detail": "No project found for id 'spring-unknown'",
  "instance": "/projects/spring-unknown"
}

可以通过将 spring.mvc.problemdetails.enabled 设置为 true 来启用此支持。

你还可以定义一个带有 @ControllerAdvice 注解的类,以自定义要返回给特定控制器和/或异常类型的 JSON 文档,如下所示:

import jakarta.servlet.RequestDispatcher;
import jakarta.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;
    }

}

在前面的示例中,如果 SomeController 中定义的控制器抛出了 MyException,则使用 MyErrorBody POJO 的 JSON 表示形式,而不是 ErrorAttributes 表示形式。

在某些情况下,控制器级别处理的错误不会被度量基础架构(metrics infrastructure)记录。应用程序可以通过将处理的异常设置为请求属性来确保这些异常被请求指标记录:

import jakarta.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 jakarta.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;
    }

}

你还可以使用常规的 Spring MVC 功能,如 @ExceptionHandler 方法和@ControllerAdvice。然后,ErrorController 捕获任何未处理的异常。

在 Spring MVC 之外映射错误页面

对于不使用 Spring MVC 的应用程序,你可以使用 ErrorPageRegistrar 接口直接注册 ErrorPages。此抽象直接与底层嵌入式 servlet 容器一起工作,即使你没有 Spring MVC 的 DispatcherServlet 也能正常工作。

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(如在Jersey和Wicket等非Spring web框架中常见)处理,那么Filter必须显式地注册为ERROR分发器,如下所示:

import java.util.EnumSet;

import jakarta.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分发器类型。

WAR部署中的错误处理

当部署到servlet容器时,Spring Boot使用其错误页面过滤器将带有错误状态的请求转发到适当的错误页面。这是必要的,因为servlet规范不提供用于注册错误页面的API。根据你的war文件部署的容器以及应用程序使用的技术,可能需要进行一些额外的配置。

只有当响应尚未提交时,错误页面过滤器才能将请求转发到正确的错误页面。默认情况下,WebSphere Application Server 8.0及更高版本会在servlet的service方法成功完成后提交响应。你应该将com.ibm.ws.webcontainer.invokeFlushAfterService设置为false来禁用此行为。

CORS 支持

跨源资源共享(CORS)是一种由大多数浏览器实现的W3C规范,它允许你以灵活的方式指定哪些跨域请求是授权的,而不是使用不太安全且功能较弱的方法,如IFRAME或JSONP。

从4.2版本开始,Spring MVC支持CORS。在Spring Boot应用程序中使用带有@CrossOrigin注解的控制器方法CORS配置不需要任何特定的配置。可以通过注册一个带有自定义addCorsMappings(CorsRegistry)方法的WebMvcConfigurer bean来定义全局CORS配置,如下所示:

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/**");
            }

        };
    }

}

JAX-RS 和 Jersey

如果你更喜欢使用JAX-RS编程模型来创建REST端点,则可以使用可用的实现之一来替代Spring MVC。Jersey和Apache CXF开箱即用,效果非常好。CXF要求你将ServletFilter注册为应用程序上下文中的@Bean。Jersey具有一些原生的Spring支持,因此在Spring Boot中为其提供了自动配置支持,以及一个启动器。

要开始使用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);
    }

}

注意:Jersey对可执行归档文件的扫描支持相当有限。例如,当运行可执行war文件时,它无法扫描完全可执行jar文件中或WEB-INF/classes中的包中的端点。为避免此限制,不应使用packages方法,而应使用register方法单独注册端点,如前面的示例所示。

对于更高级别的自定义,还可以注册实现ResourceConfigCustomizer的任意数量的bean。

所有已注册的端点都应该是带有HTTP资源注解(@GET等)的@Components,如下所示:

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.springframework.stereotype.Component;

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

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

}

由于Endpoint是一个Spring @Component,因此其生命周期由Spring管理,你可以使用@Autowired注解注入依赖项,并使用@Value注解注入外部配置。默认情况下,Jersey servlet已注册并映射到/*。你可以通过将@ApplicationPath添加到ResourceConfig来更改映射。

默认情况下,Jersey是作为名为jerseyServletRegistrationServletRegistrationBean类型的@Bean中的servlet设置的。默认情况下,servlet会延迟初始化,但你可以通过设置spring.jersey.servlet.load-on-startup来定制此行为。你可以通过创建具有相同名称的bean来禁用或覆盖该bean。还可以通过设置spring.jersey.type=filter来使用过滤器代替servlet(在这种情况下,要替换或覆盖的@BeanjerseyFilterRegistration)。过滤器具有@Order,你可以使用spring.jersey.filter.order进行设置。当使用Jersey作为过滤器时,必须存在一个servlet来处理Jersey未拦截的任何请求。如果你的应用程序不包含这样的servlet,则可能通过设置server.servlet.register-default-servlettrue来启用默认servlet。servlet和过滤器的注册都可以通过使用spring.jersey.init.*来指定属性映射来给定初始化参数。

嵌入式Servlet容器支持

对于servlet应用程序,Spring Boot包括对嵌入式Tomcat,Jetty和Undertow服务器的支持。大多数开发人员使用适当的“启动器”来获取完全配置的实例。默认情况下,嵌入式服务器在端口8080上侦听HTTP请求。

Servlet,Filter和Listener

当使用嵌入式servlet容器时,可以通过使用Spring bean或扫描servlet组件来注册servlet规范中的servlet,filter和所有listener(如HttpSessionListener)。

将Servlet,Filter和Listener注册为Spring Bean

任何ServletFilter或servlet *Listener实例,如果是Spring bean,都会注册到嵌入式容器中。这在配置过程中,如果想在application.properties中引用某个值时,会特别方便。

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

如果基于约定的映射不够灵活,可以使用ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean类进行完全控制。

不将过滤器bean排序通常是安全的。如果需要特定的顺序,应该使用@Order注解Filter或实现Ordered。不能通过在其bean方法上使用@Order来配置Filter的顺序。如果无法更改Filter类以添加@Order或实现Ordered,则必须为Filter定义一个FilterRegistrationBean,并使用setOrder(int)方法设置注册bean的顺序。避免配置以Ordered.HIGHEST_PRECEDENCE读取请求体的过滤器,因为这可能会违反你应用程序的字符编码配置。如果servlet过滤器包装了请求,则应该将其配置为小于或等于OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER的顺序。

提示:要查看应用程序中每个Filter 的顺序,请为Web日志组启用调试级别日志记录(logging.level.web=debug)。然后,将在启动时记录已注册过滤器的详细信息,包括其顺序和URL模式。

注意:在注册Filter bean时要小心,因为它们在应用程序生命周期中非常早地被初始化。如果需要注册一个与其它bean交互的Filter,考虑使用DelegatingFilterProxyRegistrationBean

servlet上下文初始化

嵌入式servlet容器不会直接执行jakarta.servlet.ServletContainerInitializer接口或Spring的org.springframework.web.WebApplicationInitializer接口。这是一个有意的设计决策,旨在降低设计为在war内部运行的第三方库可能破坏Spring Boot应用程序的风险。

如果需要在Spring Boot应用程序中进行servlet上下文初始化,则应注册一个实现org.springframework.boot.web.servlet.ServletContextInitializer接口的bean。单个onStartup方法提供了对ServletContext的访问,如果需要,可以轻松用作现有WebApplicationInitializer的适配器。

扫描Servlet,Filter和Listener

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

提示:在独立容器中,@ServletComponentScan不起作用,而是使用容器的内置发现机制。

ServletWebServerApplicationContext

在底层,Spring Boot使用不同类型的ApplicationContext来支持嵌入式servlet容器。ServletWebServerApplicationContext是一种特殊的WebApplicationContext类型,它通过搜索单个ServletWebServerFactory bean来引导自身。通常,已经自动配置了TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory

注意:你通常不需要了解这些实现类。大多数应用程序都是自动配置的,将为你创建适当的ApplicationContextServletWebServerFactory

在嵌入式容器设置中,ServletContext会在应用程序上下文初始化期间作为服务器启动的一部分进行设置。因此,ApplicationContext中的bean无法可靠地使用ServletContext进行初始化。解决此问题的一种方法是将ApplicationContext注入为bean的依赖项,并仅在需要时访问ServletContext。另一种方法是在服务器启动后使用回调。这可以通过使用ApplicationListener来实现,该监听器监听ApplicationStartedEvent,如下:

import jakarta.servlet.ServletContext;

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.web.context.WebApplicationContext;

public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {

    private ServletContext servletContext;

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
    }

}

自定义嵌入式Servlet容器

可以通过使用Spring Environment属性来配置常见的servlet容器设置。通常,你会在application.propertiesapplication.yaml文件中定义这些属性。

常见的服务器设置包括:

  • 网络设置:用于传入HTTP请求的监听端口(server.port)、要绑定的接口地址(server.address)等。
  • 会话设置:会话是否持久(server.servlet.session.persistent)、会话超时时间(server.servlet.session.timeout)、会话数据存储位置(server.servlet.session.store-dir)以及会话cookie配置(server.servlet.session.cookie.*)等。
  • 错误管理:错误页面的位置(server.error.path)等。
  • SSL
  • HTTP压缩

Spring Boot尽可能多地公开常见设置,但这并不总是可能的。对于这些情况,专用的命名空间提供了针对服务器的特定自定义。例如,可以使用嵌入式servlet容器的特定功能来配置访问日志。

SameSite cookie

web浏览器可以使用SameSite cookie属性来控制是否在跨站点请求中提交cookie,以及如何提交。该属性对于现代web浏览器特别重要,因为现代浏览器已经开始改变在缺少该属性时使用的默认值。

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

例如,如果希望会话cookie的SameSite属性为None,可以在application.propertiesapplication.yaml文件中添加以下内容:

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

如果想更改添加到HttpServletResponse中的其它cookie的SameSite属性,可以使用CookieSameSiteSupplier。将Cookie传递给CookieSameSiteSupplier,可以返回SameSite值,或者返回null

可以使用许多方便的工厂和过滤方法来快速匹配特定的cookie。例如,添加以下bean将自动为名称与正则表达式myapp.*匹配的所有cookie应用LaxSameSite

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.*");
    }

}

字符编码

用于请求和响应处理的嵌入式servlet容器的字符编码行为可以通过server.servlet.encoding.*配置属性进行配置。

当请求的Accept-Language头指示了请求的区域(locale)设置时,servlet容器将自动将其映射到字符集。每个容器都提供默认的locale到字符集的映射,你应该验证它们是否满足你的应用程序需求。当它们不满足时,请使用server.servlet.encoding.mapping配置属性来自定义映射,如下所示:

server.servlet.encoding.mapping.ko=UTF-8

在前面的示例中,已将ko(朝鲜语)区域设置映射到UTF-8。这相当于在传统war部署的web.xml文件中的<locale-encoding-mapping-list>条目。

程序化定制

如果你需要以编程方式配置嵌入式servlet容器,则可以注册一个实现WebServerFactoryCustomizer接口的Spring bean。WebServerFactoryCustomizer提供了对ConfigurableServletWebServerFactory的访问,其中包括许多自定义setter方法。以下示例以编程方式设置端口:

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);
    }

}

TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactoryConfigurableServletWebServerFactory的专用变体,它们分别为Tomcat,Jetty和Undertow提供了额外的自定义setter方法。以下示例显示了如何自定义TomcatServletWebServerFactory,该工厂提供了对特定于Tomcat的配置选项的访问:

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()));
    }

}

直接自定义ConfigurableServletWebServerFactory

对于需要你从ServletWebServerFactory继承的更高级使用场景,你可以自己公开这种类型的bean。
为许多配置选项提供了setter。如果你需要执行更复杂的操作,还提供了几个受保护的“钩子”方法。

注意:自动配置的customizer仍应用于你的自定义工厂,因此请谨慎使用该选项。

JSP 限制

当运行使用嵌入式servlet容器(并且打包为可执行存档文件)的Spring Boot应用程序时,JSP支持有一些限制。

  • 如果使用war打包,那么对于Jetty和Tomcat,它应该可以工作。使用java -jar启动的可执行war将可以工作,并且也可以部署到任何标准容器中。使用可执行jar时不支持JSP。
  • Undertow不支持JSP。
  • 创建自定义的error.jsp页面不会覆盖错误处理的默认视图。应该使用自定义错误页面代替。
  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值