Skip to content
On this page

Spring 第八讲 MVC

pom.xml 准备

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/>
    </parent>

    <groupId>org.laoshiren</groupId>
    <artifactId>spring-bean-init</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>

        <aspectj.version>1.9.7</aspectj.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>
</project>
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

DispatcherServlet

什么是DispatcherServlet? DispatcherServlet是前置控制器,配置在web.xml文件中的。拦截匹配的请求,Servlet拦截匹配规则要自己定义,把拦截下来的请求,依据相应的规则分发到目标Controller来处理。

a.png

我们就看看如何配置在spring 中如何配置

java
public class DispatchServletInit {

    public static void main(String[] args) {
        // 支持内嵌web 容器
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

    }

}
1
2
3
4
5
6
7
8
9
10
java
@Configuration
@ComponentScan
class WebConfig {
    // 内嵌web容器的配置类 有3项是必须配置的
    // 1. 内嵌web 容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }
    // 2. DispatchServlet
    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }
    // 3. 注册Bean 将DispatchServlet 注册到web 容器中
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet){
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

可以直接访问localhost:8080

DispatcherServlet 初始化时机

初次访问我们直接报错,毕竟里面什么也没写,但是我们还是看到了 初始化 Initializing Spring DispatcherServlet 'dispatcherServlet'

shell
二月 05, 2023 10:32:57 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring DispatcherServlet 'dispatcherServlet'
22:32:57.857 [http-nio-8080-exec-1] INFO org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
22:32:57.881 [http-nio-8080-exec-1] DEBUG _org.springframework.web.servlet.HandlerMapping.Mappings - 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping' {}
22:32:58.164 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - ControllerAdvice beans: none
22:32:58.196 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - ControllerAdvice beans: none
22:32:58.210 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
22:32:58.210 [http-nio-8080-exec-1] INFO org.springframework.web.servlet.DispatcherServlet - Completed initialization in 353 ms
22:32:58.219 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/", parameters={}
22:32:58.227 [http-nio-8080-exec-1] WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /
22:32:58.228 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
22:32:58.256 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/favicon.ico", parameters={}
22:32:58.256 [http-nio-8080-exec-2] WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /favicon.ico
22:32:58.256 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
1
2
3
4
5
6
7
8
9
10
11
12
13
14

当我们启动项目的时候,发现并不是一开始就初始化的,而是在第一次请求的时候才开始初始化,详情看DispatcherServlet#onRefresh() 的调用链路 ,那如何设置tomcat 一启动就初始化DispatcherServlet 呢?

java
class WebConfig {
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet){
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        // tomcat启动时 就初始化,如果使用多个Servlet 那数字就代表优先级
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }
}
1
2
3
4
5
6
7
8
9
10

默认值就是-1 即不初始化

java
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {

    private int loadOnStartup = -1;
}
1
2
3
4

在springboot 项目中使用spring.mvc.servlet.load-on-startup=1

DispatcherServlet 初始化

我们粗看一下DispatcherServlet#onRefresh()

DispatcherServlet#onRefresh.png

RequestMappingHandlerMapping

什么是RequestMappingHandlerMapping? 他其实是解析@RequestMapping 派生注解建立请求路径和处理器映射关系。根据请求快速找到哪个控制器的哪个方法进行处理。

第一步就是覆盖掉默认配置

java
@Configuration
@ComponentScan
class WebConfig1 {
    // 加入RequestMappingHandlerMapping
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }
}
1
2
3
4
5
6
7
8
9

其次随便定义一个Controller

java
@Slf4j
@Controller
public class TestController {
    @GetMapping("/get")
    public ModelAndView get(){
        log.info("get");
        return null;
    }
    @PostMapping("/post")
    public ModelAndView post(@RequestParam("name")String name){
        log.info("post {}", name);
        return null;
    }
    @PutMapping("/put")
    public ModelAndView put(){
        log.info("put");
        return null;
    }
    @RequestMapping("/request")
    public ModelAndView request(){
        log.info("request");
        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

在容器中查找RequestMappingHandlerMapping

java
public class RequestMappingHandlerMappingMain {

    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig1.class);
        // 用于解析 @Request @GetMapping 派生注解 并存储控制器方法映射关系
        RequestMappingHandlerMapping hm = context.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = hm.getHandlerMethods();
        handlerMethods.forEach((k,v)->{
            System.out.println(k+" = "+v);
        });
        // 根据请求获取控制器方法
        MockHttpServletRequest request = new MockHttpServletRequest("GET","/get");
        // 返回控制器方法 处理器执行链,(控制器方法,拦截器)
        HandlerExecutionChain handlerChain = hm.getHandler(request);
        System.out.println(handlerChain);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

如果没有MockHttpServletRequest类,将 <artifactId>spring-boot-starter-test</artifactId> scope 修改成 compile

RequestMappingHandlerAdapter

作用就是调用控制器方法

一样首先覆盖掉默认配置,因为需要调用 invokeHandlerMethod() 方法,但是默认实现又是protected 修饰的无法调用,所以要重写RequestMappingHandlerAdapter

java
class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
    // 仅仅修改访问修饰符
    @Override
    public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        return super.invokeHandlerMethod(request, response, handlerMethod);
    }
}
@Configuration
@ComponentScan(basePackages = {"com.laoshiren.app.controller"})
class WebConfig2 {
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        return new MyRequestMappingHandlerAdapter();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java
public class RequestMappingHandlerMappingMain {

    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig2.class);

        RequestMappingHandlerMapping hm = context.getBean(RequestMappingHandlerMapping.class);
        // 根据请求获取控制器方法
        MockHttpServletRequest request = new MockHttpServletRequest("GET","/get");
        // 设置参数
        // request.setParameter("name","laoshiren");
        // 返回控制器方法 处理器执行链,(控制器方法,拦截器)
        HandlerExecutionChain handlerChain = hm.getHandler(request);

        MyRequestMappingHandlerAdapter adapter = context.getBean(MyRequestMappingHandlerAdapter.class);

        ModelAndView modelAndView =
                adapter.invokeHandlerMethod(
                        // 请求
                        request,
                        // 响应
                        new MockHttpServletResponse(),
                        // 对应的handlerMethod
                        (HandlerMethod) handlerChain.getHandler());
        System.out.println(modelAndView);
    }
}
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

自定义HandlerMethodArgumentResolver

java
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}
1
2
3
4

自定义解析器

java
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
    // 是否支持某个参数
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 参数上是否存在@Token 注解
        return parameter.hasParameterAnnotation(Token.class);
    }

    // 具体解析参数
    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  // request Response 封装
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        String token = webRequest.getHeader("token");
        return token;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

将自定义解析器注册到adapter上

java
@Configuration
@ComponentScan(basePackages = {"com.laoshiren.app.controller"})
class WebConfig2 {

    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        MyRequestMappingHandlerAdapter myRequestMappingHandlerAdapter = new MyRequestMappingHandlerAdapter();
        // 请求参数
        TokenArgumentResolver argumentResolver = new TokenArgumentResolver();
        List<HandlerMethodArgumentResolver> arrayList = new ArrayList<>();
        arrayList.add(argumentResolver);
        myRequestMappingHandlerAdapter.setCustomArgumentResolvers(arrayList);
        return myRequestMappingHandlerAdapter;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

测试

java
public class RequestMappingHandlerMappingMain {

    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig2.class);

        RequestMappingHandlerMapping hm = context.getBean(RequestMappingHandlerMapping.class);
        // 根据请求获取控制器方法
        MockHttpServletRequest request = new MockHttpServletRequest("GET", "/get");
        // 返回控制器方法 处理器执行链,(控制器方法,拦截器)
        // HandlerExecutionChain handlerChain = hm.getHandler(request);
        MyRequestMappingHandlerAdapter adapter = context.getBean(MyRequestMappingHandlerAdapter.class);
        // 参数解析器 ,RequestBody Cookie 等
        List<HandlerMethodArgumentResolver> argumentResolvers = adapter.getArgumentResolvers();

        MockHttpServletRequest tokenRequest = new MockHttpServletRequest("GET", "/token");
        tokenRequest.addHeader("token","laoshiren");
        HandlerExecutionChain handlerChainPost = hm.getHandler(tokenRequest);

        ModelAndView modelAndViewToken =
                adapter.invokeHandlerMethod(
                        // 请求
                        tokenRequest,
                        // 响应
                        new MockHttpServletResponse(),
                        // 对应的handlerMethod
                        (HandlerMethod) handlerChainPost.getHandler());
    }
}
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

自定义HandlerMethodReturnValueHandler

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}

1
2
3
4
5

同理

java
public class YamlHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
    private static final Yaml YAML = new Yaml();
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return returnType.hasParameterAnnotation(Yml.class);
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        String dump = YAML.dump(returnValue);
        System.out.println(">>>>>>>>>>>>>>>>dump - "+ dump);
        // 获取响应
        HttpServletResponse nativeResponse = webRequest.getNativeResponse(HttpServletResponse.class);
        // 设置编码
        nativeResponse.setContentType("text/plain;charset=utf-8");
        // 输出
        nativeResponse.getWriter().print(dump);
        // 设置请求已经处理完毕
        mavContainer.setRequestHandled(true);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

注册到Adapter

java
@Configuration
@ComponentScan(basePackages = {"com.laoshiren.app.controller"})
class WebConfig2 {

    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        MyRequestMappingHandlerAdapter myRequestMappingHandlerAdapter = new MyRequestMappingHandlerAdapter();
        // 请求参数
        TokenArgumentResolver argumentResolver = new TokenArgumentResolver();
        List<HandlerMethodArgumentResolver> arrayList = new ArrayList<>();
        arrayList.add(argumentResolver);
        myRequestMappingHandlerAdapter.setCustomArgumentResolvers(arrayList);
        // 响应参数
        YamlHandlerMethodReturnValueHandler returnValueHandler = new YamlHandlerMethodReturnValueHandler();
        List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
        returnValueHandlers.add(returnValueHandler);
        myRequestMappingHandlerAdapter.setCustomReturnValueHandlers(returnValueHandlers);
        return myRequestMappingHandlerAdapter;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java
public class RequestMappingHandlerMappingMain {

    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig2.class);

        RequestMappingHandlerMapping hm = context.getBean(RequestMappingHandlerMapping.class);
        // 根据请求获取控制器方法
        MockHttpServletRequest request = new MockHttpServletRequest("GET", "/get");
        // 返回控制器方法 处理器执行链,(控制器方法,拦截器)
//        HandlerExecutionChain handlerChain = hm.getHandler(request);
        MyRequestMappingHandlerAdapter adapter = context.getBean(MyRequestMappingHandlerAdapter.class);
        // 参数解析器 ,RequestBody Cookie 等
        List<HandlerMethodArgumentResolver> argumentResolvers = adapter.getArgumentResolvers();
        System.out.println("-----");
        // 返回值解析器
        List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();

        MockHttpServletRequest yamlRequest = new MockHttpServletRequest("GET", "/yaml");
        HandlerExecutionChain handlerChainYaml = hm.getHandler(yamlRequest);
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        ModelAndView modelAndViewYaml =
                adapter.invokeHandlerMethod(
                        // 请求
                        yamlRequest,
                        // 响应
                        mockHttpServletResponse,
                        // 对应的handlerMethod
                        (HandlerMethod) handlerChainYaml.getHandler());
    }
}
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

MVC 处理流程

到达服务器处理流程

  1. 服务器提供了DispatcherServlet 他使用的是标准的Servlet 技术
    1. 路径:默认映射路径为 / 即会匹配到所有请求URL,可作为请求的统一入口,也被称之为前控制器
      1. JSP 不会匹配到 DispatcherServlet
      2. 其他有路径的Servlet 匹配优先级也会高于DispatcherServlet
    2. 创建:在Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供了 DispatcherServlet 的 Bean
    3. 初始化: DispatcherServlet 初始化会优先找容器里寻找各种组件,作为成员变量
      1. HandlerMapping(RequestMappingHandlerMapping) 初始化记录各个Controller 对应请求路径和对应类方法
      2. HandlerAdapter(RequestMappingHandlerAdapter) 初始化时准备参数解析器、返回值解析器、消息转换器
      3. HandlerExceptionResolver 异常处理器
      4. ViewResolver 视图解析器
  2. DispatcherServlet 会利用 RequestMappingHandlerMapping 查找对应的HandlerMapping
    1. 根据路径找到@ReuqstMapping 对应的控制器方法
    2. 控制器方法被封装为 HandlerMethod 对象,并结合匹配到的拦截器(Interceptor) 一起返回给 DispatcherServlet
    3. HandlerMethod 和拦截器合在一起成为HandlerExecutionChain(调用链)对象
  3. DispatcherServlet 开始处理请求
    1. 调用Interceptor 的 preHandle
    2. RequestMappingHandlerAdapter 调用invokeHandlerMethod 方法
    3. 调用Interceptor 的 postHandle
    4. 处理异常或视图渲染
      • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
        • @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
      • 正常,走视图解析及渲染流程
    5. 调用拦截器的 afterCompletion 方法

Released under the MIT License.