05-Web开发(下) 1. 错误处理 2. 原生组件注入 3. 嵌入式 Servlet 容器 4. 定制化小结
1.1 默认规则
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. 原生组件注入
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 容器
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 最基本的使用)。