目录
1、RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
5、@ControllerAdvice 之 @InitBinder
9、@ControllerAdvice 之 ResponseBodyAdvice
1、ExceptionHandlerExceptionResolver
12、BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter
RouterFunctionMapping 与 HandlerFunctionAdapter
13、SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter
1、RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter 俩是一对,分别用来
-
处理 @RequestMapping 映射
-
调用控制器方法、并处理方法参数与方法返回值
1、DispatcherServlet 初始化
自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}
// 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它
// token=令牌
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}
自定义参数与返回值处理器 :
public class MyArgumentResolver implements HandlerMethodArgumentResolver {
// 是否支持某个参数
@Override
public boolean supportsParameter(MethodParameter parameter) {
Token token = parameter.getParameterAnnotation(Token.class);
return token != null;
}
// 解析参数
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
return nativeWebRequest.getHeader("token");
}
}
public class MyReturn implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter parameter) {
Yml yml = parameter.getMethodAnnotation(Yml.class);
return yml != null;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest) throws Exception {
// 1. 转换返回结果为指定yaml 字符串
String str = new Yaml().dump(returnValue);
// 2. 将 yaml 字符串写入响应
HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);
response.setContentType("text/plain;charset=utf-8");
response.getWriter().print(str);
// 3. 设置请求已经处理完毕
modelAndViewContainer.setRequestHandled(true);
}
}
@Controller
public class Controller1 {
private static final Logger log = LoggerFactory.getLogger(Controller1.class);
@GetMapping("/test1")
public ModelAndView test1() throws Exception {
System.err.println("test1");
return null;
}
@PostMapping("/test2")
public ModelAndView test2(@RequestParam("name") String name) {
System.err.println("test2:"+name);
return null;
}
@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
System.err.println("test3:"+token);
return null;
}
@RequestMapping("/test4")
// @ResponseBody
@Yml
public User test4() {
System.err.println("test4");
return new User("张三", 18);
}
public static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
String str = new Yaml().dump(new User("张三", 18));
System.out.println(str);
}
}
配置类:
@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {
// 内嵌 web 容器工厂
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties){
return new TomcatServletWebServerFactory(serverProperties.getPort());
}
// 创建 DispatcherServlet
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet();
}
//注册 DispatcherServlet, Spring MVC 的入口
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet dispatcherServlet,WebMvcProperties webMvcProperties){
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
//启动时就创建
// registrationBean.setLoadOnStartup(1);
registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registrationBean;
}
// 如果用 DispatcherServlet 初始化时默认添加的组件, 并不会作为 bean, 给测试带来困扰
// 加入RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(){
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
// 自定义参数与返回值处理器
MyArgumentResolver resolver = new MyArgumentResolver();
MyReturn myReturn = new MyReturn();
handlerAdapter.setCustomArgumentResolvers(List.of(resolver));
handlerAdapter.setCustomReturnValueHandlers(List.of(myReturn));
return handlerAdapter;
}
@Bean("/hello")
public Controller controller(){
return (request, response) -> {
response.getWriter().println("hello world");
return null;
};
}
}
public class Day20Application {
public static void main(String[] args) throws Exception {
AnnotationConfigServletWebServerApplicationContext context=
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
// 作用 解析 @RequestMapping 以及派生注解,生成路径与控制器方法的映射关系, 在初始化时就生成
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取映射结果
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
System.err.println("handlerMethods:");
handlerMethods.forEach((k, v) -> {
System.out.println(k+"=="+v);
});
System.err.println("------------\n");
// 请求来了,获取控制器方法 返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test4");
request.setParameter("name","张三");
request.addHeader("token","这是个令牌");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.err.println("执行链:"+chain);
System.err.println(">>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
RequestMappingHandlerAdapter handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class);
Method method = handlerAdapter.getClass().getDeclaredMethod("invokeHandlerMethod",
HttpServletRequest.class, HttpServletResponse.class,HandlerMethod.class);
method.setAccessible(true);
method.invoke(handlerAdapter,request,response,(HandlerMethod)chain.getHandler());
//检测响应
byte[] content = response.getContentAsByteArray();
System.err.println(new String(content, StandardCharsets.UTF_8));
// System.err.println(">>>>>>>>>>>>>>>>>>>>> 所有参数解析器");
// for (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) {
// System.out.println(resolver);
// }
//
// System.err.println(">>>>>>>>>>>>>>>>>>>>> 所有返回值解析器");
// for (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) {
// System.out.println(handler);
// }
}
}
-
DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化
-
在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化
-
RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
-
key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
-
value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
-
有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
-
-
RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:
-
HandlerMethodArgumentResolver 解析控制器方法参数
-
HandlerMethodReturnValueHandler 处理控制器方法返回值
-
2、参数解析器
public class Day21Application {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 准备测试 Request
HttpServletRequest request = mockRequest();
// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class,
String.class, int.class, String.class, MultipartFile.class, int.class, String.class,
String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
// 要点2. 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,null);
// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
//多个解析的组合
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
new RequestParamMethodArgumentResolver(beanFactory,false),//为 false 则必须有@RequestParam
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(beanFactory),
new ServletCookieValueMethodArgumentResolver(beanFactory),
new ExpressionValueMethodArgumentResolver(beanFactory),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false),// 必须有 @ModelAttribute
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),//↓↑位置不能对调
new ServletModelAttributeMethodProcessor(true),// 省略了 @ModelAttribute
new RequestParamMethodArgumentResolver(beanFactory,true)//可省略@RequestParam
);
List<String> list = new ArrayList<>();
//获取参数上的注解
Arrays.stream(parameter.getParameterAnnotations()).forEach(annotation -> {
list.add("【@"+annotation.annotationType().getSimpleName()+"】 ");
});
//解析参数名
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
if(list.size() == 0){
list.add("");
}
if(composite.supportsParameter(parameter)){
Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
System.err.println(v.getClass());
System.err.println("【"+parameter.getParameterIndex()+"】 "+ list.get(0) +parameter.getParameterType().getSimpleName()
+" - "+parameter.getParameterName()+" --> "+v);
if(container.getModel() != null && container.getModel().size() > 0){
System.err.println("模型数据为::"+container.getModel());
}
}else{
System.err.println("【"+parameter.getParameterIndex()+"】 "+ list.get(0) +parameter.getParameterType().getSimpleName()
+" - "+parameter.getParameterName());
}
}
}
private static HttpServletRequest mockRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name1", "zhangsan");
request.setParameter("name2", "lisi");
request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
//建立对应关系,并存储到map当中
Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
System.out.println(">>>"+map);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
request.setContentType("application/json");
request.setCookies(new Cookie("token", "123456"));
request.setParameter("name", "张三");
request.setParameter("age", "18");
request.setContent(("{\"name\":\"李四\",\"age\":20}").getBytes(StandardCharsets.UTF_8));
return new StandardServletMultipartResolver().resolveMultipart(request);
}
static class Controller {
public void test(
@RequestParam("name1") String name1, // name1=张三
String name2, // name2=李四
@RequestParam("age") int age, // age=18
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
@RequestParam("file") MultipartFile file, // 上传文件
@PathVariable("id") int id, // /test/124 /test/{id}
@RequestHeader("Content-Type") String header,
@CookieValue("token") String token,
@Value("${JAVA_HOME}") String home2, // spring 获取数据 ${} #{}
HttpServletRequest request, // request, response, session ...
@ModelAttribute("abc") User user1, // name=zhang&age=18
User user2, // name=zhang&age=18
@RequestBody User user3 // json
) {
}
}
static class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
-
初步了解 RequestMappingHandlerAdapter 的调用过程
-
控制器方法被封装为 HandlerMethod
-
准备对象绑定与类型转换
-
准备 ModelAndViewContainer 用来存储中间 Model 结果
-
解析每个参数值
-
-
解析参数依赖的就是各种参数解析器,它们都有两个重要方法
-
supportsParameter 判断是否支持方法参数
-
resolveArgument 解析方法参数
-
-
常见参数的解析
-
@RequestParam
-
省略 @RequestParam
-
@RequestParam(defaultValue)
-
MultipartFile
-
@PathVariable
-
@RequestHeader
-
@CookieValue
-
@Value
-
HttpServletRequest 等
-
@ModelAttribute
-
省略 @ModelAttribute
-
@RequestBody
-
-
组合模式在 Spring 中的体现
-
@RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 ${ } #{ }中获取
3、参数名解析
/*
目标: 如何获取方法参数名, 注意把 a22 目录添加至模块的类路径
1. a22 不在 src 是避免 idea 自动编译它下面的类
2. spring boot 在编译时会加 -parameters
3. 大部分 IDE 编译时都会加 -g
*/
public class A22 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
// 1. 反射获取参数名
Method foo = Bean2.class.getMethod("foo", String.class, int.class);
/*for (Parameter parameter : foo.getParameters()) {
System.out.println(parameter.getName());
}*/
// 2. 基于 LocalVariableTable 本地变量表
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(foo);
System.out.println(Arrays.toString(parameterNames));
}
}
-
如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
-
如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
-
普通类, 会包含局部变量表, 用 asm 可以拿到参数名
-
接口, 不会包含局部变量表, 无法获得参数名
-
这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名
4、对象绑定与类型转换
1、底层第一套转换接口与实现 (Spring)
-
Printer 把其它类型转为 String
-
Parser 把 String 转为其它类型
-
Formatter 综合 Printer 与 Parser 功能
-
Converter 把类型 S 转为类型 T
-
Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
-
FormattingConversionService 利用其它们实现转换
2、底层第二套转换接口(jdk)
-
PropertyEditor 把 String 与其它类型相互转换
-
PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
-
与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配
3、高层接口与实现
-
它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
-
首先看是否有自定义转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
-
再看有没有 ConversionService 转换
-
再利用默认的 PropertyEditor 转换
-
最后有一些特殊处理
-
-
SimpleTypeConverter 仅做类型转换
-
BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property(get、set)
-
DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field(成员变量)
-
ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property (false)还是 Field(true),具备校验与获取校验结果功能
4、类型转换与数据绑定
public class Test1 {
public static void main(String[] args) {
// 仅有类型转换的功能
SimpleTypeConverter converter = new SimpleTypeConverter();
Integer num = converter.convertIfNecessary("13", int.class);
Date date = converter.convertIfNecessary("2022/01/01", Date.class);
System.err.println(num);
System.err.println(date);
}
}
public class Test2 {
public static void main(String[] args) {
// 利用反射原理, 为 bean 的属性赋值
MyBean myBean = new MyBean();
BeanWrapperImpl wrapper = new BeanWrapperImpl(myBean);
wrapper.setPropertyValue("a","10");
wrapper.setPropertyValue("b","hello");
wrapper.setPropertyValue("c","2022/01/01");
System.err.println(myBean);
}
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 +
'}';
}
}
}
public class Test3 {
public static void main(String[] args) {
// 利用反射原理, 为 bean 的属性赋值
Test2.MyBean myBean = new Test2.MyBean();
DirectFieldAccessor accessor = new DirectFieldAccessor(myBean);
accessor.setPropertyValue("a","10");
accessor.setPropertyValue("b","hello");
accessor.setPropertyValue("c","2022/01/01");
System.err.println(myBean);
}
static class MyBean {
private int a;
private String b;
private Date c;
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
public class Test4 {
public static void main(String[] args) {
// 执行数据绑定
MyBean myBean = new MyBean();
DataBinder binder = new DataBinder(myBean);
binder.initDirectFieldAccess();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("a","10");
pvs.add("b","hello");
pvs.add("c","2022/01/01");
binder.bind(pvs);
System.err.println(myBean);
}
static class MyBean {
private int a;
private String b;
private Date c;
@Override
public String toString() {
return "MyBean{" +
"a=" + a +
", b='" + b + '\'' +
", c=" + c +
'}';
}
}
}
web 环境下
public class Test5 {
public static void main(String[] args) {
// web 环境下的数据绑定
MyBean myBean = new MyBean();
ServletRequestDataBinder binder = new ServletRequestDataBinder(myBean);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("a","10");
request.setParameter("b","hello");
request.setParameter("c","2022/01/01");
binder.bind(new ServletRequestParameterPropertyValues(request));
System.err.println(myBean);
}
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 +
'}';
}
}
}
5、数据绑定工厂
public class Test6 {
public static void main(String[] args) throws Exception {
User user = new User();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("birthday","2022|01|01");
request.setParameter("address.name","北京");
// ServletRequestDataBinder binder = new ServletRequestDataBinder(user);
// 1. 用工厂, 无转换功能
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,null);
//2. 用 @InitBinder 转换 使用的是:PropertyEditorRegistry PropertyEditor
// InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("test", WebDataBinder.class));
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method),null);
//3. 用 ConversionService 转换 使用的是:ConversionService Formatter
// FormattingConversionService service = new FormattingConversionService();
// service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
// ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
// initializer.setConversionService(service);
// ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,initializer);
//4. 同时加了 @InitBinder(优先) 和 ConversionService
//5. 使用默认 ConversionService 转换 需要@DateTimeFormat配合使用
// DefaultFormattingConversionService service = new DefaultFormattingConversionService(); //非spring boot
ApplicationConversionService service = new ApplicationConversionService();//spring boot 使用
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(service);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,initializer);
WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
binder.bind(new ServletRequestParameterPropertyValues(request));
System.err.println(user);
}
static class MyController{
@InitBinder
public void test(WebDataBinder dataBinder){
// 扩展 dataBinder 的转换器
dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));
}
}
public static class User {
@DateTimeFormat(pattern = "yyyy|MM|dd")
private Date birthday;
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"birthday=" + birthday +
", address=" + address +
'}';
}
}
public static class Address {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Address{" +
"name='" + name + '\'' +
'}';
}
}
}
public class MyDateFormatter implements Formatter<Date> {
private final String desc;
public MyDateFormatter(String desc) {
this.desc = desc;
}
@Override
public String print(Date date, Locale locale) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.format(date);
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
System.err.println(desc);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.parse(text);
}
}
ServletRequestDataBinderFactory 的用法和扩展点
-
可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器
-
控制器私有范围
-
-
可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器
-
公共范围
-
-
同时加了 @InitBinder 和 ConversionService 的转换优先级
-
优先采用 @InitBinder 的转换器
-
其次使用 ConversionService 的转换器
-
使用默认转换器
-
特殊处理(例如有参构造)
-
6、获取泛型参数
public class Test7 {
public static void main(String[] args) {
// 1. java api
Type type = StudentDao.class.getGenericSuperclass();
System.err.println(type);
if(type instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) type;
System.err.println(">>>"+parameterizedType.getActualTypeArguments()[0]);
}
System.err.println("===================");
// 2. spring api 1
Class<?> clazz = GenericTypeResolver.resolveTypeArgument(StudentDao.class, BaseDao.class);
System.err.println(clazz);
// 3. spring api 2
Class<?> c = ResolvableType.forClass(StudentDao.class).getSuperType().getGeneric().resolve();
System.err.println(c);
}
static class StudentDao extends BaseDao<Student> {
}
}
5、@ControllerAdvice 之 @InitBinder
@Configuration
public class WebConfig {
@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 转换器"));
}
public void foo() {
}
}
@Controller
static class Controller2 {
@InitBinder
public void binder21(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder21 转换器"));
}
@InitBinder
public void binder22(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder22 转换器"));
}
public void bar() {
}
}
}
public class A24 {
private static final Logger log = LoggerFactory.getLogger(A24.class);
public static void main(String[] args) throws Exception {
/*
@InitBinder 的来源有两个
1. @ControllerAdvice 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 在初始化时解析并记录
2. @Controller 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 会在控制器方法首次执行时解析并记录
*/
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setApplicationContext(context);
handlerAdapter.afterPropertiesSet();
log.debug("1. 刚开始...");
showBindMethods(handlerAdapter);
Method getDataBinderFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class);
getDataBinderFactory.setAccessible(true);
log.debug("2. 模拟调用 Controller1 的 foo 方法时 ...");
getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo")));
showBindMethods(handlerAdapter);
log.debug("3. 模拟调用 Controller2 的 bar 方法时 ...");
getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar")));
showBindMethods(handlerAdapter);
context.close();
}
//(了解)
@SuppressWarnings("all")
private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException {
Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
initBinderAdviceCache.setAccessible(true);
Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCache.get(handlerAdapter);
log.debug("全局的 @InitBinder 方法 {}",
globalMap.values().stream()
.flatMap(ms -> ms.stream().map(m -> m.getName()))
.collect(Collectors.toList())
);
Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
initBinderCache.setAccessible(true);
Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCache.get(handlerAdapter);
log.debug("控制器的 @InitBinder 方法 {}",
controllerMap.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(v -> e.getKey().getSimpleName() + "." + v.getName()))
.collect(Collectors.toList())
);
}
}
-
RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法
-
RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法
-
以上两种 @InitBinder 的解析结果都会缓存来避免重复解析
-
控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂
6、控制器方法执行流程(@ModelAttribute)
HandlerMethod 需要
-
bean 即是哪个 Controller
-
method 即是 Controller 中的哪个方法
ServletInvocableHandlerMethod 需要
-
WebDataBinderFactory 负责对象绑定、类型转换
-
ParameterNameDiscoverer 负责参数名解析
-
HandlerMethodArgumentResolverComposite 负责解析参数
-
HandlerMethodReturnValueHandlerComposite 负责处理返回值
可拆分成:
@Configuration
public class WebConfig {
@ControllerAdvice
static class MyControllerAdvice {
@ModelAttribute("a")
public String aa() {
return "aa";
}
}
@Controller
static class Controller1 {
@ModelAttribute("b")
public String aa() {
return "bb";
}
@ResponseStatus(HttpStatus.OK)
public ModelAndView foo(@ModelAttribute("u") User user) {
System.out.println("foo");
return null;
}
}
static class User {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}
public class Day26Application {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setApplicationContext(context);
adapter.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("name","张三");
/*
现在可以通过 ServletInvocableHandlerMethod 把这些整合在一起, 并完成控制器方法的调用, 如下
*/
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
new WebConfig.Controller1(),WebConfig.Controller1.class.getMethod("foo", WebConfig.User.class)
);
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null,null);
handlerMethod.setDataBinderFactory(factory);
handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
ModelAndViewContainer container = new ModelAndViewContainer();
// 获取模型工厂方法
Method method = RequestMappingHandlerAdapter.class.getDeclaredMethod("getModelFactory",
HandlerMethod.class, WebDataBinderFactory.class);
method.setAccessible(true);
ModelFactory modelFactory = (ModelFactory) method.invoke(adapter, handlerMethod, factory);
// 初始化模型数据
modelFactory.initModel(new ServletWebRequest(request),container,handlerMethod);
handlerMethod.invokeAndHandle(new ServletWebRequest(request),container);
System.err.println(container.getModel());
container.getClass();
}
public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) {
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false),
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
new ServletModelAttributeMethodProcessor(true),
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
);
return composite;
}
}
-
RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @ModelAttribute 方法
-
RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @ModelAttribute 方法
-
以上两种 @ModelAttribute 的解析结果都会缓存来避免重复解析
-
控制器方法调用时,会综合利用本类的 @ModelAttribute 方法和 @ControllerAdvice 中的 @ModelAttribute 方法创建模型工厂
7、返回值处理器
public class Day27Application {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
test7(context);
}
// 1. 测试返回值类型为 ModelAndView
public static void test1(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test1");
Controller controller = new Controller();
Object returnValue = method.invoke(controller);// 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller,method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
ServletWebRequest request = new ServletWebRequest(new MockHttpServletRequest(),new MockHttpServletResponse());
// 检查是否支持此类型的返回值
if(composite.supportsReturnType(handlerMethod.getReturnType())){
composite.handleReturnValue(returnValue,handlerMethod.getReturnType(),container,request);
System.err.println(container.getModel());
System.err.println(container.getViewName());
// 渲染视图
renderView(context,container,request);
}
}
// 2. 测试返回值类型为 String 时, 把它当做视图名
public static void test2(AnnotationConfigApplicationContext context) throws Exception {
//与test1一致
}
//3. 测试返回值添加了 @ModelAttribute 注解时, 此时需找到默认视图名
public static void test3(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test3");
Controller controller = new Controller();
Object returnValue = method.invoke(controller);// 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller,method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
MockHttpServletRequest r = new MockHttpServletRequest();
r.setRequestURI("/test3");
UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(r);
ServletWebRequest request = new ServletWebRequest(r,new MockHttpServletResponse());
// 检查是否支持此类型的返回值
if(composite.supportsReturnType(handlerMethod.getReturnType())){
composite.handleReturnValue(returnValue,handlerMethod.getReturnType(),container,request);
System.err.println(container.getModel());
System.err.println(container.getViewName());
// 渲染视图
renderView(context,container,request);
}
}
public static void test4(AnnotationConfigApplicationContext context) throws Exception {
//与test3一致
}
public static void test5(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test5");
Controller controller = new Controller();
Object returnValue = method.invoke(controller);// 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller,method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
MockHttpServletRequest r = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest request = new ServletWebRequest(r,response);
// 检查是否支持此类型的返回值
if(composite.supportsReturnType(handlerMethod.getReturnType())){
composite.handleReturnValue(returnValue,handlerMethod.getReturnType(),container,request);
System.err.println(container.getModel());
System.err.println(container.getViewName());
// 渲染视图
if(!container.isRequestHandled()){
renderView(context,container,request);
}else{
System.err.println("响应:"+new String(response.getContentAsByteArray(),StandardCharsets.UTF_8));
}
}
}
public static void test6(AnnotationConfigApplicationContext context) throws Exception {
Method method = Controller.class.getMethod("test6");
Controller controller = new Controller();
Object returnValue = method.invoke(controller);// 获取返回值
HandlerMethod handlerMethod = new HandlerMethod(controller,method);
ModelAndViewContainer container = new ModelAndViewContainer();
HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
MockHttpServletRequest r = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest request = new ServletWebRequest(r,response);
// 检查是否支持此类型的返回值
if(composite.supportsReturnType(handlerMethod.getReturnType())){
composite.handleReturnValue(returnValue,handlerMethod.getReturnType(),container,request);
System.err.println(container.getModel());
System.err.println(container.getViewName());
// 渲染视图
if(!container.isRequestHandled()){
renderView(context,container,request);
}else{
for (String name : response.getHeaderNames()) {
System.err.println("请求头:"+name+"=="+response.getHeader(name));
}
System.err.println("响应:"+new String(response.getContentAsByteArray(),StandardCharsets.UTF_8));
}
}
}
public static void test7(AnnotationConfigApplicationContext context) throws Exception {
//与test6一致
}
public static HandlerMethodReturnValueHandlerComposite getReturnValueHandler() {
HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
composite.addHandler(new ModelAndViewMethodReturnValueHandler());
composite.addHandler(new ViewNameMethodReturnValueHandler());
composite.addHandler(new ServletModelAttributeMethodProcessor(false));
composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new HttpHeadersReturnValueHandler());
composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new ServletModelAttributeMethodProcessor(true));
return composite;
}
@SuppressWarnings("all")
private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
ServletWebRequest webRequest) throws Exception {
System.err.println(">>>>>> 渲染视图");
FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);
String viewName = container.getViewName() != null ? container.getViewName() : new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());
System.err.println("没有获取到视图名, 采用默认视图名: {"+viewName+"}");
// 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化
View view = resolver.resolveViewName(viewName, Locale.getDefault());
view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());
System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8));
}
static class Controller {
public ModelAndView test1() {
System.err.println("test1()");
ModelAndView mav = new ModelAndView("view1");
mav.addObject("name", "张三");
return mav;
}
public String test2() {
System.err.println("test2()");
return "view2";
}
@ModelAttribute
// @RequestMapping("/test3")
public User test3() {
System.err.println("test3()");
return new User("李四", 20);
}
public User test4() {
System.err.println("test4()");
return new User("王五", 30);
}
public HttpEntity<User> test5() {
System.err.println("test5()");
return new HttpEntity<>(new User("赵六", 40));
}
public HttpHeaders test6() {
System.err.println("test6()");
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/html");
return headers;
}
@ResponseBody
public User test7() {
System.err.println("test7()");
return new User("钱七", 50);
}
}
// 必须用 public 修饰, 否则 freemarker 渲染其 name, age 属性时失败
public static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
-
常见的返回值处理器
-
ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer
-
返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer
-
返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer
-
此时需找到默认视图名
-
-
返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer
-
此时需找到默认视图名
-
-
返回值类型为 ResponseEntity 时
-
此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
-
-
返回值类型为 HttpHeaders 时
-
会设置 ModelAndViewContainer.requestHandled 为 true
-
-
返回值添加了 @ResponseBody 注解时
-
此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
-
-
-
组合模式在 Spring 中的体现 + 1
8、MessageConverter
public class Day28 {
public static void main(String[] args) throws Exception {
System.out.println("test1:");
test1();
System.out.println("\ntest2:");
test2();
System.out.println("\ntest3:");
test3();
System.out.println("\ntest4:");
test4();
}
public static void test1() throws IOException {
MockHttpOutputMessage message = new MockHttpOutputMessage();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
if(converter.canWrite(User.class,MediaType.APPLICATION_JSON)){
converter.write(new User("张三",18), MediaType.APPLICATION_JSON,message);
System.err.println(message.getBodyAsString());
}
}
public static void test2() throws IOException {
MockHttpOutputMessage message = new MockHttpOutputMessage();
MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) {
converter.write(new User("李四", 18), MediaType.APPLICATION_XML, message);
System.err.println(message.getBodyAsString());
}
}
public static void test3() throws IOException {
MockHttpInputMessage message = new MockHttpInputMessage(
("{\"name\":\"李四\",\"age\":20}").getBytes(StandardCharsets.UTF_8)
);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
if(converter.canRead(User.class,MediaType.APPLICATION_JSON)){
Object read = converter.read(User.class, message);
System.err.println(read);
}
}
@ResponseBody
@RequestMapping(produces = "application/json")
public User getUser() {
return null;
}
public static void test4() throws Exception {
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("王五",22),
new MethodParameter(Day28.class.getMethod("getUser"),-1),
new ModelAndViewContainer(),
webRequest
);
System.err.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
public static class User {
private String name;
private int age;
@JsonCreator
public User(@JsonProperty("name") String name, @JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
-
MessageConverter 的作用
-
@ResponseBody 是返回值处理器解析的
-
但具体转换工作是 MessageConverter 做的
-
-
如何选择 MediaType
-
首先看 @RequestMapping 上有没有指定
-
其次看 request 的 Accept 头有没有指定
-
最后按 MessageConverter 的顺序, 谁能谁先转换
-
9、@ControllerAdvice 之 ResponseBodyAdvice
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result {
private int code;
private String msg;
private Object data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@JsonCreator
private Result(@JsonProperty("code") int code, @JsonProperty("data") Object data) {
this.code = code;
this.data = data;
}
private Result(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static Result success() {
return new Result(200, null);
}
public static Result success(Object data) {
return new Result(200, data);
}
public static Result error(String msg) {
return new Result(500, "服务器内部错误:" + msg);
}
}
@Configuration
public class WebConfig {
@ControllerAdvice
static class MyControllerAdvice implements ResponseBodyAdvice<Object> {
// 满足条件才转换
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 类型
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
return body;
}
return Result.success(body);
}
}
// @Controller
// @ResponseBody
@RestController
public static class MyController {
public User user() {
return new User("王五", 18);
}
}
public static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
public class Day29 {
// {"name":"王五","age":18}
// {"code":xx, "msg":xx, data: {"name":"王五","age":18} }
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
context.getBean(WebConfig.MyController.class),
WebConfig.MyController.class.getMethod("user")
);
handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(Collections.emptyList(), null));
handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers(context));
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndViewContainer container = new ModelAndViewContainer();
handlerMethod.invokeAndHandle(new ServletWebRequest(request, response), container);
System.err.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
/*
学到了什么
a. advice 之三, ResponseBodyAdvice 返回响应体前包装
*/
}
public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) {
HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
composite.addResolvers(
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),
new PathVariableMethodArgumentResolver(),
new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
new ServletRequestMethodArgumentResolver(),
new ServletModelAttributeMethodProcessor(false),
new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
new ServletModelAttributeMethodProcessor(true),
new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
);
return composite;
}
public static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers(AnnotationConfigApplicationContext context) {
// 添加 advice
List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);
List<Object> collect = annotatedBeans.stream().filter(b -> ResponseBodyAdvice.class.isAssignableFrom(b.getBeanType()))
.collect(Collectors.toList());
HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
composite.addHandler(new ModelAndViewMethodReturnValueHandler());
composite.addHandler(new ViewNameMethodReturnValueHandler());
composite.addHandler(new ServletModelAttributeMethodProcessor(false));
composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
composite.addHandler(new HttpHeadersReturnValueHandler());
composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter()), collect));
composite.addHandler(new ServletModelAttributeMethodProcessor(true));
return composite;
}
}
10、异常解析器
1、ExceptionHandlerExceptionResolver
public class Day30 {
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();
System.err.println("----- 1.测试 json ------");
HandlerMethod handlerMethod = new HandlerMethod(new Controller1(),Controller1.class.getMethod("foo"));
Exception e = new ArithmeticException("被零除1");
resolver.resolveException(request,response,handlerMethod,e);
System.err.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
System.err.println("\n----- 2.测试 mav ------");
HandlerMethod handlerMethod2 = new HandlerMethod(new Controller2(),Controller2.class.getMethod("foo"));
Exception e2 = new ArithmeticException("被零除2");
ModelAndView mav = resolver.resolveException(request,response,handlerMethod2,e2);
System.err.println(mav.getModel());
System.err.println(mav.getViewName());
System.err.println("\n----- 3.测试嵌套异常 ------");
HandlerMethod handlerMethod3 = new HandlerMethod(new Controller3(),Controller3.class.getMethod("foo"));
Exception e3 = new Exception("e1", new RuntimeException("e2", new IOException("e3")));
resolver.resolveException(request,response,handlerMethod3,e3);
System.err.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
System.err.println("\n----- 4.测试异常处理方法参数解析 ------");
HandlerMethod handlerMethod4 = new HandlerMethod(new Controller4(),Controller4.class.getMethod("foo"));
Exception e4 = new Exception("e1");
resolver.resolveException(request,response,handlerMethod4,e4);
System.err.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
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.err.println(request);
return Map.of("error", e.getMessage());
}
}
}
-
它能够重用参数解析器、返回值处理器,实现组件重用
-
它能够支持嵌套异常
2、@ExceptionHandler
@Configuration
public class WebConfig {
@ControllerAdvice
static class MyControllerAdvice {
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(Exception e) {
return Map.of("error", e.getMessage());
}
}
@Bean
public ExceptionHandlerExceptionResolver resolver() {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
return resolver;
}
}
public class Day31 {
public static void main(String[] args) throws NoSuchMethodException {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);
HandlerMethod handlerMethod = new HandlerMethod(new Controller5(),Controller5.class.getMethod("foo"));
Exception e = new Exception("eeee");
resolver.resolveException(request,response,handlerMethod,e);
System.err.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}
static class Controller5 {
public void foo() {
}
}
}
-
ExceptionHandlerExceptionResolver 初始化时会解析 @ControllerAdvice 中的 @ExceptionHandler 方法
-
ExceptionHandlerExceptionResolver 会以类为单位,在该类首次处理异常时,解析此类的 @ExceptionHandler 方法
-
以上两种 @ExceptionHandler 的解析结果都会缓存来避免重复解析
11、Tomcat 异常处理
public class Day32 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
handlerMapping.getHandlerMethods().forEach((RequestMappingInfo k, HandlerMethod v) -> {
System.err.println("映射路径:" + k + "\t方法信息:" + v);
});
}
}
@Configuration
public class WebConfig {
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
@Bean // @RequestMapping
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
@Bean // 注意默认的 RequestMappingHandlerAdapter 不会带 jackson 转换器
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
return handlerAdapter;
}
@Bean // 修改了 Tomcat 服务器默认错误地址
public ErrorPageRegistrar errorPageRegistrar() { // 出现错误,会使用请求转发 forward 跳转到 error 地址
return webServerFactory -> webServerFactory.addErrorPages(new ErrorPage("/error"));
}
@Bean //TomcatServletWebServerFactory 初始化前用它增强, 注册所有 ErrorPageRegistrar
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
return new ErrorPageRegistrarBeanPostProcessor();
}
@Controller
public static class MyController {
@RequestMapping("test")
public ModelAndView test() {
int i = 1 / 0;
return null;
}
// @RequestMapping("/error")
// @ResponseBody
// public Map<String, Object> error(HttpServletRequest request) {
// Throwable e = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// return Map.of("error", e.getMessage());
// }
}
//SpringBoot中的,ErrorProperties 封装环境键值, ErrorAttributes 控制有哪些错误信息
@Bean
public BasicErrorController basicErrorController() {
ErrorProperties errorProperties = new ErrorProperties();
errorProperties.setIncludeException(true);
return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}
//---------视图渲染---------
//名称为 error 的视图, 作为 BasicErrorController 的 text/html 响应结果
@Bean
public View error() {
return new View() {
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.err.println(">>>"+model);
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("<h3>服务器内部错误</h3>");
}
};
}
//收集容器中所有 View 对象, bean 的名字作为视图名
@Bean
public ViewResolver viewResolver() {
return new BeanNameViewResolver();
}
}
-
我们知道 @ExceptionHandler 只能处理发生在 mvc 流程中的异常,例如控制器内、拦截器内,那么如果是 Filter 出现了异常,如何进行处理呢?
-
在 Spring Boot 中,是这么实现的:
-
因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚
-
先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为
/error
也可以通过${server.error.path}
进行配置 -
当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至
/error
这个地址-
当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理
-
-
Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为
/error
,所以处理异常的职责就又回到了 Spring -
异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到
-
具体异常信息会由 DefaultErrorAttributes 封装好
-
BasicErrorController 通过 Accept 头判断需要生成哪种 MediaType 的响应
-
如果要的不是 text/html,走 MessageConverter 流程
-
如果需要 text/html,走 mvc 流程,此时又分两种情况
-
配置了 ErrorViewResolver,根据状态码去找 View
-
没配置或没找到,用 BeanNameViewResolver 根据一个固定为 error 的名字找到 View,即所谓的 WhitelabelErrorView
-
-
-
12、BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter
public class A33 {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context
= new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
/*
学到了什么
a. BeanNameUrlHandlerMapping, 以 / 开头的 bean 的名字会被当作映射路径
b. 这些 bean 本身当作 handler, 要求实现 Controller 接口
c. SimpleControllerHandlerAdapter, 调用 handler
对比
a. RequestMappingHandlerAdapter, 以 @RequestMapping 作为映射路径
b. 控制器的具体方法会被当作 handler
c. RequestMappingHandlerAdapter, 调用 handler
*/
}
}
@Configuration
public class WebConfig {
@Bean // 内嵌 web 容器工厂
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory(8080);
}
@Bean // 创建 DispatcherServlet
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean // 注册 DispatcherServlet, Spring MVC 的入口
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
// /c1 --> /c1必须有 /
// /c2 --> /c2
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
return new BeanNameUrlHandlerMapping();
}
//控制器实现相同的接口
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
@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;
};
}
}
自定义:
@Configuration
public class WebConfig_1 {
@Bean // 内嵌 web 容器工厂
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory(8080);
}
@Bean // 创建 DispatcherServlet
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean // 注册 DispatcherServlet, Spring MVC 的入口
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
//自定义
// /c1 --> /c1
// /c2 --> /c2
@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);
}
@Autowired
private ApplicationContext context;
private Map<String, Controller> collect;
@PostConstruct
public void init() {
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 {
@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 = (Controller) handler;
controller.handleRequest(request, response);
}
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
@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;
};
}
}
-
BeanNameUrlHandlerMapping,以 / 开头的 bean 的名字会被当作映射路径
-
这些 bean 本身当作 handler,要求实现 Controller 接口
-
SimpleControllerHandlerAdapter,调用 handler
-
模拟实现这组映射器和适配器
RouterFunctionMapping 与 HandlerFunctionAdapter
public class A34 {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
/*
学到了什么
函数式控制器
a. RouterFunctionMapping, 通过 RequestPredicate 映射
b. handler 要实现 HandlerFunction 接口
c. HandlerFunctionAdapter, 调用 handler
对比
a. RequestMappingHandlerMapping, 以 @RequestMapping 作为映射路径
b. 控制器的具体方法会被当作 handler
c. RequestMappingHandlerAdapter, 调用 handler
*/
}
}
@Configuration
public class WebConfig {
@Bean // 内嵌 web 容器工厂
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory(8080);
}
@Bean // 创建 DispatcherServlet
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean // 注册 DispatcherServlet, Spring MVC 的入口
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
@Bean
public RouterFunctionMapping routerFunctionMapping() {
return new RouterFunctionMapping();
}
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
}
@Bean
public RouterFunction<ServerResponse> r1() {
return route(GET("/r1"), request -> ok().body("this is r1"));
}
@Bean
public RouterFunction<ServerResponse> r2() {
return route(GET("/r2"), request -> ok().body("this is r2"));
}
}
13、SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter
@Configuration
public class WebConfig {
@Bean // 内嵌 web 容器工厂
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory(8080);
}
@Bean // 创建 DispatcherServlet
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean // 注册 DispatcherServlet, Spring MVC 的入口
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
Map<String, ResourceHttpRequestHandler> map = context.getBeansOfType(ResourceHttpRequestHandler.class);
handlerMapping.setUrlMap(map);
System.err.println(map);
return handlerMapping;
}
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter();
}
/*
/index.html
/r1.html
/r2.html
/**
*/
@Bean("/**")
public ResourceHttpRequestHandler handler1() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(List.of(new ClassPathResource("static/")));
handler.setResourceResolvers(List.of(
//读取到缓存中的资源
new CachingResourceResolver(new ConcurrentMapCache("cache1")),
//读取压缩的资源
new EncodedResourceResolver(),
//原始资源解析
new PathResourceResolver()
));
return handler;
}
/*
/img/1.jpg
/img/2.jpg
/img/3.jpg
/img/**
*/
@Bean("/img/**")
public ResourceHttpRequestHandler handler2() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(List.of(new ClassPathResource("images/")));
return handler;
}
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {
Resource resource = context.getResource("classpath:static/index.html");
return new WelcomePageHandlerMapping(null, context, resource, "/**");
// Controller 接口
}
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
//启动时对静态资源进行压缩
@PostConstruct
@SuppressWarnings("all")
public void initGzip() throws IOException {
Resource resource = new ClassPathResource("static");
File dir = resource.getFile();
for (File file : dir.listFiles(pathname -> pathname.getName().endsWith(".html"))) {
System.out.println(file);
try (FileInputStream fis = new FileInputStream(file); GZIPOutputStream fos = new GZIPOutputStream(new FileOutputStream(file.getAbsoluteFile() + ".gz"))) {
byte[] bytes = new byte[8 * 1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
}
}
}
}
-
SimpleUrlHandlerMapping 不会在初始化时收集映射信息,需要手动收集
-
SimpleUrlHandlerMapping 映射路径
-
ResourceHttpRequestHandler 作为静态资源 handler
-
HttpRequestHandlerAdapter, 调用此 handler
欢迎页:
-
欢迎页支持静态欢迎页与动态欢迎页
-
WelcomePageHandlerMapping 映射欢迎页(即只映射 '/')
-
它内置的 handler ParameterizableViewController 作用是不执行逻辑,仅根据视图名找视图
-
视图名固定为 forward:index.html
-
-
SimpleControllerHandlerAdapter, 调用 handler
-
转发至 /index.html
-
处理 /index.html 又会走上面的静态资源处理流程
-
映射器与适配器小结:
-
HandlerMapping 负责建立请求与控制器之间的映射关系
-
RequestMappingHandlerMapping (与 @RequestMapping 匹配)
-
WelcomePageHandlerMapping (/)
-
BeanNameUrlHandlerMapping (与 bean 的名字匹配 以 / 开头)
-
RouterFunctionMapping (函数式 RequestPredicate, HandlerFunction)
-
SimpleUrlHandlerMapping (静态资源 通配符 /** /img/**)
-
之间也会有顺序问题, boot 中默认顺序如上
-
-
HandlerAdapter 负责实现对各种各样的 handler 的适配调用
-
RequestMappingHandlerAdapter 处理:@RequestMapping 方法
-
参数解析器、返回值处理器体现了组合模式
-
-
SimpleControllerHandlerAdapter 处理:Controller 接口
-
HandlerFunctionAdapter 处理:HandlerFunction 函数式接口
-
HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口 (静态资源处理)
-
这也是典型适配器模式体现
-
-
ResourceHttpRequestHandler.setResourceResolvers 这是典型责任链模式体现
14、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
-
@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 方法
-
处理异常或视图渲染
-
如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
-
@ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
-
-
正常,走视图解析及渲染流程
-
-
调用拦截器的 afterCompletion 方法
-