20) RequestMappingHandlerMapping 与 RequestMapping- HandlerAdapter
RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter 俩是一对,分别用来
-
处理 @RequestMapping 映射
-
调用控制器方法、并处理方法参数与方法返回值
首先创建了 Servlet 容器(TomcatServletWebServer),其次创建了 Servlet 的前端控制器(DispatcherServlet),最后将控制器注册到容器中(Register)。
收获💡
-
DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化 registrationBean.setLoadOnStartup()
-
在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化。
每个具体的init的方法中都会去收集自定义的 Resolver 如果没有则会加载默认的配置
DispatcherServlet.properties
可查看 Tomcat 启动日志查看
-
RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中 key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息 value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象。有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet 主要方法:org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
-
RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:HandlerMethodArgumentResolver 解析控制器方法参数;HandlerMethod-ReturnValueHandler 处理控制器方法返回值 主要方法:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
/** * 为了方便调用 RequestMappingHandlerAdapter 的 invokeHandlerMethod 方法 * 自定义的 HandlerAdapter */ public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter { @Override public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { return super.invokeHandlerMethod(request, response, handlerMethod); } } /** * 自定义参数解析器 */ 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 { return webRequest.getHeader("token"); } } /** * 自定义返回值解析器 */ 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().print(str); // 3. 设置请求已经处理完毕(不让SpringMVC走视图解析的步骤) mavContainer.setRequestHandled(true); } } /** * 为了方便调用 RequestMappingHandlerAdapter 的 invokeHandlerMethod 方法 * 自定义的 HandlerAdapter */ public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter { @Override public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { return super.invokeHandlerMethod(request, response, handlerMethod); } } // config 类省略部分代码 @Bean public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() { TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler(); // RequestMappingHandlerAdapter的 invokeHandlerMethod 范访问范围是私有的 创建子类 重写方法 // 为 HandlerAdapter 添加自定义的 ArgumentResolver(参数解析器)与自定义的 ReturnValueHandler(返回值解析器) MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter(); handlerAdapter.setCustomArgumentResolvers(List.of(tokenArgumentResolver)); handlerAdapter.setCustomReturnValueHandlers(List.of(ymlReturnValueHandler)); return handlerAdapter; } // controller 省略部分代码 @PutMapping("/test3") public ModelAndView test3(@Token String token) { log.debug("test3({})", token); return null; } @RequestMapping("/test4") // @ResponseBody @Yml public User test4() { log.debug("test4"); return new User("张三", 18); } // 测试类 public class A20 { private static final Logger log = LoggerFactory.getLogger(A20.class); public static void main(String[] args) throws Exception { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); // 作用 解析 @RequestMapping 以及派生注解,生成路径与控制器方法的映射关系, 在初始化时就生成 RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); // 请求来了,获取控制器方法 返回处理器执行链对象 MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3"); request.setParameter("name", "张三"); request.addHeader("token", "某个令牌"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain chain = handlerMapping.getHandler(request); System.out.println(chain); System.out.println(">>>>>>>>>>>>>>>>>>>>>"); // HandlerAdapter 作用: 调用控制器方法 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)); /* 学到了什么 a. DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化 b. 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等 本章介绍两个最为重要的组件 a. RequestMappingHandlerAdapter, 以 @RequestMapping 作为映射路径 b. RequestMappingHandlerAdapter, 调用 handler c. 控制器的具体方法会被当作 handler - handler 的参数和返回值多种多样 - 需要解析方法参数, 由 HandlerMethodArgumentResolver 来做 - 需要处理方法返回值, 由 HandlerMethodReturnValueHandler 来做 */ } }
执行效果
>>>>>>>>>>>>>>>>>>>>>
[DEBUG] 23:13:32.542 [main] com.itheima.a20.Controller1 - test3(某个令牌)
Spring MVC学习教程之RequestMappingHandlerAdapter详解_java_脚本之家 (jb51.net)
21) 参数解析器
演示 - 常见参数解析器
代码参考
com.itheima.a21 包
从handlerMapping 中获取到 HandlerMethod。遍历所有参数(准备好参数解析器组合,依次放入参数解析器,暴露出形参的名字-使用的DefaultParameterNameDiscoverer 既结合了标准的基于反射的参数又结合了本地变量表的方式。调用HandlerMethodArgumentResolver 的两个方法进行解析,参数解析器使用到了 WebDataBinder-用于对象绑定与数据转换以及ModelAndView容器,对中间存在的model进行存储)。使用到了组合模式组合模式 | 菜鸟教程 (runoob.com)。添加参数解析器的顺序也有讲究。因为@Parameter 与@ModelAttribute 都可以省略。 所以参数解析器分别配置了两个。并将能解析省略注解的参数解析器放置在最后用于兜底处理。
收获💡
-
初步了解 RequestMappingHandlerAdapter 的调用过程
-
控制器方法被封装为 HandlerMethod
-
准备对象绑定与类型转换
-
准备 ModelAndViewContainer 用来存储中间 Model 结果
-
解析每个参数值
-
-
解析参数依赖的就是各种参数解析器,它们都有两个重要方法
-
supportsParameter 判断是否支持方法参数
-
resolveArgument 解析方法参数
-
-
常见参数的解析
-
@RequestParam
-
省略 @RequestParam
-
@RequestParam(defaultValue)
-
MultipartFile
-
@PathVariable
-
@RequestHeader
-
@CookieValue
-
@Value
-
HttpServletRequest 等
-
@ModelAttribute
-
省略 @ModelAttribute
-
@RequestBody
-
-
组合模式在 Spring 中的体现
-
@RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取
参数名解析
演示 - 两种方法获取参数名
// 防止IDE自动编译,该包不能放在src 下,而是作为修改项目结构->依赖->添加 JAR或目录
package com.itheima.a22;
public class Bean2 {
public void foo(String name, int age) {
}
}
public class A22 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
// 1. 反射获取参数名(编译时使用 javac -parameters)
Method foo = Bean2.class.getMethod("foo", String.class, int.class);
/*for (Parameter parameter : foo.getParameters()) {
System.out.println(parameter.getName());
}*/
// 2. 基于 LocalVariableTable 本地变量表(编译时使用 -g 参数名会维护到本地变量表中,可通过反编译class查看 javap -c -v)
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(foo);
System.out.println(Arrays.toString(parameterNames));
/*
学到了什么
a. 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
b. 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
1. 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
2. 接口, 不会包含局部变量表, 无法获得参数名 (这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名)
*/
}
}
代码参考
com.itheima.a22.A22
收获💡
-
如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
-
如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
-
普通类, 会包含局部变量表, 用 asm 可以拿到参数名
-
接口, 不会包含局部变量表, 无法获得参数名
-
这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名
-
-
23) 对象绑定与类型转换
底层第一套转换接口与实现
Printer 把其它类型转为 String;Parser 把 String 转为其它类型;Formatter 综合 Printer 与 Parser 功能;Converter 把类型 S 转为类型 T;Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合;FormattingConversionService 利用其它们实现转换
底层第二套转换接口
PropertyEditor 把 String 与其它类型相互转换;PropertyEditorRegistry 可以注册多个 PropertyEditor 对象;与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配
高层接口与实现
它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
-
首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
-
再看有没有 ConversionService 转换
-
再利用默认的 PropertyEditor 转换
-
最后有一些特殊处理
SimpleTypeConverter 仅做类型转换;BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property;DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field;ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能.
DataBinder
public class TestDataBinder {
public static void main(String[] args) {
// 执行数据绑定
MyBean target = new MyBean();
DataBinder dataBinder = new DataBinder(target);
dataBinder.initDirectFieldAccess();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("a", "10");
pvs.add("b", "hello");
pvs.add("c", "1999/03/04");
dataBinder.bind(pvs);
System.out.println(target);
}
static class MyBean {
private int a;
private String b;
private Date c;
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
ServletRequestDataBinder
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import java.util.Date;
public class TestServletDataBinder {
public static void main(String[] args) {
// web 环境下数据绑定
MyBean target = new MyBean();
ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("a", "10");
request.setParameter("b", "hello");
request.setParameter("c", "1999/03/04");
// dataBinder.bind(new ServletRequestParameterPropertyValues(request));
dataBinder.bind(request);
System.out.println(target);
}
static class MyBean {
private int a;
private String b;
private Date c;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public Date getC() {
return c;
}
public void setC(Date c) {
this.c = c;
}
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
获取泛型参数
class Teacher {
}
class BaseDao<T> {
T findOne() {
return null;
}
}
class TeacherDao extends BaseDao<Teacher> {
}
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.ResolvableType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class TestGenericType {
public static void main(String[] args) {
// 小技巧
// 1. java api
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
Type type = TeacherDao.class.getGenericSuperclass();
System.out.println(type);
if (type instanceof ParameterizedType ) {
System.out.println(((ParameterizedType) type).getActualTypeArguments()[0]);
}
// 2. spring api 1
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
Class<?> t = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);
System.out.println(t);
// 3. spring api 2
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(ResolvableType.forClass(TeacherDao.class).getSuperType().getGeneric().resolve());
}
}
24) @ControllerAdvice 之 @InitBinder
准备 @InitBinder 在整个 HandlerAdapter 调用过程中所处的位置
@ControllerAdvice
static class MyControllerAdvice {
@InitBinder
public void binder3(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器"));
}
}
@Controller
static class Controller1 {
@InitBinder
public void binder1(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder1 转换器"));
}
@RequestMapping("xxxxx")
public void foo() {
}
}
@ControllerAdvice 类下配置的是全局的类型转换器的 @InitBinder 方法一般设置无返回值,形参可为WebRequest、WebDataBinder。@Controller 及其衍生子类 仅能在局域生效
收获💡
-
RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法
-
RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法
-
以上两种 @InitBinder 的解析结果都会缓存来避免重复解析
-
控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂
25) 控制器方法执行流程
26) @ControllerAdvice 之 @ModelAttribute
标注在控制器方法参数上的 @ModelAttribute 使用的是控制器参数解析器Servlet -ModelAttribute -MethodProcessor;用于解析url请求头中 `?arreibute0=abc&arreibute1=123`;
标注在控制器方法上的@ModelAttribute,使用的是HandlerAdpater。
27) 返回值处理器
常见的返回值处理器与返回值
常见的返回值处理器
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@4c9e38
org.springframework.web.method.annotation.ModelMethodProcessor@5d1e09bc
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@4bdc8b5d
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@3bcd426c
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@5f14a673
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@726a17c4
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@5dc3fcb7
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@c4c0b41
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@76911385
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@5467eea4
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@160396db
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@7a799159
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@40ab8a8
org.springframework.web.method.annotation.MapMethodProcessor@6ff37443
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@65cc8228
static class Controller {
private static final Logger log = LoggerFactory.getLogger(Controller.class);
// 1. 测试返回值类型为 ModelAndView
public ModelAndView test1() {
log.debug("test1()");
ModelAndView mav = new ModelAndView("view1");
mav.addObject("name", "张三");
return mav;
}
// 2. 测试返回值类型为 String 时, 把它当做视图名
public String test2() {
log.debug("test 2()");
return "view2";
}
// 3. 测试返回值添加了 @ModelAttribute 注解时, 此时需找到默认视图名
// 如果找不到则会使用路径的名字 “test3”
@ModelAttribute
// @RequestMapping("/test3")
public User test3() {
log.debug("test3()");
return new User("李四", 20);
}
// 4. 测试返回值不加 @ModelAttribute 注解且返回非简单类型时, 此时需找到默认视图名
public User test4() {
log.debug("test4()");
return new User("王五", 30);
}
// 5. 测试返回值类型为 ResponseEntity 时, 此时不走视图流程
public HttpEntity<User> test5() {
log.debug("test5()");
return new HttpEntity<>(new User("赵六", 40));
}
// 6. 测试返回值类型为 HttpHeaders 时, 此时不走视图流程
public HttpHeaders test6() {
log.debug("test6()");
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/html");
return headers;
}
// 7. 测试返回值添加了 @ResponseBody 注解时, 此时不走视图流程
@ResponseBody
public User test7() {
log.debug("test7()");
return new User("钱七", 50);
}
}
后三种返回值类型,不走视图解析流程是因为 `mavContainer.setRequestHandled(true);`
28) MessageConverter
@Controller 标注的类上 @RequestBody 与 @ResponseBody 都使用了 MessageConverter;
getSupportedMediaTypes 可通过 检查响应头(优先级高,统一设置 或者 @RequestMapping (produces = "application/json") ) 或者请求头确定使用哪种类型转换
private static void test4() throws IOException, HttpMediaTypeNotAcceptableException, NoSuchMethodException {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest webRequest = new ServletWebRequest(request, response);
request.addHeader("Accept", "application/xml");
response.setContentType("application/json");
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
List.of(
new MappingJackson2HttpMessageConverter(), new MappingJackson2XmlHttpMessageConverter()
));
processor.handleReturnValue(
new User("张三", 18),
new MethodParameter(A28.class.getMethod("user"), -1),
new ModelAndViewContainer(),
webRequest
);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
@ResponseBody
@RequestMapping(produces = "application/json")
public User user() {
return null;
}
29) @ControllerAdvice 之 ResponseBodyAdvice 返回响应体前包装
ResponseBodyAdvice 返回响应体前包装; ResponseBodyAdvice#supports用来检查Mapping上是否标注了 指定的注解(@ResponseBody),ResponseBodyAdvice#beforeBodyWrite 是具体的逻辑。我们所有的程序返回值大部分是Object类型的。
@ControllerAdvice
static class MyControllerAdvice implements ResponseBodyAdvice<Object> {
// 满足条件才转换
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (returnType.getMethodAnnotation(ResponseBody.class) != null ||
AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) {
// returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)) {
return true;
}
return false;
}
// 将 User 或其它类型统一为 Result 类型
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果已经是Result 类型的数据 则不用再转换了
if (body instanceof Result) {
return body;
}
return Result.ok(body);
}
@RestController
public static class MyController {
public User user() {
return new User("王五", 18);
}
}
ResponseBodyAdvice 失效的场景
// 参数 HttpServletResponse 使得mapping 无法被增强
@ResponseBody
@RequestMapping("test")
public void test(HttpServletRequest request, HttpServletResponse response){
// 业务逻辑处理
}
跟踪断点排查到了 mavContainer.isRequestHandled(); 那么何时设置的
mavContainer.setRequestHandled(true);
我们搜索源码可得
满足了 mavContainer != null;所以没有走到 `this.returnValueHandlers.handleReturnValue`。
SpringMVC系列第18篇:强大的RequestBodyAdvice解密_路人甲Java的博客-CSDN博客
30) 异常解析器
org.springframework.web.servlet.DispatcherServlet#doDispatch 首先获取处理器,然后执行handle 方法。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
返回 mv是null 的情况 返回值为 HttpEntity、HttpHeader、或者标注了@ResponseBody;
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
是对正常返回视图 或者是执行期间遇到的异常对异常的处理
package com.itheima.a30;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
public class A30 {
public static void main(String[] args) throws NoSuchMethodException {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
resolver.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
// 1.测试 json
// HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));
// Exception e = new ArithmeticException("被零除");
// resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
// 2.测试 mav
// HandlerMethod handlerMethod = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo"));
// Exception e = new ArithmeticException("被零除");
// ModelAndView mav = resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(mav.getModel());
// System.out.println(mav.getViewName());
// 3.测试嵌套异常
// HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));
// Exception e = new Exception("e1", new RuntimeException("e2", new IOException("e3")));
// resolver.resolveException(request, response, handlerMethod, e);
// System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
// 4.测试异常处理方法参数解析
HandlerMethod handlerMethod = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));
Exception e = new Exception("e1");
resolver.resolveException(request, response, handlerMethod, e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
/*
学到了什么
a. ExceptionHandlerExceptionResolver 能够重用参数解析器、返回值处理器,实现组件重用
b. 能够支持嵌套异常
*/
}
static class Controller1 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(ArithmeticException e) {
return Map.of("error", e.getMessage());
}
}
static class Controller2 {
public void foo() {
}
@ExceptionHandler
public ModelAndView handle(ArithmeticException e) {
return new ModelAndView("test2", Map.of("error", e.getMessage()));
}
}
static class Controller3 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(IOException e3) {
return Map.of("error", e3.getMessage());
}
}
static class Controller4 {
public void foo() {}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handler(Exception e, HttpServletRequest request) {
System.out.println(request);
return Map.of("error", e.getMessage());
}
}
}
ExceptionHandlerExceptionResolver 能够解析 标注了@RequestMapping 的返回值 与响应值 是因为 在该Bean初始化的时候添加了一批参数解析器与返回值处理器。能够处理嵌套异常并返回最原始的异常是应为递归遍历了异常。
Spring Boot 自定义ErrorPage
方式一
@Configuration
public class ErrorPageConfig {
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
ErrorPage errorPage400 = new ErrorPage(HttpStatus.BAD_REQUEST,"/error-400.html");
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND,"/error-404.html");
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"/error-500.html");
configurableEmbeddedServletContainer.addErrorPages(errorPage400,errorPage404,errorPage500);
}
};
}
}
方式二
@Bean
public BasicErrorController basicErrorController() {
ErrorProperties errorProperties = new ErrorProperties();
errorProperties.setIncludeException(true);
return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}
/**
* 添加视图名为error 的视图
* @return
*/
@Bean
public View error() {
return new View() {
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println(model);
response.setContentType("text/html;charset=utf-8");
response.getWriter().print(/*"""
<h3>服务器内部错误</h3>
"*/"");
}
};
}
/**
* 供 DisPatcherService 使用
* @return
*/
@Bean
public ViewResolver viewResolver() {
return new BeanNameViewResolver();
}
BasicErrorController 提供两种实现 一种是ModelAndView 另外一种是Json 格式的数据。BasicErrorController 可以传递配置属性 ErrorProperties。 通过读取配置文件动态加载。
server.error.include-exception=true
server.error.include-message=always
server.error.exception=false
server.error.whitelabel.enabled=true
server.error.whitelabel.message=always
server.error.whitelabel.exception=false
33) BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter
添加HandlerMethod
// TomcatServletWebServerFactory、DispatcherServlet Tomcat DispatcherServletRegistrationBean 省略
// 添加三个Bean 都实现了Controller 接口 并且Bean的名字以'/'开头
Component("/c1")
public static class Controller1 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().print("this is c1");
return null;
}
}
@Component("/c2")
public static class Controller2 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().print("this is c2");
return null;
}
}
@Bean("/c3")
public Controller controller3() {
return (request, response) -> {
response.getWriter().print("this is c3");
return null;
};
}
添加 HandlerMapping 与 HandlerAdapter
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
return new BeanNameUrlHandlerMapping();
}
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
// 模拟实现 HandlerMapping HandlerAdapter
@Component
static class MyHandlerMapping implements HandlerMapping {
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
String key = request.getRequestURI();
Controller controller = collect.get(key);
if (controller == null) {
return null;
}
return new HandlerExecutionChain(controller);
}
// 依赖注入 ApplicationContext
@Autowired
private ApplicationContext context;
// 存储 url-HandlerMethod 的 Map 信息
private Map<String, Controller> collect;
// 初始化的时候需要url 与 HandlerMethod
@PostConstruct
public void init() {
// 获取 ApplicationContext 类型是 Controller的map信息,将名字以 '/' 开头的维护在一个Map 中
collect = context.getBeansOfType(Controller.class).entrySet()
.stream().filter(e -> e.getKey().startsWith("/"))
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
System.out.println(collect);
}
}
@Component
static class MyHandlerAdapter implements HandlerAdapter {
// HandlerAdapter是否要处理此 handlerMethod,如果是 Controller 类型的则处理
@Override
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof Controller controller) {
// 处理请求并返回
controller.handleRequest(request, response);
}
// 此处省略返回 ModelAndView 的细节
return null;
}
// 过时的方法无需关注
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
mvc 处理流程
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
-
服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
-
路径:默认映射路径为
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器-
jsp 不会匹配到 DispatcherServlet
-
其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
-
-
创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
-
初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
-
HandlerMapping,初始化时记录映射关系
-
HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
-
HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
-
ViewResolver
-
-
-
DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
-
例如根据 /hello 路径找到 @RequestMapping("/hello") 对应的控制器方法
-
控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
-
HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
-
-
DispatcherServlet 接下来会:
-
调用拦截器的 preHandle 方法
-
RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle org.springframework.web.servlet.DispatcherServlet#doDispatch // 应用注册拦截器的preHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //使用给定的处理程序来处理此请求 主要是HandlerMethod mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ServletInvocableHandlerMethod org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#createInvocableHandlerMethod org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle org.springframework.web.servlet.DispatcherServlet#doDispatch
RequestMappingHandlerAdapter
-
@ControllerAdvice 全局增强点1️⃣:补充模型数据
-
@ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器
-
使用 HandlerMethodArgumentResolver 准备参数
-
@ControllerAdvice 全局增强点3️⃣:RequestBody 增强
-
-
调用 ServletInvocableHandlerMethod
-
使用 HandlerMethodReturnValueHandler 处理返回值
-
@ControllerAdvice 全局增强点4️⃣:ResponseBody 增强
-
-
根据 ModelAndViewContainer 获取 ModelAndView
-
如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
-
例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null
-
-
如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程
-
-
-
调用拦截器的 postHandle 方法
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle org.springframework.web.servlet.DispatcherServlet#doDispatch // 应用注册拦截器的postHandle方法 mappedHandler.applyPostHandle(processedRequest, response, mv);
-
处理异常或视图渲染
-
如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
-
@ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
-
-
正常,走视图解析及渲染流程
-
-
调用拦截器的 afterCompletion 方法
-
org.springframework.web.servlet.DispatcherServlet#doDispatch
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 此处执行了 HandlerMethodInterceptor 的afterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
// 所有的catch 语句块捕获异常使用
ExceptionHandlerMethodResolver
1:首先调用的就是处理业务逻辑方法 service
org.springframework.web.servlet.FrameworkServlet#service,发现走的是super.service(request, response);
2:调用父类的service方法,里面根据不同的请求类型调用不同的方法,下面以get为例
javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse),
3:调用处理请求的方法
org.springframework.web.servlet.FrameworkServlet#doGet
4:调用处理请求接口
org.springframework.web.servlet.FrameworkServlet#processRequest
5:调用doService处理方法,来真正处理请求,service->doService
org.springframework.web.servlet.FrameworkServlet#doService
!!!发现doService是个抽象方法,并没有具体的实现
6:这时候才真正的进入到doService,在子类中实现,校验一系列的参数
org.springframework.web.servlet.DispatcherServlet#doService
7:放入分发器中去处理
org.springframework.web.servlet.DispatcherServlet#doDispatch
7.1:是否是文件类型
org.springframework.web.servlet.DispatcherServlet#checkMultipart
7.2:查找处理器映射器
org.springframework.web.servlet.DispatcherServlet#getHandler
7.3:查找处理器适配器
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
7.4:查找视图
org.springframework.web.servlet.HandlerAdapter#handle
7.5:填充视图 -- 后置处理器
org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle
7.6:处理分发结果
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
总结
其实springmvc的执行流程相对来说还是比较简单的, 大致的流程是
service->doGet(doPost)->doService->doDispatch->校验一些参数->寻找对应的处理器
————————————————
版权声明:本文为CSDN博主「奔跑的小猪zc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45191798/article/details/101085042