SpringMVC 底层实现
1 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter
1.1 DispatcherServlet初始化时机
- DispatcherServlet 初始化默认是在第一次调用 DispatcherServlet 时初始化,而不是 tomcat 启动时初始化
- 要想在 tomcat 启动时初始化,可以在 setLoadOnStartup() 传入大于0的整数,值的大小代表如有多个 servlet 初始化的优先级,值越小优先级越高
- 以下使用配置文件方式实现,可以解耦
编写实现:
@Configuration
@ComponentScan
//普通的spring项目,要读取配置文件,需要配置,可以使用 key value 的形式单个读取,也可以批量读取
@PropertySource("classpath:application.properties")
//如果想批量读取 WebMvcProperties 观察源码可知可以读取以 "spring.mvc" 前缀开头的配置
//ServerProperties 可以读取以 "server" 前缀开头的配置
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {
//1.内嵌 web 容器
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(
ServerProperties serverProperties) {
//获取配置文件的容器端口号
Integer port = serverProperties.getPort();
return new TomcatServletWebServerFactory(port);
}
//2.创建DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
//3.注册DispatcherServlet 到 web 容器
//DispatcherServlet 初始化默认是在第一次调用 DispatcherServlet 时初始化,而不是 tomcat 启动时初始化
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
// 传入dispatcherServlet 和 匹配路径
DispatcherServletRegistrationBean registrationBean =
new DispatcherServletRegistrationBean(dispatcherServlet, "/");
//1.1 要想在 tomcat 启动时初始化,可以传入大于0的整数,值的大小代表如有多个 servlet 初始化的优先级,值越小优先级越高
//registrationBean.setLoadOnStartup(1);
//1.2 初始化时机也可以配置在配置文件中
int loadOnStartup = webMvcProperties.getServlet().getLoadOnStartup();
registrationBean.setLoadOnStartup(loadOnStartup);
return registrationBean;
}
}
public class A20 {
private static final Logger log = LoggerFactory.getLogger(A20.class);
public static void main(String[] args) {
//创建Spring容器,并且支持内嵌web容器 如tomcat
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
}
server.port=9090
logging.level.com.wang=debug
# 初始化时机
spring.mvc.servlet.load-on-startup=1
1.2 DispatcherServlet 初始化执行的逻辑
观察源码可知 DispatcherServlet 的初始化主要是其中的 onRefresh() 方法
DispatcherServlet 部分源码如下 :
protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context); //文件上传解析器
this.initLocaleResolver(context); //本地化解析器,会解析不同国家地区的语言
this.initThemeResolver(context);
this.initHandlerMappings(context);//路径映射,映射到不同控制器Controller
this.initHandlerAdapters(context);//适配不同形式的控制器方法
this.initHandlerExceptionResolvers(context);//异常解析
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
1.3 RequestMappingHandlerMapping 基本用途
测试用的Controller:
@Controller
public class Controller1 {
private static final Logger log = LoggerFactory.getLogger(Controller1.class);
@GetMapping("/test1")
public ModelAndView test1() {
log.debug("test1");
return null;
}
@PostMapping("/test2")
public ModelAndView test2(@RequestParam("name") String name) {
log.debug("test2({})",name);
return null;
}
}
加入 RequestMappingHandlerMapping 的Bean:
//如果用 DispatcherServlet 初始化添加的组件,并不会作为Bean,给测试带来困扰
//1.加入RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
public class A20 {
private static final Logger log = LoggerFactory.getLogger(A20.class);
public static void main(String[] args) throws Exception {
//创建Spring容器,并且支持内嵌web容器 如tomcat
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
//作用 :解析 @Request 注解以及派生注解,生成路径与控制器方法的映射关系。在初始化时
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
//获取映射信息,key:路径信息 ,value:映射的方法
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
handlerMethods.forEach((k, v) -> {
System.out.println(k + " = " + v);
});
//请求来了,怎么获取控制器的方法,返回处理器执行链对象
HandlerExecutionChain chain = handlerMapping
.getHandler(new MockHttpServletRequest("GET", "/test1"));
System.out.println(chain);
}
}
打印输出:
{GET [/test1]} = com.wang.a20.Controller1#test1()
{POST [/test2]} = com.wang.a20.Controller1#test2(String)
14:25:41.892 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.wang.a20.Controller1#test1()
HandlerExecutionChain with [com.wang.a20.Controller1#test1()] and 0 interceptors
1.4 RequestMappingHandlerAdapter
RequestMappingHandlerAdapter 源码中有一个重要的方法,部分源码截图:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
Object result;
try {
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
由于该方法是 protected 修饰的,我们可以编写一个 MyRequestMappingHandlerAdapter 类去继承 RequestMappingHandlerAdapter ,并重写 invokeHandlerMethod() 方法
public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
@Override
public ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {
return super.invokeHandlerMethod(request, response, handlerMethod);
}
}
测试用的Controller:
@Controller
public class Controller1 {
private static final Logger log = LoggerFactory.getLogger(Controller1.class);
@GetMapping("/test1")
public ModelAndView test1() {
log.debug("test1");
return null;
}
@PostMapping("/test2")
public ModelAndView test2(@RequestParam("name") String name) {
log.debug("test2({})",name);
return null;
}
}
1 RequestMappingHandlerAdapter 调用并执行对应的Controller
//请求来了,怎么获取控制器的方法,返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println("-----------------------------");
//获取 RequestMappingHandlerAdapter,去执行对应的Controller
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request,response, (HandlerMethod) chain.getHandler());
输出打印:
-----------------------------
14:42:10.952 [main] DEBUG com.wang.a20.Controller1 - test1
2 RequestMappingHandlerAdapter 参数和返回值解析器
- 当遇到如下情况需要传参的情况,如何解析对应的参数注解:
//请求来了,怎么获取控制器的方法,返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
request.setParameter("name","wang");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println("-----------------------------");
//获取 RequestMappingHandlerAdapter,去执行对应的Controller
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request,response, (HandlerMethod) chain.getHandler());
输出打印:
-----------------------------
14:44:44.932 [main] DEBUG com.wang.a20.Controller1 - test2(wang)
RequestMappingHandlerAdapter 的参数解析器:
System.out.println(">>>>>>>>>>>>>>>>>所有的参数解析器:");
List<HandlerMethodArgumentResolver> argumentResolvers = handlerAdapter.getArgumentResolvers();
for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {
System.out.println(argumentResolver);
}
输出部分打印:
>>>>>>>>>>>>>>>>>所有的参数解析器:
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@209775a9
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@18e7143f
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@f9b7332
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@74cec793
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@6fefce9e
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@4f8969b0
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1bdf8190
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@192f2f27
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@8a589a2
..........
如:RequestParamMethodArgumentResolver就是解析 @RequestParam 的
- 返回值解析器:
System.out.println(">>>>>>>>>>>>>>>>>所有的返回值解析器:");
List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
System.out.println(returnValueHandler);
}
输出部分打印:
>>>>>>>>>>>>>>>>>所有的返回值解析器:
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@6bab2585
org.springframework.web.method.annotation.ModelMethodProcessor@74bdc168
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@644c78d4
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@532a02d9
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@611f8234
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@7bb3a9fe
...........
如:ModelAndViewMethodReturnValueHandler 就是返回 ModelAndView 的解析器
1.5 自定义参数解析器
-
自定义注解
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Token { }
-
编写参数解析器
public class TokenArgumentResolver implements HandlerMethodArgumentResolver { //是否支持某个参数 @Override public boolean supportsParameter(MethodParameter parameter) { Token token = parameter.getParameterAnnotation(Token.class); return token != null; } //解析参数 @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String token = webRequest.getHeader("token"); return token; } }
-
将自定义参数解析器设置到 RequestMappingHandlerAdapter 中
@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); List<HandlerMethodArgumentResolver> argumentResolverList = new ArrayList<>(); argumentResolverList.add(tokenArgumentResolver); MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter(); //设置自定义参数解析器 handlerAdapter.setCustomArgumentResolvers(argumentResolverList); return handlerAdapter; }
-
编写测试 Controller
@PutMapping("/test3") public ModelAndView test3(@Token String token) { log.debug("test3({})", token); return null; }
-
测试:
//请求来了,怎么获取控制器的方法,返回处理器执行链对象 MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3"); request.setParameter("name", "wang"); request.addHeader("token", "令牌信息"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = handlerMapping.getHandler(request); System.out.println(chain); System.out.println("-----------------------------"); //获取 RequestMappingHandlerAdapter,去执行对应的Controller MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class); handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
-
输出打印:
----------------------------- 15:21:01.410 [main] DEBUG com.wang.a20.Controller1 - test3(令牌信息)
1.6 自定义返回值解析器
案例:输出 yaml 格式的字符串
-
自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Yml { }
-
自定义返回值解析器
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler { @Override public boolean supportsReturnType(MethodParameter returnType) { Yml yml = returnType.getMethodAnnotation(Yml.class); return yml != null; } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { //1.转化结果为 yaml 样式的字符串 String str = new Yaml().dump(returnValue); //2.将 yaml 字符串写入响应 HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); response.setContentType("text/plain;charset=utf-8"); response.getWriter().println(str); //3.设置请求已经处理完毕,不需要springmvc做视图解析 mavContainer.setRequestHandled(true); } }
-
将自定义返回值解析器设置到 RequestMappingHandlerAdapter 中
@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); List<HandlerMethodArgumentResolver> argumentResolverList = new ArrayList<>(); argumentResolverList.add(tokenArgumentResolver); YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler(); List<HandlerMethodReturnValueHandler> returnValueHandlerList = new ArrayList<>(); returnValueHandlerList.add(ymlReturnValueHandler); MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter(); //设置自定义参数解析器 handlerAdapter.setCustomArgumentResolvers(argumentResolverList); //设置自定义返回值 handlerAdapter.setCustomReturnValueHandlers(returnValueHandlerList); return handlerAdapter; }
-
编写测试 Controller
@RequestMapping("test4") @Yml public User test4() { log.debug("test4"); return new User("wang", 23); }
-
测试自定义返回值解析器
/请求来了,怎么获取控制器的方法,返回处理器执行链对象 MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test4"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = handlerMapping.getHandler(request); System.out.println(chain); System.out.println("-----------------------------"); //获取 RequestMappingHandlerAdapter,去执行对应的Controller MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class); handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler()); //检查响应对象 byte[] content = response.getContentAsByteArray(); System.out.println(new String(content, StandardCharsets.UTF_8));
-
输出打印
----------------------------- 17:27:48.116 [main] DEBUG com.wang.a20.Controller1 - test4 !!com.wang.a20.Controller1$User {age: 23, name: wang}
2 mvc处理流程
当浏览器发送请求后,请求到达服务器:
- 服务器提供了DispatcherServlet,它使用的是标准的 Servlet 技术
- 路径:默认路径为
/
,即会匹配到所有的 URL ,可以作为请求的统一入口,也被称为:前控制器;例外的是:jsp
不会匹配到DispatcherServlet - 创建:在 Boot 中,有DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 Bean
- 初始化:DispatcherServlet 初始化时会优先到容器中去寻找各个组件,作为他的成员变量
- HandlerMapping,初始化时记录映射关系
- HandlerAdaptor,初始化时准备参数解析器、返回值处理器、消息转换器
- HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
- ViewResolver
- 路径:默认路径为
- DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
- 例如根据
/hello
路径找到@RequestMapping(“/hello”) 对应的控制器方法 - 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给DispatcherServlet
- HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
- 例如根据
- DispatcherServlet 接下来会:
- 调用拦截器的 preHandle 方法
- RequestMappingHandlerAdaptor 调用 handler 方法,准备数据绑定工厂、模型工厂、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
- @ControllerAdvice增强1:补充模型数据
- @ControllerAdvice增强2:补充自定义类型转换器
- 使用 HandlerMethodArgumentResolver 准备参数
- @ControllerAdvice增强3:RequestBody 增强
- 调用 ServletInvocableHandlerMethod
- 使用HandlerMethodReturnValueHandler 处理返回值
- 如果返回的 ModelAndView 为null,不走第四步视图解析及渲染流程,例如:有@ResponseBody 的控制器方法,调用HttpMessageConvert 将结果转换为 JSON ,这是返回的 ModelAndView 就为 null
- 如果返回的ModelAndView 不为null,会在第四步走视图解析及渲染流程
- @ControllerAdvice 增强4:ResponseBody 增强
- 调用 PostHandler 方法
- 处理异常或视图渲染
- 如果 1~3 步出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- @ControllerAdvice增强5:@ExceptionHandler 异常处理
- 没有异常,走视图解析及渲染流程
- 如果 1~3 步出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- 调用拦截器的 afterCompletion 方法