# 04.SpringBoot-Web开发3

# 1.拦截器

# 1.实现

1.编写一个拦截器实现HandlerInterceptor接口

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    //目标方法执行之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);
        HttpSession session = request.getSession();
        Object user = session.getAttribute("loginUser");
        if(user==null){
            session.setAttribute("msg", "请先登录");
            response.sendRedirect("/");
            return false;
        }
        return true;
    }
    //目标方法执行之后
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }

    //页面渲染之后
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

2.拦截器注册到容器中(实现WebMvcConfigureraddInterceptors()),指定拦截规则(注意,如果是拦截所有,静态资源也会被拦截】

@Configuration
public class registerInterceptor implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**","/error");
    }
}
1
2
3
4
5
6
7
8
9

# 2.原理

1.根据当前请求,找到 HandlerExecutionChain(可以处理请求的handler以及handler的所有拦截器)

2.先顺序执行所有拦截器的prehandle()方法。

  • 如果当前拦截器 prehandle()返回true,继续执行下一个拦截器的此方法

  • 如果返回false,直接倒序执行所有已经执行了的afterCompletion()方法

3.如果任何一个拦截器返回false,直接跳出不执行目标方法

4.所有拦截器返回true,执行目标方法

5.执行完目标方法开始倒序执行所有拦截器的postHandle方法

6.前面步骤有任何异常,都会直接倒叙触发已经执行了的拦截器的afterCompletion()

7.页面渲染成功也会倒叙触发afterCompletion

DispatcherServlet中涉及到HandlerInterceptor的地方:

public class DispatcherServlet extends FrameworkServlet {

    ...

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

                ...

                //该方法内调用HandlerInterceptor的preHandle()
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                ...
                //该方法内调用HandlerInterceptor的postHandle()
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }            
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            //该方法内调用HandlerInterceptor接口的afterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            //该方法内调用HandlerInterceptor接口的afterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            ...
        }
    }

    private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {

        if (mappedHandler != null) {
            //该方法内调用HandlerInterceptor接口的afterCompletion方法
            mappedHandler.triggerAfterCompletion(request, response, ex);
        }
        throw ex;
    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {

        ...

        if (mappedHandler != null) {
            //该方法内调用HandlerInterceptor接口的afterCompletion方法
            // Exception (if any) is already handled..
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
public class HandlerExecutionChain {

    ...

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for (int i = 0; i < this.interceptorList.size(); i++) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            //HandlerInterceptor的preHandle方法
            if (!interceptor.preHandle(request, response, this.handler)) {

                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
        return true;
    }

       void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
            throws Exception {

        for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);

            //HandlerInterceptor接口的postHandle方法
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }

    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            try {
                //HandlerInterceptor接口的afterCompletion方法
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }


} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 2.文件上传

# 1.实现

参考表单

<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
    <div class="form-group">
        <label for="exampleInputEmail1">邮箱</label>
        <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
    </div>

    <div class="form-group">
        <label for="exampleInputPassword1">名字</label>
        <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
    </div>

    <div class="form-group">
        <label for="exampleInputFile">头像</label>
        <input type="file" name="headerImg" id="exampleInputFile">
    </div>

    <div class="form-group">
        <label for="exampleInputFile">生活照</label>
        <input type="file" name="photos" multiple>
    </div>

    <div class="checkbox">
        <label>
            <input type="checkbox"> Check me out
        </label>
    </div>
    <button type="submit" class="btn btn-primary">提交</button>
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

controller

    @GetMapping("/form_layouts")
    public String form() {
        return "form/form_layouts";
    }

    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos) throws IOException {
        log.info("上传的信息:email={},username={},headerImg={},photos={}",
                email,username,headerImg.getSize(),photos.length);
        if (!headerImg.isEmpty()){
            String headOldName = headerImg.getOriginalFilename();
            //获取前缀
            String suffix=headOldName.substring(headOldName.lastIndexOf("."));
            //拼接新文件名
            String newHeadName= UUID.randomUUID().toString()+suffix;
            //保存文件
            headerImg.transferTo(new File("D:\\images"+File.separator  + newHeadName));
        }
        for (MultipartFile photo : photos) {
            if (!photo.isEmpty()){
                String photoOldName = photo.getOriginalFilename();
                //获取前缀
                assert photoOldName != null;
                String photoSuffix=photoOldName.substring(photoOldName.lastIndexOf("."));
                //拼接新文件名
                String newPhotoName= UUID.randomUUID().toString()+photoSuffix;
                //保存文件
                photo.transferTo(new File("D:\\images" +File.separator + newPhotoName));
            }
        }
        return "main";
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

文件上传相关的配置类:

org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.MultipartProperties

文件大小相关配置项:

server:
  port: 8888
spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
1
2
3
4
5
6
7

# 2.原理

文件的自动配置类MultipartAutoConfiguration配置了文件上传解析器StandardServletMultipartResolver

public class MultipartAutoConfiguration {
    private final MultipartProperties multipartProperties;

    public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
        this.multipartProperties = multipartProperties;
    }

    @Bean
    @ConditionalOnMissingBean({MultipartConfigElement.class, CommonsMultipartResolver.class})
    public MultipartConfigElement multipartConfigElement() {
        return this.multipartProperties.createMultipartConfig();
    }

    @Bean(
        name = {"multipartResolver"}
    )
    @ConditionalOnMissingBean({MultipartResolver.class})
    public StandardServletMultipartResolver multipartResolver() {
        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
        return multipartResolver;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class StandardServletMultipartResolver implements MultipartResolver {
    private boolean resolveLazily = false;
    private boolean strictServletCompliance = false;

    public StandardServletMultipartResolver() {
    }

    public void setResolveLazily(boolean resolveLazily) {
        this.resolveLazily = resolveLazily;
    }

    public void setStrictServletCompliance(boolean strictServletCompliance) {
        this.strictServletCompliance = strictServletCompliance;
    }

    public boolean isMultipart(HttpServletRequest request) {
        return StringUtils.startsWithIgnoreCase(request.getContentType(), this.strictServletCompliance ? "multipart/form-data" : "multipart/");
    }

    public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
        return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
    }

    public void cleanupMultipart(MultipartHttpServletRequest request) {
        if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest)request).isResolved()) {
            try {
                Iterator var5 = request.getParts().iterator();

                while(var5.hasNext()) {
                    Part part = (Part)var5.next();
                    if (request.getFile(part.getName()) != null) {
                        part.delete();
                    }
                }
            } catch (Throwable var4) {
                Throwable ex = var4;
                LogFactory.getLog(this.getClass()).warn("Failed to perform cleanup of multipart items", ex);
            }
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

流程分析

public class DispatcherServlet extends FrameworkServlet {

    @Nullable
    private MultipartResolver multipartResolver;

    private void initMultipartResolver(ApplicationContext context) {
        ...

        //这个就是配置类配置的StandardServletMultipartResolver文件上传解析器
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        ...
    }

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;//最后finally的回收flag
        ...
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                //做预处理,如果有上传文件
                //就将普通的 HttpServletRequest
                // 包装为 MultipartHttpServletRequest(支持文件参数解析)
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);

                ...

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                ...

                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            }
            ....

        finally {

            ...

            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }

    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
            ...
            return this.multipartResolver.resolveMultipart(request);
            ...
        }
    }

    protected void cleanupMultipart(HttpServletRequest request) {
        if (this.multipartResolver != null) {
            MultipartHttpServletRequest multipartRequest =
                    WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
            if (multipartRequest != null) {
                this.multipartResolver.cleanupMultipart(multipartRequest);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());跳到以下的类

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ModelAndView mav;
        ...
        mav = invokeHandlerMethod(request, response, handlerMethod);
        ...
        return mav;
    }

    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {//关注点 
                //设置参数解析器
                //用其中的RequestPartMethodArgumentResolver进行后续解析
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            ...
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            ...

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

    ...
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        ...
    }

    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        ...
        return doInvoke(args);//反射调用
    }

    @Nullable
    protected Object doInvoke(Object... args) throws Exception {
        Method method = getBridgedMethod();
        ReflectionUtils.makeAccessible(method);
        return method.invoke(getBean(), args);
        ...
    }

    //处理得出multipart参数,准备稍后的反射调用(@PostMapping标记的上传方法)
    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        ...
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            //关注点1
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
                //关注点2
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                ...
            }
        }
        return args;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {

    //对应上面代码关注点1
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //标注@RequestPart的参数
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return true;
        }
        else {
            if (parameter.hasParameterAnnotation(RequestParam.class)) {
                return false;
            }
            return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
        }
    }

    //对应上面代码关注点2
    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");

        RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
        boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());

        String name = getPartName(parameter, requestPart);
        parameter = parameter.nestedIfOptional();
        Object arg = null;

        //封装成MultipartFile类型的对象作参数
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            arg = mpArg;
        }

        ...

        return adaptArgumentIfNecessary(arg, parameter);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public final class MultipartResolutionDelegate {
    ...

    @Nullable
    public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
            throws Exception {

        MultipartHttpServletRequest multipartRequest =
                WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
        boolean isMultipart = (multipartRequest != null || isMultipartContent(request));

        if (MultipartFile.class == parameter.getNestedParameterType()) {
            if (!isMultipart) {
                return null;
            }
            if (multipartRequest == null) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }
            return multipartRequest.getFile(name);
        }
        else if (isMultipartFileCollection(parameter)) {
            if (!isMultipart) {
                return null;
            }
            if (multipartRequest == null) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }
            List<MultipartFile> files = multipartRequest.getFiles(name);
            return (!files.isEmpty() ? files : null);
        }
        else if (isMultipartFileArray(parameter)) {
            if (!isMultipart) {
                return null;
            }
            if (multipartRequest == null) {
                multipartRequest = new StandardMultipartHttpServletRequest(request);
            }
            List<MultipartFile> files = multipartRequest.getFiles(name);
            return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);
        }
        else if (Part.class == parameter.getNestedParameterType()) {
            if (!isMultipart) {
                return null;
            }
            return request.getPart(name);
        }
        else if (isPartCollection(parameter)) {
            if (!isMultipart) {
                return null;
            }
            List<Part> parts = resolvePartList(request, name);
            return (!parts.isEmpty() ? parts : null);
        }
        else if (isPartArray(parameter)) {
            if (!isMultipart) {
                return null;
            }
            List<Part> parts = resolvePartList(request, name);
            return (!parts.isEmpty() ? parts.toArray(new Part[0]) : null);
        }
        else {
            return UNRESOLVABLE;
        }
    }

    ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

# 3.错误处理

# 1.默认错误处理机制

  • 默认情况下,Spring Boot提供/error处理所有错误的映射

  • 机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

{
  "timestamp": "2020-11-22T05:53:28.416+00:00",
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/asadada"
}
1
2
3
4
5
6
7
  • 要对其进行自定义,添加View解析为error

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

  • /templates/error/下的4xx,5xx页面会被自动解析

# 2.各种组件的功能

  • ErrorMVCAutoConfiguration自动配置异常处理规则

    • 容器中的组件:DefaultErrorAttributes->id:errorAttributes

      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered

      • 其中定义了一些错误页面包含的东西(具体错误细节,状态码,错误路径等)

    • 容器中的组件:BasicErrorController->id:basicErrorController(json+白页 适配响应)

      • 处理默认/error的请求 ,server.error.path不配置默认为空(@RequestMapping({"${server.error.path:${error.path:/error}}"})

      • 页面响应:(public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)

      • 上图响应默认的视图名称为error,正好容器中自动配置了组件View->id是error(响应默认错误页也就是白页),容器中又存放了组件BeanNameViewResolver(视图解析器);可按照返回的视图名作为id查找view
       @Configuration(
              proxyBeanMethods = false
          )
          @ConditionalOnProperty(
              prefix = "server.error.whitelabel",
              name = {"enabled"},
              matchIfMissing = true
          )
          @Conditional({ErrorTemplateMissingCondition.class})
          protected static class WhitelabelErrorViewConfiguration {
              private final StaticView defaultErrorView = new StaticView();
      
              protected WhitelabelErrorViewConfiguration() {
              }
      
              @Bean(
                  name = {"error"}
              )
              @ConditionalOnMissingBean(
                  name = {"error"}
              )
              public View defaultErrorView() {
                  return this.defaultErrorView;
              }
      
              @Bean
              @ConditionalOnMissingBean
              public BeanNameViewResolver beanNameViewResolver() {
                  BeanNameViewResolver resolver = new BeanNameViewResolver();
                  resolver.setOrder(2147483637);
                  return resolver;
              }
          }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
    • 容器中的组件:DefaultErrorViewResolver->id:conventionErrorViewResolver

      • 如果发送错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面比如(error/404,5xx.html)

# 3.异常处理步骤

1.执行目标方法,目标方法期间有任何异常都会被catch,而且标志当前请求结束,并且用dispatchException包装起来

2.进入视图解析流程(页面渲染)

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                     //对方法进行处理
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    Exception ex = var20;
                    //包装异常
                    dispatchException = ex;
                } catch (Throwable var21) {
                    Throwable err = var21;
                    dispatchException = new NestedServletException("Handler dispatch failed", err);
                }
                 //对应2.视图解析流程
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                Exception ex = var22;
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            } catch (Throwable var23) {
                Throwable err = var23;
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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

3.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);方法内处理发送的异常,处理完返回ModelAndView

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
        boolean errorView = false;
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                this.logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else {
                Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                 //处理异常
                 mv = this.processHandlerException(request, response, handler, exception);
                errorView = mv != null;
            }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13

下面是处理异常的具体流程分析

  • 1.遍历所有的handlerExceptionResolvers,看谁能处理当前异常

  •    @Nullable
        protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
            request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
            ModelAndView exMv = null;
            if (this.handlerExceptionResolvers != null) {
                Iterator var6 = this.handlerExceptionResolvers.iterator();
    
                while(var6.hasNext()) {
                    HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
                    exMv = resolver.resolveException(request, response, handler, ex);
                    if (exMv != null) {
                        break;
                    }
                }
            }
    
            if (exMv != null) {
                if (exMv.isEmpty()) { 
                       //将异常信息存入作用域
                    request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
                    return null;
                } else {
                    if (!exMv.hasView()) {
                        String defaultViewName = this.getDefaultViewName(request);
                        if (defaultViewName != null) {
                            exMv.setViewName(defaultViewName);
                        }
                    }
    
                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("Using resolved error view: " + exMv, ex);
                    } else if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Using resolved error view: " + exMv);
                    }
    
                    WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
                    return exMv;
                }
            } else { 
                   //抛出
                throw ex;
            }
        }  }
            }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
  • 2.系统默认的异常解析器

    • 1.DefaultErrorAttributes先来处理异常。把异常信息保存到request域,并且返回null;

    • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
          ...
          public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
              this.storeErrorAttributes(request, ex);
              return null;
          }
      
          private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
              request.setAttribute(ERROR_ATTRIBUTE, ex);//把异常信息保存到request域
          }
          ...
      
      }    
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
    • 2.默认没有人能处理异常,所以异常会被抛出

      • 1.底层最终发送/error请求,被底层的BasicErrorController处理

      • @Controller
        @RequestMapping("${server.error.path:${error.path:/error}}")
        public class BasicErrorController extends AbstractErrorController {
        
            @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
            public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
               HttpStatus status = getStatus(request);
               Map<String, Object> model = Collections
                     .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
               response.setStatus(status.value());
               ModelAndView modelAndView = resolveErrorView(request, response, status, model);
               //如果/template/error内没有4**.html或5**.html,
               //modelAndView为空,最终还是返回viewName为error的modelAndView
               return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
            }
        
            ...
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
      • 2.解析错误视图;遍历所有的ErrorViewResolver(在上述代码的resolveErrorView方法中),看谁能解析

      • 3.默认的DefaultErrorViewResolver,作用是把响应状态码作为错误页的地址,error/5xx.html

      • 4.响应error/5xx.html

# 4.异常处理原理

  • 自定义错误页

    • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的

//处理所有controller发生的异常
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler({ArithmeticException.class, NullPointerException.class}) 
    //异常处理器和处理的异常类型
    public String handleException(Exception e){
        log.error("异常是:{}",e.getMessage());
        return "login";
    }
}
1
2
3
4
5
6
7
8
9
10
11

Dispatcher类中由于之前没有配置异常处理解析器,所以上面提到了默认没有执行所以exMv一直为空,现在由自己配置的异常处理解析器处理执行,返回页面

(后续自己配置的其他异常处理解析器也在这执行)

   @Nullable
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
        request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        ModelAndView exMv = null;
        if (this.handlerExceptionResolvers != null) {
            Iterator var6 = this.handlerExceptionResolvers.iterator();

            while(var6.hasNext()) {
                HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
                exMv = resolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • @ResponseStatus+自定义异常;底层是ResponseStatusExceptionResolver,把responsestatus注解的信息拿过来底层调用response.sendError(statusCode, resolvedReason);告诉tomcat发送/error,后续是/error请求的处理逻辑

实现

@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {

    public  UserTooManyException(){

    }
    public  UserTooManyException(String message){
        super(message);
    }
}
1
2
3
4
5
6
7
8
9
10
@Controller
public class TableController {

    @GetMapping("/dynamic_table")
    public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
        //表格内容的遍历
         List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("lisi", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe ", "aaddd"));
        model.addAttribute("users",users);

        if(users.size()>3){
            throw new UserTooManyException();//抛出自定义异常
        }
        return "table/dynamic_table";
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • Spring自家异常如 org.springframework.web.bind.MissingServletRequestParameterExceptionDefaultHandlerExceptionResolver 处理Spring自家异常。

    • 也会执行response.sendError(400, ex.getMessage());结束当前请求,tomcat发送/error请求
  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则

实现

@Order(value = Ordered.HIGHEST_PRECEDENCE) //优先级,数字越小优先级越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            response.sendError(511,"我喜欢的错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  • ErrorViewResolver 实现自定义处理异常(大多数情况都不会重写,因为未处理过的底层都会被它处理)

    • response.sendError(),error请求就会转给controller。

    • 你的异常没有任何人能处理,tomcat底层调用response.sendError(),error请求就会转给controller。

    • basicErrorController 要去的页面地址是 ErrorViewResolver解析的 。

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    ...

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections
                .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }

    protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
            Map<String, Object> model) {
        //这里用到ErrorViewResolver接口
        for (ErrorViewResolver resolver : this.errorViewResolvers) {
            ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
            if (modelAndView != null) {
                return modelAndView;
            }
        }
        return null;
    }

    ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 5.原生组件注入

# 1.原生注解与Servlet注入

  • 原生注解方式注入
@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello,myServlet");
    }
}
1
2
3
4
5
6
7
@Slf4j
@WebFilter(urlPatterns={"/css/*","/images/*"})
public class MyWebFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("MyWebFilter初始化完成");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("MyWebFilter开始执行");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        log.info("MyWebFilter销毁");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Slf4j
@WebListener
public class MyWebListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MyWebListener监听到项目初始化完成");

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MyWebListener监听到项目销毁");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

加上servlet扫描包注解

@ServletComponentScan("com.demo.boot.servlet")
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.demo.boot") //三个注解相当于上面的SpringBootApplication
public class WebAdminApplication {
    public static void main(String[] args) {
        //返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(WebAdminApplication.class, args);

    }
}
1
2
3
4
5
6
7
8
9
10
11

# 2.Spring方式注入

ServletRegistrationBeanFilterRegistrationBean, and ServletListenerRegistrationBean

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

    @Bean
    public FilterRegistrationBean myFilter(){
        //return new FilterRegistrationBean(myFilter,myServlet());
        MyWebFilter myWebFilter = new MyWebFilter();
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(myWebFilter);
        filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/css/*","/images/*"));
        return filterFilterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MyWebListener myWebListener = new MyWebListener();
        return new ServletListenerRegistrationBean(myWebListener);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.DispatcherServlet注入原理

DispatcherServletAutoConfiguration配置类

@AutoConfigureOrder(Integer.MIN_VALUE)
@AutoConfiguration(
    after = {ServletWebServerFactoryAutoConfiguration.class}
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({DispatcherServlet.class})
public class DispatcherServletAutoConfiguration {
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    public DispatcherServletAutoConfiguration() {
    }


    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({DispatcherServletRegistrationCondition.class})
    @ConditionalOnClass({ServletRegistration.class})
    @EnableConfigurationProperties({WebMvcProperties.class})
    @Import({DispatcherServletConfiguration.class})
    protected static class DispatcherServletRegistrationConfiguration {
        protected DispatcherServletRegistrationConfiguration() {
        }

        @Bean(
            name = {"dispatcherServletRegistration"}
        )
        @ConditionalOnBean(
            value = {DispatcherServlet.class},
            name = {"dispatcherServlet"}
        )
        //注册DispatcherServlet的类
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
            registration.setName("dispatcherServlet");
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({DefaultDispatcherServletCondition.class})
    @ConditionalOnClass({ServletRegistration.class})
    @EnableConfigurationProperties({WebMvcProperties.class})
    protected static class DispatcherServletConfiguration {
        protected DispatcherServletConfiguration() {
        }

        @Bean(
            name = {"dispatcherServlet"}
        ) 
        //创建DispatcherServlet的类
        public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
            dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
            return dispatcherServlet;
        }

        @Bean
        @ConditionalOnBean({MultipartResolver.class})
        @ConditionalOnMissingBean(
            name = {"multipartResolver"}
        )
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            return resolver;
        }

        ...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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

所以当没有注册MyServlet,自动注册的DispatcherServlet会拦截/my请求,当注册MyServlet时,根据精准匹配原则由我们自己注册的Servlet会拦截处理请求。

:当使用注解注册时,必须要加Servlet包扫描,否则不会扫描到注解,也就不会注册


# 6.嵌入式Servlet容器

# 1.原理

默认支持的WebServer

  • TomcatJetty, or Undertow
  • ServletWebServerApplicationContext容器启动寻找ServletWebServerFactory 并引导创建服务器。

原理:

  • SpringBoot应用启动发现当前是Web应用,web场景包-导入tomcat。

  • web应用会创建一个web版的IOC容器 ServletWebServerApplicationContext 。

  • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory

  • SpringBoot底层默认有很多的WebServer工厂(ServletWebServerFactoryAutoConfiguration``ServletWebServerFactoryConfiguration内创建的Bean),如:

    • TomcatServletWebServerFactory

    • JettyServletWebServerFactory

    • UndertowServletWebServerFactory

  • 底层会有一个自动配置类ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration

  • ServletWebServerFactoryConfiguration 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory

  • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize——this.tomcat.start();

  • 内嵌服务器,与以前手动把启动服务器相比,改成现在使用代码启动(tomcat核心jar包存在)。

Spring Boot默认使用Tomcat服务器,若需更改其他服务器,则修改工程pom.xml:

<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-jetty</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

总结:SpringBoot 启动 Web 应用时,会先识别到 Web 场景依赖并导入 Tomcat 相关包,随后创建 Web 专属的 IOC 容器 ServletWebServerApplicationContext;该容器启动时会寻找 ServletWebServerFactory 类型的 Bean,而底层的自动配置类 ServletWebServerFactoryAutoConfiguration 会导入 ServletWebServerFactoryConfiguration,后者会根据项目中实际引入的 Web 服务器依赖(默认是 Tomcat),动态创建对应的服务器工厂 Bean(如 TomcatServletWebServerFactory);最终由 TomcatServletWebServerFactory 创建并启动 Tomcat 服务器(核心是通过 TomcatWebServer 的初始化方法调用 tomcat.start()),实现了将 Web 服务器内嵌化,通过代码而非手动方式启动服务器的核心逻辑。

# 2.定制化Servlet容器

  • 实现WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>

    • 把配置文件的值和ServletWebServerFactory进行绑定

    • @Component
      public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
          @Override
          public void customize(ConfigurableWebServerFactory factory) {
              factory.setPort(8081);
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
  • 修改配置文件 server.xxx

  • 直接自定义 ConfigurableServletWebServerFactory

    • /**
       * 直接自定义ConfigurableServletWebServerFactory Bean
       * 优先级:自定义Bean > WebServerFactoryCustomizer > 配置文件(server.xxx)
       */
      @Configuration
      public class WebServerConfig {
      
          @Bean
          public ConfigurableServletWebServerFactory webServerFactory() {
              // 1. 创建Tomcat服务器工厂(也可替换为Jetty/Undertow)
              TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
      
              // 2. 基础配置(和定制器模式一致)
              factory.setPort(9999); // 端口
              factory.setContextPath("/app"); // 上下文路径
              factory.setSessionTimeout(60 * 60); // 会话超时1小时
              factory.setUriEncoding(java.nio.charset.StandardCharsets.UTF_8); // 编码
      
              // 3. Tomcat专属配置(连接器属性)
              factory.addConnectorCustomizers(connector -> {
                  // 设置Tomcat连接器的URI编码
                  connector.setURIEncoding("UTF-8");
                  // 设置连接超时时间(毫秒)
                  connector.setConnectionTimeout(30000);
                  // 设置是否开启压缩
                  connector.setCompression("on");
                  // 设置压缩的最小响应大小(字节)
                  connector.setCompressionMinSize(2048);
              });
      
              // 4. 错误页面配置(示例)
              factory.addErrorPages(new org.springframework.boot.web.server.ErrorPage(
                      org.springframework.http.HttpStatus.NOT_FOUND, "/404.html")
              );
      
              return factory;
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
    • ConfigurableServletWebServerFactory的实现类

      • TomcatServletWebServerFactory

      • JettyServletWebServerFactory

      • UndertowServletWebServerFactory


# 7.定制化原理-SpringBoot定制化组件的几种方式

# 1.修改配置文件

# 2.xxxxxCustomizer

比如上面的容器定制化案例

# 3.1xxxConfiguration+@Bean

编写自定义的配置类 xxxConfiguration + @Bean替换/增加容器中默认组件

@Configuration
public class MyConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("_m");
        return hiddenHttpMethodFilter;
    }
}
1
2
3
4
5
6
7
8
9

# 4.WebMvcConfigurer+@Bean

Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能 + @Bean给容器中再扩展一些组件

@Configuration
public class registerInterceptor implements WebMvcConfigurer {
}
1
2
3

# 5.@EnableWebMvc + WebMvcConfigurer — @Bean

上面这三个可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能

/**
 * 全面接管Spring MVC配置
 * @EnableWebMvc:关闭Spring Boot自动配置,完全自定义
 * WebMvcConfigurer:定制MVC核心规则
 */
@Configuration
@EnableWebMvc // 关键:开启全面接管,关闭自动配置
public class CustomWebMvcConfig implements WebMvcConfigurer {

    // ======================== 1. 自定义拦截器(示例:登录校验拦截器) ========================
    // 步骤1:定义拦截器
    public class LoginInterceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 模拟登录校验逻辑:检查请求头中的token
            String token = request.getHeader("token");
            if (token == null || !"admin123".equals(token)) {
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write("{\"code\":401,\"msg\":\"未登录,请先登录\"}");
                return false;
            }
            return true;
        }
    }

    // 步骤2:注册拦截器(通过WebMvcConfigurer重写方法)
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册登录拦截器,指定拦截路径和排除路径
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/api/**") // 拦截所有/api开头的请求
                .excludePathPatterns("/api/login") // 排除登录接口
                .excludePathPatterns("/api/register"); // 排除注册接口
    }

    // ======================== 2. 静态资源配置(替代默认的classpath:/static/) ========================
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 自定义静态资源映射:访问/static/** 映射到classpath:/my-static/
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/my-static/")
                .setCachePeriod(3600); // 静态资源缓存时间(秒)

        // 映射本地磁盘静态资源:访问/images/** 映射到D:/images/
        registry.addResourceHandler("/images/**")
                .addResourceLocations("file:D:/images/");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

原理:

1.WebMvcAutoConfiguration默认的SpringMVC的自动配置功能类,如静态资源、欢迎页等。

2.一旦使用 @EnableWebMvc ,会@Import(DelegatingWebMvcConfiguration.class)。

3.DelegatingWebMvcConfiguration的作用,只保证SpringMVC最基本的使用

  • 把所有系统中的WebMvcConfigurer拿过来,所有功能的定制都是这些WebMvcConfigurer合起来一起生效。

  • 自动配置了一些非常底层的组件,如RequestMappingHandlerMapping,这些组件依赖的组件都是从容器中获取。如:public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport。

4.WebMvcAutoConfiguration里面的配置要能生效必须@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

5.@EnableWebMvc 导致了WebMvcAutoConfiguration 没有生效。

原理分析套路

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties - 绑定配置文件项。

最近更新: 9/19/2026, 1:27:08 PM
编程NOTE   |