Spring高级(三)—— SpringMVC

目录

1、RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter

1、DispatcherServlet 初始化

2、参数解析器

3、参数名解析

4、对象绑定与类型转换

1、底层第一套转换接口与实现 (Spring)

2、底层第二套转换接口(jdk)

3、高层接口与实现

4、类型转换与数据绑定

5、数据绑定工厂

6、获取泛型参数

 5、@ControllerAdvice 之 @InitBinder

6、控制器方法执行流程(@ModelAttribute)

7、返回值处理器

 8、MessageConverter

9、@ControllerAdvice 之 ResponseBodyAdvice

 10、异常解析器

1、ExceptionHandlerExceptionResolver

2、@ExceptionHandler

11、Tomcat 异常处理

12、BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter

RouterFunctionMapping 与 HandlerFunctionAdapter

13、SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter

14、mvc 处理流程


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);
//        }

    }
}

  1. DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化

  2. 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化

  3. RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中

    • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息

    • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象

    • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet

  4. 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 +
                    '}';
        }
    }
}

 

  1. 初步了解 RequestMappingHandlerAdapter 的调用过程

    1. 控制器方法被封装为 HandlerMethod

    2. 准备对象绑定与类型转换

    3. 准备 ModelAndViewContainer 用来存储中间 Model 结果

    4. 解析每个参数值

  2. 解析参数依赖的就是各种参数解析器,它们都有两个重要方法

    • supportsParameter 判断是否支持方法参数

    • resolveArgument 解析方法参数

  3. 常见参数的解析

    • @RequestParam

    • 省略 @RequestParam

    • @RequestParam(defaultValue)

    • MultipartFile

    • @PathVariable

    • @RequestHeader

    • @CookieValue

    • @Value

    • HttpServletRequest 等

    • @ModelAttribute

    • 省略 @ModelAttribute

    • @RequestBody

  4. 组合模式在 Spring 中的体现

  5. @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));

    }

}
  1. 如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名

  2. 如果编译时添加了 -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 的用法和扩展点

  1. 可以解析控制器的 @InitBinder 标注方法作为扩展点,添加自定义转换器

    • 控制器私有范围

  2. 可以通过 ConfigurableWebBindingInitializer 配置 ConversionService 作为扩展点,添加自定义转换器

    • 公共范围

  3. 同时加了 @InitBinder 和 ConversionService 的转换优先级

    1. 优先采用 @InitBinder 的转换器

    2. 其次使用 ConversionService 的转换器

    3. 使用默认转换器

    4. 特殊处理(例如有参构造)

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())
        );
    }
}

  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法

  2. RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法

  3. 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析

  4. 控制器方法调用时,会综合利用本类的 @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;
    }
}
  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @ModelAttribute 方法

  2. RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @ModelAttribute 方法

  3. 以上两种 @ModelAttribute 的解析结果都会缓存来避免重复解析

  4. 控制器方法调用时,会综合利用本类的 @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 +
                    '}';
        }
    }
}

 

  1. 常见的返回值处理器

    • ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer

    • 返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer

    • 返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer

      • 此时需找到默认视图名

    • 返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer

      • 此时需找到默认视图名

    • 返回值类型为 ResponseEntity 时

      • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true

    • 返回值类型为 HttpHeaders 时

      • 会设置 ModelAndViewContainer.requestHandled 为 true

    • 返回值添加了 @ResponseBody 注解时

      • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true

  2. 组合模式在 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 +
                    '}';
        }
    }
}

  1. MessageConverter 的作用

    • @ResponseBody 是返回值处理器解析的

    • 但具体转换工作是 MessageConverter 做的

  2. 如何选择 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());
        }
    }
}

  1. 它能够重用参数解析器、返回值处理器,实现组件重用

  2. 它能够支持嵌套异常

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() {

        }
    }
}
  1. ExceptionHandlerExceptionResolver 初始化时会解析 @ControllerAdvice 中的 @ExceptionHandler 方法

  2. ExceptionHandlerExceptionResolver 会以类为单位,在该类首次处理异常时,解析此类的 @ExceptionHandler 方法

  3. 以上两种 @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 中,是这么实现的:

    1. 因为内嵌了 Tomcat 容器,因此可以配置 Tomcat 的错误页面,Filter 与 错误页面之间是通过请求转发跳转的,可以在这里做手脚

    2. 先通过 ErrorPageRegistrarBeanPostProcessor 这个后处理器配置错误页面地址,默认为 /error 也可以通过 ${server.error.path} 进行配置

    3. 当 Filter 发生异常时,不会走 Spring 流程,但会走 Tomcat 的错误处理,于是就希望转发至 /error 这个地址

      • 当然,如果没有 @ExceptionHandler,那么最终也会走到 Tomcat 的错误处理

    4. Spring Boot 又提供了一个 BasicErrorController,它就是一个标准 @Controller,@RequestMapping 配置为 /error,所以处理异常的职责就又回到了 Spring

    5. 异常信息由于会被 Tomcat 放入 request 作用域,因此 BasicErrorController 里也能获取到

    6. 具体异常信息会由 DefaultErrorAttributes 封装好

    7. 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;
        };
    }
}
  1. BeanNameUrlHandlerMapping,以 / 开头的 bean 的名字会被当作映射路径

  2. 这些 bean 本身当作 handler,要求实现 Controller 接口

  3. SimpleControllerHandlerAdapter,调用 handler

  4. 模拟实现这组映射器和适配器

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);
                }
            }
        }
    }

}

  1. SimpleUrlHandlerMapping 不会在初始化时收集映射信息,需要手动收集

  2. SimpleUrlHandlerMapping 映射路径

  3. ResourceHttpRequestHandler 作为静态资源 handler

  4. HttpRequestHandlerAdapter, 调用此 handler

欢迎页:

  1. 欢迎页支持静态欢迎页与动态欢迎页

  2. WelcomePageHandlerMapping 映射欢迎页(即只映射 '/')

    • 它内置的 handler ParameterizableViewController 作用是不执行逻辑,仅根据视图名找视图

    • 视图名固定为 forward:index.html

  3. SimpleControllerHandlerAdapter, 调用 handler

    • 转发至 /index.html

    • 处理 /index.html 又会走上面的静态资源处理流程

映射器与适配器小结

  1. HandlerMapping 负责建立请求与控制器之间的映射关系

    • RequestMappingHandlerMapping (与 @RequestMapping 匹配)

    • WelcomePageHandlerMapping (/)

    • BeanNameUrlHandlerMapping (与 bean 的名字匹配 以 / 开头)

    • RouterFunctionMapping (函数式 RequestPredicate, HandlerFunction)

    • SimpleUrlHandlerMapping (静态资源 通配符 /** /img/**)

    • 之间也会有顺序问题, boot 中默认顺序如上

  2. HandlerAdapter 负责实现对各种各样的 handler 的适配调用

    • RequestMappingHandlerAdapter 处理:@RequestMapping 方法

      • 参数解析器、返回值处理器体现了组合模式

    • SimpleControllerHandlerAdapter 处理:Controller 接口

    • HandlerFunctionAdapter 处理:HandlerFunction 函数式接口

    • HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口 (静态资源处理)

    • 这也是典型适配器模式体现

  3. ResourceHttpRequestHandler.setResourceResolvers 这是典型责任链模式体现

14、mvc 处理流程

当浏览器发送一个请求 http://localhost:8080/hello 后,请求到达服务器,其处理流程是:

  1. 服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术

    • 路径:默认映射路径为 /,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器

      • jsp 不会匹配到 DispatcherServlet

      • 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet

    • 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean

    • 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量

      • HandlerMapping,初始化时记录映射关系

      • HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器

      • HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器

      • ViewResolver

  2. DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法

    • 例如根据 /hello 路径找到 @RequestMapping("/hello") 对应的控制器方法

    • 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet

    • HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象

  3. DispatcherServlet 接下来会:

    1. 调用拦截器的 preHandle 方法

    2. 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 步走视图解析及渲染流程

    3. 调用拦截器的 postHandle 方法

    4. 处理异常或视图渲染

      • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程

        • @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理

      • 正常,走视图解析及渲染流程

    5. 调用拦截器的 afterCompletion 方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值