05-Web开发(下)

1. 错误处理

1.1 默认规则

https://docs.spring.io/spring-boot/docs/2.3.12.RELEASE/reference/html/spring-boot-features.html#boot-features-error-handling

1.1.1 /error

By default, Spring Boot provides an /error mapping that handles all errors in a sensible way, and it is registered as a “global” error page in the servlet container. For machine clients, it produces a JSON response with details of the error, the HTTP status, and the exception message. For browser clients, there is a “whitelabel” error view that renders the same data in HTML format (to customize it, add a View that resolves to error).

1.1.2 server.error

There are a number of server.error properties that can be set if you want to customize the default error handling behavior.

1.1.3 ErrorXxx.java

To replace the default behavior completely, you can implement ErrorController and register a bean definition of that type or add a bean of type ErrorAttributes to use the existing mechanism(机制) but replace the contents.

The BasicErrorController can be used as a base class for a custom ErrorController. This is particularly useful if you want to add a handler for a new content type (the default is to handle text/html specifically and provide a fallback for everything else). To do so, extend BasicErrorController, add a public method with a @RequestMapping that has a produces attribute, and create a bean of your new type.

You can also define a class annotated with @ControllerAdvice to customize the JSON document to return for a particular controller and/or exception type, as shown in the following example:

@ControllerAdvice(basePackageClasses = AcmeController.class)
public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {

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

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

In the preceding example, if YourException is thrown by a controller defined in the same package as AcmeController, a JSON representation of the CustomErrorType POJO is used instead of the ErrorAttributes representation.

1.2 自动配置

ErrorMvcAutoConfiguration 内定义组件大致如下:

@ConditionalOnMissingBean(value = ErrorAttributes.class)
Bean("errorAttributes", DefaultErrorAttributes.class)

@ConditionalOnMissingBean(value = ErrorController.class)
Bean("basicErrorController", BasicErrorController.class)

@ConditionalOnMissingBean(name = "error")
Bean("error", StaticView.class)

@ConditionalOnMissingBean(ErrorViewResolver.class)
Bean("conventionErrorViewResolver", DefaultErrorViewResolver.class)

1.2.1 ErrorAttributes

定义错误页面中可以包含哪些数据:

为什么要实现 HanderExceptionResolver 接口呢?#1.3.1p2,#1.3.2p1。

1.2.2 ErrorController

1.2.3 StaticView

1.2.4 DefaultErrorViewResolver

1.3 源码跟踪

1.3.1 handler 抛出异常

1.3.2 response.sendError | 兜底

先精准匹配,找 error/500.html;如果没有,再去找 error/5xx.html。

1.4 其他异常处理方式

public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response,
            @Nullable Object handler, Exception ex);
}

public abstract class AbstractHandlerExceptionResolver
                        implements HandlerExceptionResolver, Ordered {
  @Override
  @Nullable
  public ModelAndView resolveException(
        HttpServletRequest request, HttpServletResponse response,
        @Nullable Object handler, Exception ex) {

    if (shouldApplyTo(request, handler)) {
      prepareResponse(ex, response);
      // ======= ↓↓↓↓↓↓ Step Into ↓↓↓↓↓↓ =======
      ModelAndView result = doResolveException(request, response, handler, ex);
      if (result != null) {
        // Print debug message when warn logger is not enabled.
        if (logger.isDebugEnabled()
                && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
          logger.debug("Resolved ["+ex+"]" + (result.isEmpty()?"":" to "+result));
        }
        // Explicitly configured warn logger in logException method.
        logException(ex, request);
      }
      return result;
    } else {
      return null;
    }
  }

  // ...
}

1.4.1 指定能处理哪些异常

@ControllerAdvice(全局异常处理器) + @ExceptionHandler(标注异常处理方法,value 为能够处理的异常)

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({
        ArithmeticException.class, NullPointerException.class,
        ArrayIndexOutOfBoundsException.class})
    public String handlerLowLevelException(Exception e) {
        log.error("Exception is: {}", e);
        return "main";
    }

}

1.4.2 抛出自定义响应状态的异常

@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "访问受限")
public class BusinessException extends RuntimeException {}

这个解析器的解析方式就是 sendError,接着就会引出第二个请求处理过程,如下所示。

1.4.3 抛出 Spring 的内置异常

1.5 自定义异常处理器

可以选择实现 HandlerExceptionResolver 接口,也可以选择继承 AbstractHandlerExceptionResolver,该类实现了 HandlerExceptionResolver 和 Ordered 接口(保证 resolvers 的顺序,如果多个处理器都能处理这个异常,把想用的那个放在靠前位置)。

1.6 小结

闭环了,你理理。

2. 原生组件注入

https://docs.spring.io/spring-boot/docs/2.3.12.RELEASE/reference/html/spring-boot-features.html#boot-features-embedded-container

2.1 注入方式

2.1.1 方式一

(1)类上标注对应的 @WebXxx 注解,类再分别继承 / 实现各自应实现的原生类/接口。

@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {...}

@WebFilter(urlPatterns = {"/css/*", "/images/*"})
public class MyFilter implements Filter {...}

@WebListener
public class MyServletContextListener implements ServletContextListener {...}

(2)在 SpringBoot 主启动类上标注 @ServletComponentScan 注解。

2.1.2 方式二

@Configuration
public class OriginConfig {

  @Bean
  public ServletRegistrationBean myServlet() {
    MyServlet myServlet = new MyServlet();
    return new ServletRegistrationBean(myServlet, "/my");
  }

  @Bean
  public FilterRegistrationBean myFilter() {
    MyFilter myFilter = new MyFilter();
    // return new FilterRegistrationBean(myFilter, myServlet());
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/css/*", "/my"));
    return filterRegistrationBean;
  }

  @Bean
  public ServletListenerRegistrationBean myListener() {
    MyServletContextListener listener = new MyServletContextListener();
    return new ServletListenerRegistrationBean(listener);
  }
}

2.2 DispatcherServlet

3. 嵌入式 Servlet 容器

https://docs.spring.io/spring-boot/docs/2.3.12.RELEASE/reference/html/spring-boot-features.html#boot-features-embedded-container-application-context

3.1 源码分析

Under the hood, Spring Boot uses a different type of ApplicationContext for embedded servlet container support. The ServletWebServerApplicationContext is a special type of WebApplicationContext that bootstraps itself by searching for a single ServletWebServerFactory bean. Usually a TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory has been auto-configured.

Web 应用会创建一个 web 版的 IOC 容器 —— ServletWebServerApplicationContext,该容器启动时会去找 ServletWebServerFactory 来创建启动 WebServer。

ServletWebServerFactory 自动配置相关(ServletWebServerFactoryAutoConfiguration 导入 ServletWebServerFactoryConfiguration):

web-starter 依赖了 tomcat-starter,所以上上图获取 WebServerFactory 拿到的是汤姆猫工厂。如果不想用 tomcat,可以在引入 web-starter 的时候,把 tomcat-starter 排除掉,然后添加你所需要的 webServer-starter。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

3.2 定制内置 Servlet 容器

3.2.1 ServerProperties

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {...}

3.2.2 ConfigurableServletWebServerFactory

public interface ConfigurableServletWebServerFactory
    extends ConfigurableWebServerFactory, ServletWebServerFactory, WebListenerRegistry {...}

TomcatServletWebServerFactory, JettyServletWebServerFactory and UndertowServletWebServerFactory are dedicated variants of ConfigurableServletWebServerFactory that have additional customization setter methods for Tomcat, Jetty and Undertow respectively. The following example shows how to customize TomcatServletWebServerFactory that provides access to Tomcat-specific configuration options:

@Component
public class TomcatServerCustomizerExample
                implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

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

3.2.3 ServletWebServerFactoryCustomizer

将配置文件中的配置后置修改到工厂中。

If you need to programmatically configure your embedded servlet container, you can register a Spring bean that implements the WebServerFactoryCustomizer interface. WebServerFactoryCustomizer provides access to the ConfigurableServletWebServerFactory, which includes numerous customization setter methods. The following example shows programmatically setting the port:

@Component
public class CustomizationBean
               implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

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

4. 定制化小结

  • 修改配置文件;
  • XxxCustomizer;
  • 编写自定义的配置类 XxxConfiguration + @Bean 替换/增加容器中默认组件;
  • 实现 WebMvcConfigurer 即可定制化 Web 功能,@Bean 给容器中再扩展一些组件;
  • @EnableWebMvc + WebMvcConfigurer 可以全面接管 SpringMVC,但是所有规则就要自己全部重新配置了(非常底层的组件还是会配好的,只保证 SpringMVC 最基本的使用)。
原文地址:https://www.cnblogs.com/liujiaqi1101/p/15269807.html