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