aoe1231

看得完吗

Spring高级 - 第2部分

10、RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter

代码演示:

/**
 * 例如经常要用到请求头中的 token 信息,用下面的注解来标注由哪个参数来获取它
 *      token=令牌
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String name;
    private int age;
}

@Slf4j
@Controller
public class Controller1 {

    @GetMapping("/test1")
    public ModelAndView test1() throws Exception {
        log.info("test1()");
        return null;
    }

    @PostMapping("/test2")
    public ModelAndView test2(@RequestParam("name") String name) {
        log.info("test2({})", name);
        return null;
    }

    @PutMapping("/test3")
    public ModelAndView test3(@Token String token) {
        log.info("test3({})", token);
        return null;
    }

    @RequestMapping("/test4.yml")
    @Yml // 定义作用:将返回的 User 转换为 yml 格式
    public User test4() {
        log.info("test4()");
        return new User("张三", 18);
    }

    public static void main(String[] args) {
        String str = new Yaml().dump(new User("张三", 18));
        System.out.println(str);
    }
}

// 自定义请求映射处理器适配器(将protect方法重写为public方法方便调用)
public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {

    @Override
    public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        return super.invokeHandlerMethod(request, response, handlerMethod);
    }

}

/**
 * 方法参数解析器 - 解析 @Token
 */
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
    // 是否支持某个参数
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Token token = parameter.getParameterAnnotation(Token.class);
        return token != null;
    }

    // 解析参数,返回的结果就是给方法的参数赋的值
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return webRequest.getHeader("token");
    }
}

/**
 * 返回值处理器 - 处理 @Yml
 */
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        Yml yml = returnType.getMethodAnnotation(Yml.class);// 获取方法参数所在方法上的注解
        return yml != null;
    }

    /**
     *
     * @param returnValue Controller 返回的对象
     * @param returnType
     * @param mavContainer
     * @param webRequest
     * @throws Exception
     */
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 1、转换返回结果为 yml
        String str = new Yaml().dump(returnValue);
        // 2、将 yaml 字符串写入响应
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        response.setContentType("test/plain;charset=utf-8");
        response.getWriter().print(str);
        // 2、设置请求已经处理完毕
        mavContainer.setRequestHandled(true);
    }
}

@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
// WebMvcProperties :绑定配置文件中以 spring.webmvc 开头的所有属性
// ServerProperties :绑定配置文件中以 server 开头的所有属性
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class}) // 存入容器
public class WebConfig {
    // 内嵌 Web 容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
        TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
        serverFactory.setPort(serverProperties.getPort());
        return serverFactory;
    }

    // 创建 DispatchServlet
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    // 注册DispatchServlet,SpringMVC 的入口
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); // 值大于0,则在创建DispatchServlet之后初始化
        return registrationBean;
    }

    // 1、如果用 DispatchServlet 初始化时默认添加的组件,并不会作为Bean,给测试带来困扰
    // 因此我们手动往容器中加入 RequestMappingHandlerMapping
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    // 2、继续加入 RequestMappingHandlerAdapter,会替换掉 DispatchServlet 默认的 4 个 HandlerAdapter
    @Bean
    public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();

        // 添加自定义参数解析器
        TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver(); // @Token
        handlerAdapter.setCustomArgumentResolvers(Arrays.asList(tokenArgumentResolver));
        // 添加自定义返回值处理器
        YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler(); // @Yml
        handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(ymlReturnValueHandler));

        return handlerAdapter;
    }
}

// application.properties
server.port=81
spring.mvc.servlet.load-on-startup: 1

编写测试代码:

@Slf4j
public class Test16Application {

    static MockHttpServletRequest test1Request() {
        return new MockHttpServletRequest("GET", "/test1");
    }

    static MockHttpServletResponse test1Response() {
        return new MockHttpServletResponse();
    }

    static MockHttpServletRequest test2Request() {
        MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
        request.setParameter("name", "张三");
        return request;
    }

    static MockHttpServletResponse test2Response() {
        return new MockHttpServletResponse();
    }

    static MockHttpServletRequest test3Request() {
        MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
        request.addHeader("token", "某个令牌");
        return request;
    }

    static MockHttpServletResponse test3Response() {
        return new MockHttpServletResponse();
    }

    static MockHttpServletRequest test4Request() {
        return new MockHttpServletRequest("GET", "/test4.yml");
    }

    static MockHttpServletResponse test4Response() {
        return new MockHttpServletResponse();
    }

    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

        // 1、假设请求来了
        MockHttpServletRequest request = test4Request(); // 模拟请求
        MockHttpServletResponse response = test4Response(); // 模拟响应

        // 2、获取控制器方法,返回处理器执行链对象
        System.out.println("--------------");
        // 作用:解析 @RequestMapping注解和派生注解,生成路径与控制器方法的映射关系,在初始化时就生成了
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 根据请求获取到对应的处理器执行链 - 会找到2.1获取到的对应处理器方法(Controller 的对应方法),并和拦截器一起封装成执行链
        HandlerExecutionChain chain = handlerMapping.getHandler(request);
        System.out.println(chain);
        // 2.1、调试:handlerMapping.getHandler(request) 方法会获取请求的 HandlerMethod(代表控制器方法信息):
        System.out.println("--------------");
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k,v) -> {
            System.out.println(k + "=" + v);
        });

        // 3、获取请求映射处理器适配器,作用是调用控制器方法,需要使用到上面获取到的处理器执行链。会使用我们添加的参数解析器、返回值处理器处理
        System.out.println("--------------");
        MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
        handlerAdapter.invokeHandlerMethod(request, response, ((HandlerMethod) chain.getHandler()));

        // 4、检查响应
        System.out.println("--------------");
        byte[] content = response.getContentAsByteArray();
        System.out.println(new String(content, StandardCharsets.UTF_8));

        // 查看适配器的所有参数解析器
        System.out.println(">>>>>>>>>>>>>>>");
        List<HandlerMethodArgumentResolver> argumentResolvers = handlerAdapter.getArgumentResolvers();
        for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {
            System.out.println(argumentResolver);
        }

        // 查看返回值解析器
        System.out.println(">>>>>>>>>>>>>>>");
        List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
        for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
            System.out.println(returnValueHandler);
        }
    }
}

结果:
...
--------------
15:21:24.198 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.clp.test16.Controller1#test4()
HandlerExecutionChain with [com.clp.test16.Controller1#test4()] and 0 interceptors
--------------
{POST [/test2]}=com.clp.test16.Controller1#test2(String)
{PUT [/test3]}=com.clp.test16.Controller1#test3(String)
{GET [/test1]}=com.clp.test16.Controller1#test1()
{ [/test4.yml]}=com.clp.test16.Controller1#test4()
--------------
15:21:24.207 [main] INFO com.clp.test16.Controller1 - test4()
--------------
!!com.clp.test16.User {age: 18, name: 张三}

>>>>>>>>>>>>>>>
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@2de366bb
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@3f093abe
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@61a002b1
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@4eeea57d
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@780ec4a5
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@e24ddd0
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@6f70f32f
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@548e76f1
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@5aabbb29
org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@72c927f1
org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@1ac85b0c
org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@3dd69f5a
org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@3aa3193a
org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@1ee4730
org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@59a67c3a
org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@5003041b
org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@724bade8
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@16fb356
org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@6bc248ed
org.springframework.web.method.annotation.ModelMethodProcessor@23a9ba52
org.springframework.web.method.annotation.MapMethodProcessor@ca27722
org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@70ab80e3
org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@9573b3b
org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@67427b69
com.clp.test16.TokenArgumentResolver@78461bc4
org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@544630b7
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@64f857e7
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1095f122
>>>>>>>>>>>>>>>
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@58c540cf
org.springframework.web.method.annotation.ModelMethodProcessor@3d6300e8
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@1b822fcc
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@24a1c17f
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@56102e1c
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@73511076
org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@7927bd9f
org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@532721fd
org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@410954b
org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@7fb9f71f
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@3b366632
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@51f49060
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@514eedd8
org.springframework.web.method.annotation.MapMethodProcessor@617fe9e1
com.clp.test16.YmlReturnValueHandler@6970140a
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1cf2fed4

11、参数解析器

11.1、参数解析器模拟实现

/**
 * 目标:解析控制器方法的参数值
 * 常见的参数处理器如下:
 * org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@2de366bb
 * org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@3f093abe
 * org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@61a002b1
 * org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@4eeea57d
 * org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@780ec4a5
 * org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@e24ddd0
 * org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@6f70f32f
 * org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@548e76f1
 * org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@5aabbb29
 * org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@72c927f1
 * org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@1ac85b0c
 * org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@3dd69f5a
 * org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@3aa3193a
 * org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@1ee4730
 * org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@59a67c3a
 * org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@5003041b
 * org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@724bade8
 * org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@16fb356
 * org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@6bc248ed
 * org.springframework.web.method.annotation.ModelMethodProcessor@23a9ba52
 * org.springframework.web.method.annotation.MapMethodProcessor@ca27722
 * org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@70ab80e3
 * org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@9573b3b
 * org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@67427b69
 * org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@544630b7
 * org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@64f857e7
 * org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1095f122
 *
 * 每个参数处理器能干啥:
 *      1、看是否支持某种参数;
 *      2、获取参数的值
 * HandlerMethodArgumentResolverComposite:组合模式在Spring中的体现
 * @RequestParam、@CookieValue 等注解中的参数名、默认值,都可以写成活的,都从 ${} #{} 中获取
 */
public class Test17Application {
    public static void main(String[] args) throws Exception {
        // 解析配置类的容器(非Web环境)
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

        // 准备测试 Request
        HttpServletRequest multiRequest = mockRequest();

        // 要点1:控制器方法被封装为 HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(Controller.class, 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 binderFactory = new ServletRequestDataBinderFactory(null, null);

        // 要点3:准备 ModelAndViewController 用来存储中间 Model 结果
        ModelAndViewContainer container = new ModelAndViewContainer();

        // 要点4:解析每个参数值
        for (MethodParameter methodParameter : handlerMethod.getMethodParameters()) {
            String annotations = Arrays.stream(methodParameter.getParameterAnnotations())
                    .map(anno -> anno.annotationType().getSimpleName())
                    .collect(Collectors.joining());
            String str = annotations.length() > 0 ? "@" + annotations + " " : " ";
            methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            // 多个解析器的组合
            HandlerMethodArgumentResolverComposite resolverComposite = new HandlerMethodArgumentResolverComposite();
            resolverComposite.addResolvers(
                    // 解析 @PathVariable
                    new PathVariableMethodArgumentResolver(),
                    // 解析 @RequestHeader
                    new RequestHeaderMethodArgumentResolver(beanFactory),
                    // 解析 @CookieValue
                    new ServletCookieValueMethodArgumentResolver(beanFactory),
                    // 解析 @Value
                    new ExpressionValueMethodArgumentResolver(beanFactory),
                    // 获取 HttpServletRequest (该解析器不只解析 HttpServletRequest)
                    new ServletRequestMethodArgumentResolver(),
                    // 解析 @RequestBody
                    new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter())),
                    // 解析 @ModelAttribute
                    new ServletModelAttributeMethodProcessor(false), // true:可以省略 @ModelAttribute
                    // 解析 @RequestParam                               false 表示方法参数必须有 @RequestParam 才能解析
                    new RequestParamMethodArgumentResolver(beanFactory, false)
            );

            // 判断解析器是否支持此方法参数的解析
            if (resolverComposite.supportsParameter(methodParameter)) {
                Object obj = resolverComposite.resolveArgument(methodParameter, container, new ServletWebRequest(multiRequest), binderFactory);
                // 打印调试信息
                System.out.println("[" + methodParameter.getParameterIndex() + "] " + str
                        + methodParameter.getParameterType().getSimpleName()
                        + " " + methodParameter.getParameterName() + " -> " + obj + "(" + obj.getClass().getSimpleName() + ")"
                );
                System.out.println("模型数据为:" + container.getModel());
            } else {
                // 打印调试信息
                System.out.println("[" + methodParameter.getParameterIndex() + "] " + str
                        + methodParameter.getParameterType().getSimpleName()
                        + " " + methodParameter.getParameterName()
                );
            }
            System.out.println();
        }
    }

    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)));
        // 为 @PathVariable 解析器提供数据支持 // {id=123}
        Map<String, String> uriTemplateVariables = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
        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=值 的,涉及类型转换
                @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // 如果没有获取到值,则使用默认值(从环境变量、配置文件中获取)
                @RequestParam("file") MultipartFile file, // 解析上传文件数据
                @PathVariable("id") int id, // 解析路径参数,如: 映射路径为test/{id} -> 请求为test/123时id值为123
                @RequestHeader("Content-Type") String header, // 获取请求头信息
                @CookieValue("token") String token, // 获取 cookie 中数据
                @Value("${JAVA_HOME}") String home2, // 从 spring 中获取数据
                HttpServletRequest request, // 解析特殊类型的参数,如 request、response、session...
                @ModelAttribute("user1") User user1, // name=zhangsan&age=18
                User user2, // 省略 @ModelAttribute
                @RequestBody User user3 // 从请求体中获取数据(默认请求体为json)
        ) {
        }
    }

    static class Config {
    }

    @Data
    static class User {
        private String name;
        private int age;

        @Override
        public String toString() {
            return "User" + "@" + hashCode() + "{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

结果:
...
[0] @RequestParam String name1 -> zhangsan(String)
模型数据为:{}

[1]  String name2

[2] @RequestParam int age -> 18(Integer)
模型数据为:{}

17:18:09.741 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'JAVA_HOME' in PropertySource 'systemEnvironment' with value of type String
[3] @RequestParam String home1 -> D:\jdks\jdk1.8(String)
模型数据为:{}

[4] @RequestParam MultipartFile file -> org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@4fb3ee4e(StandardMultipartFile)
模型数据为:{}

[5] @PathVariable int id -> 123(Integer)
模型数据为:{}

[6] @RequestHeader String header -> application/json(String)
模型数据为:{}

[7] @CookieValue String token -> 123456(String)
模型数据为:{}

17:18:09.775 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'JAVA_HOME' in PropertySource 'systemEnvironment' with value of type String
[8] @Value String home2 -> D:\jdks\jdk1.8(String)
模型数据为:{}

[9]  HttpServletRequest request -> org.springframework.web.multipart.support.StandardMultipartHttpServletRequest@43dac38f(StandardMultipartHttpServletRequest)
模型数据为:{}

[10] @ModelAttribute User user1 -> User@779432{name='张三', age=18}(User)
模型数据为:{user1=User@779432{name='张三', age=18}, org.springframework.validation.BindingResult.user1=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

[11]  User user2

17:18:09.897 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Read "application/json" to [User@846722{name='李四', age=20}]
[12] @RequestBody User user3 -> User@846722{name='李四', age=20}(User)
模型数据为:{user1=User@779432{name='张三', age=18}, org.springframework.validation.BindingResult.user1=org.springframework.validation.BeanPropertyBindingResult: 0 errors, org.springframework.validation.BindingResult.user=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

11.2、获取方法参数名

/**
 * Java 编译器默认不存储方法参数名,可以在编译时添加参数: javac -parameters .\Bean2.java
 *
 * 目标:如何获取方法参数名
 *      springboot 在编译时会加 -parameters(接口和类都会有效);
 *      大部分 IDE 编译时都会加 -g (对类有效,对接口无效)
 */
public class Test17Application2 {

    static interface Bean1 {
        public void foo(String name, int age);
    }

    static class Bean2 {
        public void foo(String name, int age) {}
    }

    public static void main(String[] args) throws NoSuchMethodException {
        System.out.println("接口:");
        System.out.println("--------------------");
        extracted(Bean1.class.getMethod("foo", String.class, int.class));
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println("类:");
        System.out.println("--------------------");
        extracted(Bean2.class.getMethod("foo", String.class, int.class));
    }

    private static void extracted(Method method) throws NoSuchMethodException {
        // 1、反射获取参数名 - 默认编译后运行是获取不到的
        List<String> list = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.toList());
        System.out.println(list);

        // 2、基于 Spring提供的工具, 本地变量表获取
        System.out.println("--------------------");
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        System.out.println(Arrays.toString(parameterNames));

        // 3、二者的结合实现
        System.out.println("--------------------");
        DefaultParameterNameDiscoverer defaultDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames2 = defaultDiscoverer.getParameterNames(method);
        System.out.println(Arrays.toString(parameterNames2));
    }
}

结果:
接口:
--------------------
[arg0, arg1]
--------------------
null
--------------------
null
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
类:
--------------------
[arg0, arg1]
--------------------
[name, age]
--------------------
[name, age]

11.3、对象绑定与类型转换

两套底层转换接口,一套高层转换接口。

11.3.1、底层第1套接口与实现(Spring 提供)

  • Printer:把其他类型转为 String;
  • Parser:把 String 转为其他类型;
  • Formatter:综合 Printer 与 Parser 功能;
  • Converter:把类型 S 转为类型 T;

Printer、Parser、Converter 经过适配器转换成 GenericConverter 放入 Converters 集合。FormatteringConversionService 利用它们实现转换。

11.3.2、底层第2套转换接口(JDK 提供)

  • PropertyEditor:把 String 与其他类型相互转换;
  • PropertyEditorRegistry:可以注册多个 PropertyEditor 对象;
  • 与第1套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配。

11.3.3、高层接口与实现

ConversionService 与 PropertyEditorRegistry 都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派 ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)。

  • 首先看是否有自定义转换器,@InitBinder 添加的即属于这种(用了适配器模式把 Formatter 转为需要的 PropertyEditor);
  • 在看看有没有 ConversionService 转换;
  • 再利用默认的 PropertyEditor 转换;
  • 最后有一些特殊处理。

SimpleTypeConverter:仅做类型转换;

BeanWrapperImpl:为 bean 的属性赋值,当需要时做类型转换,走 Property(Getter/Setter);

DirectFieldAccessor:为 bean 的属性赋值,当需要时做类型转换,走 Field(Field字段);

ServletRequestDataBinder:为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能。

11.3.4、类型转换与数据绑定示例

代码演示1:

public class Test18 {

    @Getter
    @Setter
    @ToString
    static class MyBean {
        private int a;
        private String b;
        private Date c;
    }

    public static void main(String[] args) {
        // 仅有类型转换的功能
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();
        Integer number = typeConverter.convertIfNecessary("13", int.class);
        System.out.println(number);
        Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
        System.out.println(date);

        System.out.println("----------------------------------------");
        // 利用反射原理(Getter/Setter),为 bean 的属性赋值
        MyBean myBean1 = new MyBean();
        BeanWrapperImpl beanWrapper = new BeanWrapperImpl(myBean1);
        beanWrapper.setPropertyValue("a", "10");
        beanWrapper.setPropertyValue("b", "hello");
        beanWrapper.setPropertyValue("c", "1999/03/04");
        System.out.println(myBean1);

        System.out.println("----------------------------------------");
        // 利用反射原理(Field字段),为 bean 的属性赋值
        MyBean myBean2 = new MyBean();
        DirectFieldAccessor accessor = new DirectFieldAccessor(myBean2);
        accessor.setPropertyValue("a", "10");
        accessor.setPropertyValue("b", "hello");
        accessor.setPropertyValue("c", "1999/03/04");
        System.out.println(myBean2);

        System.out.println("----------------------------------------");
        // 执行数据绑定(要绑定的数据封装在 MutablePropertyValues 中)
        MyBean myBean3 = new MyBean();
        // 准备原始数据
        DataBinder dataBinder = new DataBinder(myBean3);
        MutablePropertyValues pvs = new MutablePropertyValues();
        dataBinder.initDirectFieldAccess(); // 如果 Bean 没有提供 Getter/Setter 方法,设置为直接字段访问即可
        pvs.add("a", "10");
        pvs.add("b", "hello");
        pvs.add("c", "1999/03/04");
        dataBinder.bind(pvs);
        System.out.println(myBean3);

        System.out.println("----------------------------------------");
        // 执行数据绑定(要绑定的数据封装在 HttpServletRequest 中)
        MyBean myBean4 = new MyBean();
        // 准备原始数据
        ServletRequestDataBinder servletRequestDataBinder = new ServletRequestDataBinder(myBean4);
        servletRequestDataBinder.initDirectFieldAccess(); // 如果 Bean 没有提供 Getter/Setter 方法,设置为直接字段访问即可
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("a", "10");
        request.setParameter("b", "hello");
        request.setParameter("c", "1999/03/04");
        // 执行数据绑定
        servletRequestDataBinder.bind(request);
        System.out.println(myBean4);
    }
}

结果:
13
Thu Mar 04 00:00:00 CST 1999
----------------------------------------
Test18.MyBean(a=10, b=hello, c=Thu Mar 04 00:00:00 CST 1999)
----------------------------------------
Test18.MyBean(a=10, b=hello, c=Thu Mar 04 00:00:00 CST 1999)
----------------------------------------
Test18.MyBean(a=10, b=hello, c=Thu Mar 04 00:00:00 CST 1999)
----------------------------------------
Test18.MyBean(a=10, b=hello, c=Thu Mar 04 00:00:00 CST 1999)

代码演示2:

public class Test18_2 {

    @Getter
    @Setter
    @ToString
    static class User {
        @DateTimeFormat(pattern = "yyyy|MM|dd") // 使用方法5(默认的ConversionService)需要添加这个注解
        private Date birthday;
        private Address address;
    }

    @Getter
    @Setter
    @ToString
    static class Address {
        private String name;
    }

    public static void main(String[] args) throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1999|01|02");
        request.setParameter("address.name", "西安");

        // 1、用工厂,无转换功能
        System.out.println("----------------------");
        System.out.println(bindToUser1(request));
        // 2、用 @InitBinder 转换           PropertyEditorRegistry PropertyEditor
        System.out.println("----------------------");
        System.out.println(bindToUser2(request));
        // 3、用 ConversionService 转换     ConversionService Formatter
        System.out.println("----------------------");
        System.out.println(bindToUser3(request));
        // 4、同时加了 @InitBinder 和 ConversionService,会优先使用 @InitBinder 的转换器
        System.out.println("----------------------");
        System.out.println(bindToUser4(request));

        // 5、使用默认 ConversionService 转换
        System.out.println("----------------------");
        System.out.println(bindToUser5(request));
    }

    // 1、用工厂,无转换功能
    public static User bindToUser1(HttpServletRequest request) throws Exception {
        User targetBean = new User();
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        return targetBean;
    }

    // 2、用 @InitBinder 转换           PropertyEditorRegistry PropertyEditor
    public static User bindToUser2(HttpServletRequest request) throws Exception {
        User targetBean = new User();
        // 获取 Controller 类上标注有 @InitBinder 的方法,并调用该方法对 Binder 进行自定义处理
        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
        // 将该方法作为创建工厂的参数,之后调用 createBinder() 时会反射调用该方法
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Collections.singletonList(method), null);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        return targetBean;
    }

    static class MyController {
        @InitBinder
        public void aaa(WebDataBinder dataBinder) {
            // 扩展 DataBinder 转换器功能(该方法内部使用适配器 FormatterPropertyEditorAdapter 适配成 PropertyEditor 来使用的)
            dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));
        }
    }

    @Slf4j
    static class MyDateFormatter implements Formatter<Date> {
        private final String desc;

        public MyDateFormatter(String desc) {
            this.desc = desc;
        }

        @Override
        public Date parse(String text, Locale locale) throws ParseException {
            log.info(">>>>>> 进入了:{}", desc);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
            return sdf.parse(text);
        }

        @Override
        public String print(Date date, Locale locale) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
            return sdf.format(date);
        }
    }

    // 3、同 ConversionService 转换     ConversionService Formatter
    public static User bindToUser3(MockHttpServletRequest request) throws Exception {
        User targetBean = new User();

        FormattingConversionService conversionService = new FormattingConversionService();
        conversionService.addFormatter(new MyDateFormatter("用 ConversionService 扩展转换功能"));

        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(conversionService);

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        return targetBean;
    }

    // 4、同时加了 @InitBinder 和 ConversionService
    public static User bindToUser4(MockHttpServletRequest request) throws Exception {
        User targetBean = new User();

        // 准备初始化器
        FormattingConversionService conversionService = new FormattingConversionService();
        conversionService.addFormatter(new MyDateFormatter("用 ConversionService 扩展转换功能"));
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(conversionService);

        // 获取 Controller 类上标注有 @InitBinder 的方法,并调用该方法对 Binder 进行自定义处理
        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));

        // 优先使用 @InitBinder 的转换器
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(Collections.singletonList(method), initializer);

        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        return targetBean;
    }

    // 5、使用默认 ConversionService 转换(需要在User类的日期字段上添加@DateTimeFormat注解)
    public static User bindToUser5(MockHttpServletRequest request) throws Exception {
        User targetBean = new User();

//        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); // 非 SpringBoot 程序用这个
        ApplicationConversionService conversionService = new ApplicationConversionService(); // SpringBoot 程序用这个
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(conversionService);

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);

        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), targetBean, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        return targetBean;
    }
}

结果:
----------------------
16:53:43.650 [main] DEBUG org.springframework.beans.TypeConverterDelegate - Construction via String failed for type [java.util.Date]
org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.Date]: Constructor threw exception; nested exception is java.lang.IllegalArgumentException
	at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:224)
	at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:198)
	at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:590)
	at org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:609)
	at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:458)
	at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:278)
	at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:266)
	at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:104)
	at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:889)
	at org.springframework.validation.DataBinder.doBind(DataBinder.java:780)
	at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:207)
	at org.springframework.validation.DataBinder.bind(DataBinder.java:765)
	at com.clp.test18.Test18_2.bindToUser1(Test18_2.java:75)
	at com.clp.test18.Test18_2.main(Test18_2.java:54)
Caused by: java.lang.IllegalArgumentException: null
	at java.util.Date.parse(Date.java:617)
	at java.util.Date.<init>(Date.java:274)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:211)
	... 13 common frames omitted
Test18_2.User(birthday=null, address=Test18_2.Address(name=西安))
----------------------
16:53:43.702 [main] INFO com.clp.test18.Test18_2$MyDateFormatter - >>>>>> 进入了:用 @InitBinder 方式扩展的
Test18_2.User(birthday=Sat Jan 02 00:00:00 CST 1999, address=Test18_2.Address(name=西安))
----------------------
16:53:43.712 [main] INFO com.clp.test18.Test18_2$MyDateFormatter - >>>>>> 进入了:用 ConversionService 扩展转换功能
Test18_2.User(birthday=Sat Jan 02 00:00:00 CST 1999, address=Test18_2.Address(name=西安))
----------------------
16:53:43.714 [main] INFO com.clp.test18.Test18_2$MyDateFormatter - >>>>>> 进入了:用 @InitBinder 方式扩展的
Test18_2.User(birthday=Sat Jan 02 00:00:00 CST 1999, address=Test18_2.Address(name=西安))
----------------------
Test18_2.User(birthday=Sat Jan 02 00:00:00 CST 1999, address=Test18_2.Address(name=西安))

11.4、Spring 提供的泛型操作技巧

代码演示:

public class Test18_3 {

    static class Student {}

    static class BaseDao<T> {}

    static class StudentDao extends BaseDao<Student> {}

    public static void main(String[] args) {
        // 1、jdk api
        Type type = StudentDao.class.getGenericSuperclass();// 获取带有泛型信息的父类
        System.out.println("---------------------");
        System.out.println(type);
        // 因为继承的父类不一定有泛型信息(如 MyList extends ArrayList,但没有指定泛型参数),所以需要进行判断
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();// 获取真实的参数化类型
            System.out.println("---------------------");
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }

        // 2、spring api
        // 2.1、参数1:本类;参数2:泛型父类(默认只获取一个泛型参数类型)
        Class<?> clz = GenericTypeResolver.resolveTypeArgument(StudentDao.class, BaseDao.class);
        System.out.println("---------------------");
        System.out.println(clz);
        // 2.2、参数1:本类;参数2:泛型父类(获取所有的泛型参数类型)
        Class<?>[] classes = GenericTypeResolver.resolveTypeArguments(StudentDao.class, BaseDao.class);
        System.out.println("---------------------");
        for (Class<?> aClass : classes) {
            System.out.println(aClass);
        }
    }
}

结果:
---------------------
com.clp.test18.Test18_3$BaseDao<com.clp.test18.Test18_3$Student>
---------------------
class com.clp.test18.Test18_3$Student
---------------------
class com.clp.test18.Test18_3$Student
---------------------
class com.clp.test18.Test18_3$Student

11.5、@ControllerAdvice & @InitBinder

代码演示:

@Slf4j
public class MyDateFormatter implements Formatter<Date> {
    private final String desc;

    public MyDateFormatter(String desc) {
        this.desc = desc;
    }

    @Override
    public Date parse(String text, Locale locale) throws ParseException {
        log.info(">>>>>> 进入了:{}", desc);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.parse(text);
    }

    @Override
    public String print(Date date, Locale locale) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.format(date);
    }
}

@Configuration
public class WebConfig {
    @ControllerAdvice // 该注解表示对 Controller 进行增强(但是没有使用代理)
    static class MyControllerAdvice {
        @InitBinder // @InitBinder 注解添加方式1:在 @ControllerAdvice 里添加 ,用于补充自定的类型转换器,作用范围是全局
        public void binder3(WebDataBinder webDataBinder) {
            webDataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器"));
        }

        // @ControllerAdvice 中可添加的其他注解:
//        @ExceptionHandler // 异常处理器
//        @ModelAttribute // 会将注解的模型数据补充到 controller 的执行过程中
    }

    @Controller
    static class Controller1 {
        @InitBinder // @InitBinder 注解添加方式2:直接在 @Controller 里添加 ,用于补充自定的类型转换器,作用范围是局部
        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() {}
    }
}

@Slf4j
public class Test19Application {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        /**
         * @InitBinder 的来源有2个:
         *      1、@ControllerAdvice 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 在初始化时解析并记录;
         *      2、@Controller 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 会在控制器方法首次执行时解析并记录
         */
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        // 不放入容器,而是手动调用 , handlerAdapter 会先解析 @InitBinder,再执行控制器方法
        RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
        handlerAdapter.setApplicationContext(context); // ApplicationContextAware 接口的方法,如果这个 handlerAdapter 放入容器中,会自动执行该方法
        handlerAdapter.afterPropertiesSet(); // InitializingBean 接口的方法,如果这个 handlerAdapter 放入容器中,会自动执行该方法

        log.info("------------------------------");
        log.info("1、刚开始...");
        showBindMethods(handlerAdapter);

        Method getDataBinderFactoryMethod = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class);
        getDataBinderFactoryMethod.setAccessible(true);

        log.info("------------------------------");
        log.info("2、模拟调用 Controller1 的 foo() 方法时...");
        getDataBinderFactoryMethod.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo")));
        showBindMethods(handlerAdapter);

        log.info("------------------------------");
        log.info("3、模拟调用 Controller2 的 bar() 方法时...");
        getDataBinderFactoryMethod.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar")));
        showBindMethods(handlerAdapter);

        context.close();
    }

    @SuppressWarnings("unchecked")
    public static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException {
        Field initBinderAdviceCacheField = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
        initBinderAdviceCacheField.setAccessible(true);
        Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCacheField.get(handlerAdapter);
        log.info("全局的 @InitBinder 方法:{}", globalMap.values().stream().flatMap(methods -> methods.stream().map(method -> method.getName())).collect(Collectors.toList()));

        Field initBinderCacheField = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
        initBinderCacheField.setAccessible(true);
        Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCacheField.get(handlerAdapter);
        log.info("控制器的 @InitBinder 方法:{}", controllerMap.entrySet()
                .stream().flatMap(classSetEntry -> classSetEntry.getValue().stream().map(v -> classSetEntry.getKey().getSimpleName() + "." + v.getName()))
                .collect(Collectors.toList())
        );
    }
}

结果:
10:07:12.909 [main] INFO com.clp.test19.Test19Application - ------------------------------
10:07:12.909 [main] INFO com.clp.test19.Test19Application - 1、刚开始...
10:07:12.911 [main] INFO com.clp.test19.Test19Application - 全局的 @InitBinder 方法:[binder3]
10:07:12.913 [main] INFO com.clp.test19.Test19Application - 控制器的 @InitBinder 方法:[]
10:07:12.915 [main] INFO com.clp.test19.Test19Application - ------------------------------
10:07:12.915 [main] INFO com.clp.test19.Test19Application - 2、模拟调用 Controller1 的 foo() 方法时...
10:07:12.923 [main] INFO com.clp.test19.Test19Application - 全局的 @InitBinder 方法:[binder3]
10:07:12.924 [main] INFO com.clp.test19.Test19Application - 控制器的 @InitBinder 方法:[Controller1.binder1]
10:07:12.924 [main] INFO com.clp.test19.Test19Application - ------------------------------
10:07:12.924 [main] INFO com.clp.test19.Test19Application - 3、模拟调用 Controller2 的 bar() 方法时...
10:07:12.924 [main] INFO com.clp.test19.Test19Application - 全局的 @InitBinder 方法:[binder3]
10:07:12.925 [main] INFO com.clp.test19.Test19Application - 控制器的 @InitBinder 方法:[Controller2.binder22, Controller2.binder21, Controller1.binder1]

12、控制器方法执行流程

图示:

HandlerMethod 需要:

  • bean:即是哪个 Controller;
  • method:即是 Controller 中的哪个方法。

ServletInvocationHandlerMethod 需要:

  • WebDataBinderFactory:负责对象绑定、类型转换;
  • ParameterNameDiscover:负责参数名解析;
  • HandlerMethodArgumentResolverComposite:负责解析参数;
  • HandlerMethodReturnValueHandlerComposite:负责处理返回值。

代码演示:

@Configuration
public class WebConfig {
    @Getter
    @Setter
    @ToString
    static class User {
        private String name;
    }

    @Controller
    static class Controller1 {
        // 局部的(作用于该控制器)
        @ModelAttribute("a") // 模型名
        public String aa() {
            return "bb"; // 模型值
        }

        @ResponseStatus(HttpStatus.OK)
        public ModelAndView foo(@ModelAttribute("user1") User user) {
            System.out.println("foo");
            return null;
        }
    }

    @ControllerAdvice
    static class MyControllerAdvice {
        // 全局的
        @ModelAttribute("a") // 模型名
        public String aa() {
            return "aa"; // 模型值
        }
    }
}

public class Test20Application {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);

        // 模拟请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name", "张三");

        // 现在可以通过 ServletInvocationHandlerMethod 把这些整合在一起,并完成控制器方法的调用
        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();
        // 调用,加了@ModelAttribute注解的方法参数会放在 container 的 model 中
        handlerMethod.invokeAndHandle(new ServletWebRequest(request), container, null);
        System.out.println(container.getModel());

        context.close();
    }

    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(Arrays.asList(new MappingJackson2HttpMessageConverter())),
                new ServletModelAttributeMethodProcessor(true),
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
        );
        return composite;
    }
}

结果:
...
foo
{user1=WebConfig.User(name=张三), org.springframework.validation.BindingResult.user1=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
...

13、返回值处理器

13.1、代码示例

代码演示:

@Configuration
public class WebConfig {
    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setDefaultEncoding("utf-8");
        configurer.setTemplateLoaderPath("classpath:template");
        return configurer;
    }

    // FreeMarkerView 在借助 Spring 初始化时,会要求 web 环境才会走 setConfiguration(),这里想办法去掉了 web 环境的约束
    @Bean
    public FreeMarkerViewResolver viewResolver(FreeMarkerConfigurer configurer) {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver() {
            @Override
            protected AbstractUrlBasedView instantiateView() {
                FreeMarkerView view = new FreeMarkerView() {
                    @Override
                    protected boolean isContextRequired() {
                        return false;
                    }
                };
                view.setConfiguration(configurer.getConfiguration());
                return view;
            }
        };
        resolver.setContentType("text/html;charset=utf-8");
        resolver.setPrefix("/");
        resolver.setSuffix(".ftl");
        resolver.setExposeSpringMacroHelpers(false);
        return resolver;
    }
}

public class Test21Application {

    @Getter
    @Setter
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    static class User {
        private String name;
        private int age;
    }

    @Slf4j
    static class Controller {
        // 返回值处理器:ModelAndViewMethodReturnValueHandler
        public ModelAndView test1() {
            log.info("test1()");
            ModelAndView mav = new ModelAndView("view1");
            mav.addObject("name", "张三");
            return mav;
        }

        // 返回值处理器:ViewNameMethodReturnValueHandler
        public String test2() {
            log.info("test2()");
            return "view2"; // 返回的是视图名称
        }

        // 返回值处理器:ServletModelAttributeMethodProcessor(annotationNotRequired=false)
        @ModelAttribute
        @RequestMapping("/test3") // 没有返回视图,则默认视图名称为请求路径名
        public User test3() {
            log.info("test3");
            return new User("李四", 20); // 返回模型对象
        }

        // 返回值处理器:ServletModelAttributeMethodProcessor(annotationNotRequired=true)
        public User test4() {
            log.info("test4()");
            return new User("王五", 30);
        }

        // 返回值处理器:HttpEntityMethodProcessor
        public HttpEntity<User> test5() {
            log.info("test5()");
            return new HttpEntity<>(new User("赵六", 40)); // 会将User对象转为json格式写在响应体中
        }

        // 返回值处理器:HttpHeadersReturnValueHandler
        public HttpHeaders test6() {
            log.info("test6()");
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "text/html");
            return headers; // 会将 header 作为响应头
        }

        // 返回值处理器:RequestResponseBodyMethodProcessor
        @ResponseBody
        public User test7() {
            log.info("test7()");
            return new User("钱七", 50); // 会将User对象转为json格式写在响应体中
        }
    }

    // 提供视图
    private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
                                   ServletWebRequest webRequest) throws Exception {
    }

    // 提供返回值处理器
    private static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
        composite.addHandler(new HttpEntityMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new HttpHeadersReturnValueHandler());
        composite.addHandler(new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
        return composite;
    }

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);

        test1(context);

        context.close();
    }

    private static void test1(AnnotationConfigApplicationContext context) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());

        // 获取 Controller 方法调用的返回值、 HandlerMethod
        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();

        // 1、测试返回值类型为 ModelAndView
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
        // 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }

    private static void test2(AnnotationConfigApplicationContext context) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());

        // 获取 Controller 方法调用的返回值、 HandlerMethod
        Method method = Controller.class.getMethod("test2");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
        // 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }

    private static void test3(AnnotationConfigApplicationContext context) throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setRequestURI("/test3");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());

        // 获取 Controller 方法调用的返回值、 HandlerMethod
        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 = getReturnValueHandlers();
        // 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }

    private static void test4(AnnotationConfigApplicationContext context) throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setRequestURI("/test4");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());

        // 获取 Controller 方法调用的返回值、 HandlerMethod
        Method method = Controller.class.getMethod("test4");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();

        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandlers();
        // 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }

    private static void test5(AnnotationConfigApplicationContext context) throws Exception {
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), response);

        // 获取 Controller 方法调用的返回值、 HandlerMethod
        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 = getReturnValueHandlers();
        // 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest); // 渲染视图
            } else {
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }

    private static void test6(AnnotationConfigApplicationContext context) throws Exception {
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), response);

        // 获取 Controller 方法调用的返回值、 HandlerMethod
        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 = getReturnValueHandlers();
        // 检查是否支持此类型的返回值(根据返回值类型、方法上的注解等信息来判断)
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue, handlerMethod.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest); // 渲染视图
            } else {
                for (String headerName : response.getHeaderNames()) {
                    System.out.println(headerName + "=" + response.getHeader(headerName));
                }
            }
        }
    }
}

13.2、MessageConverter

代码演示:

/**
 * MessageConverter 的作用:@ResponseBody 是 对应的返回值处理器 解析的,但具体转换工作是 MessageConverter 做的
 * 如何选择 MediaType:
 *      - 首先看 @RequestMapping 上有没有指定(produces属性)
 *      - 其次看 request 的 Accept 上有没有指定
 *      - 最后按 MessageConverter 的顺序,谁能谁先转换
 */
public class Test22Application {

    @Getter
    @Setter
    @ToString
    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;
        }
    }

    /**
     *  将 java对象 转成 json 格式消息
     *      {"name":"张三", "age":18}
     * @throws IOException
     */
    public static void test1() throws IOException {
        MockHttpOutputMessage message = new MockHttpOutputMessage();
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {
            // 把 java 对象转换成 json字符串 并写入到 message 中
            converter.write(new User("张三", 18), MediaType.APPLICATION_JSON, message);
            System.out.println(message.getBodyAsString());
        }
    }

    /**
     * 将 java对象 转成 xml 格式消息
     *      <User>
     *          <name>李四</name>
     *          <age>19</age>
     *      </User>
     * @throws IOException
     */
    public static void test2() throws IOException {
        MockHttpOutputMessage message = new MockHttpOutputMessage();
        MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
        if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) {
            // 把 java 对象转换成 json字符串 并写入到 message 中
            converter.write(new User("李四", 19), MediaType.APPLICATION_XML, message);
            System.out.println(message.getBodyAsString());
        }
    }

    /**
     * 将 消息 转成 Java对象
     */
    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.out.println(read);
        }
    }

    /**
     * 将 java对象 转成
     */
    public static void test4() {}

    @ResponseBody
    public User user() {
        return null;
    }

    public static void main(String[] args) throws IOException {
        System.out.println("---------------------");
        test1();
//        System.out.println("---------------------");
//        test2();
        System.out.println("---------------------");
        test3();
    }
}

结果:
---------------------
{"name":"张三","age":18}
---------------------
Test22Application.User(name=李四, age=20)

13.3、@ControllerAdvice 之 @ResponseBodyAdvice

代码演示:

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Result<T> {
    private int code;
    private String msg;
    private T data;

    @JsonCreator
    private Result(@JsonProperty("code") int code, @JsonProperty("data") T data) {
        this.code = code;
        this.data = data;
    }

    private Result(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public static <T> Result<T> ok() {
        return new Result<>(200, null);
    }

    public static <T> Result<T> ok(T data) {
        return new Result<>(200, data);
    }

    public static <T> Result<T> error(String msg) {
        return new Result<>(500, "服务器内部错误:" + msg);
    }
}

@Configuration
public class WebConfig {

    @Getter
    @Setter
    @ToString
    public static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

//    @Controller
//    @ResponseBody // 位置1
    @RestController // 上面两个注解的合并
    public static class MyController {
//        @ResponseBody // 位置2
        public User user() {
            return new User("王五", 18);
        }
    }

    @ControllerAdvice
    public static class MyControllerAdvice implements ResponseBodyAdvice<Object> {
        // 满足该条件才转换
        @Override
        public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
            // 检查类上或方法上是否有 @ResponseBody 注解
            if (returnType.getMethodAnnotation(ResponseBody.class) != null
                    // 从 类上的注解以及该注解上的注解找到 @ResponseBody 注解
                    || AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) {
                return true;
            }
            return false;
        }

        // 执行转换逻辑,将 User或其他类型 封装为 Result<>
        @Override
        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.ok(body);
        }
    }
}

public class Test23Application {
    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(new ArrayList<>(), null));
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));
        handlerMethod.setHandlerMethodReturnValueHandlers(getReturnValueHandlers());

        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        handlerMethod.invokeAndHandle(new ServletWebRequest(request, response), new ModelAndViewContainer());

        System.out.println("----------------------");
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));

        context.close();
    }

    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(Arrays.asList(new MappingJackson2HttpMessageConverter())),
                new ServletModelAttributeMethodProcessor(true),
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
        );
        return composite;
    }

    // 提供返回值处理器
    private static HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
        composite.addHandler(new HttpEntityMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new HttpHeadersReturnValueHandler());
        composite.addHandler(new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
        return composite;
    }
}

结果:
...
{"code":200,"data":{"name":"王五","age":18}}
...

14、异常处理

14.1、ExceptionHandlerExceptionResolver

代码演示:

/**
 *  1、ExceptionHandlerExceptionResolver 
 *      能够重用参数解析器、返回值处理器,实现组件重用
 *      能够支持嵌套异常
 */
public class Test24Application {

    static class Controller1 {
        public void foo() {
        }

        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(ArithmeticException e) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("error", e.getMessage());
            return map;
        }
    }

    static class Controller2 {
        public void foo() {
        }

        @ExceptionHandler
        public ModelAndView handle(ArithmeticException e) {
            return new ModelAndView("test2", Collections.singletonMap("error", e.getMessage()));
        }
    }

    static class Controller3 {
        public void foo() {
        }

        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(IOException e) {
            return Collections.singletonMap("error", e.getMessage());
        }
    }

    static class Controller4 {
        public void foo() {
        }

        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(Exception e, HttpServletRequest request) {
            System.out.println(request);
            return Collections.singletonMap("error", e.getMessage());
        }
    }

    public static void main(String[] args) throws NoSuchMethodException {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter())); // 添加 json 消息转换器
        resolver.afterPropertiesSet(); // 添加一些默认的解析器(跟handlerAdapter使用相同类型的解析器)

        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ArithmeticException single_ex = new ArithmeticException("被0除 异常");
        Exception nested_ex = new Exception("e1", new RuntimeException("e2", new IOException("e3")));

        // 测试 json
        System.out.println("---------------------------------------");
//        testJson(resolver, request, response, single_ex);

        // 测试 ModelAndView
        System.out.println("---------------------------------------");
//        testModelAndView(resolver, request, response, single_ex);

        // 测试嵌套异常
        System.out.println("---------------------------------------");
//        testNestedException(resolver, request, response, nested_ex);

        // 测试异常处理方法参数解析
        System.out.println("---------------------------------------");
        testExceptionHandlerMethodArguments(resolver, request, response, nested_ex);
    }

    private static void testJson(ExceptionHandlerExceptionResolver resolver, MockHttpServletRequest request,
                                 MockHttpServletResponse response, ArithmeticException single_ex) throws NoSuchMethodException {
        HandlerMethod handlerMethod1 = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));
        resolver.resolveException(request, response, handlerMethod1, single_ex);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    private static void testModelAndView(ExceptionHandlerExceptionResolver resolver, MockHttpServletRequest request,
                                         MockHttpServletResponse response, ArithmeticException single_ex) throws NoSuchMethodException {
        HandlerMethod handlerMethod2 = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo"));
        ModelAndView modelAndView = resolver.resolveException(request, response, handlerMethod2, single_ex);
        System.out.println(modelAndView.getModel());
        System.out.println(modelAndView.getViewName());
    }

    private static void testNestedException(ExceptionHandlerExceptionResolver resolver, MockHttpServletRequest request,
                                            MockHttpServletResponse response, Exception nested_ex) throws NoSuchMethodException {
        HandlerMethod handlerMethod3 = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));
        resolver.resolveException(request, response, handlerMethod3, nested_ex);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    private static void testExceptionHandlerMethodArguments(ExceptionHandlerExceptionResolver resolver,
                                                            MockHttpServletRequest request, MockHttpServletResponse response,
                                                            Exception nested_ex) throws NoSuchMethodException {
        HandlerMethod handlerMethod4 = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));
        resolver.resolveException(request, response, handlerMethod4, nested_ex);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }
}

结果:
---------------------------------------
---------------------------------------
---------------------------------------
---------------------------------------
...
{"error":"e1"}

14.2、ControllerAdvice 之 @ExceptionHandler

代码演示:

@Configuration
public class WebConfig {

    @Bean
    public ExceptionHandlerExceptionResolver resolver() {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
        return resolver;
    }

    @ControllerAdvice // 可以提供全局的异常处理方法
    static class MyControllerAdvice {
        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(Exception e) {
            return Collections.singletonMap("error", e.getMessage());
        }
    }
}

public class Test25Application {

    static class Controller5 {
        public void foo() {}
    }

    public static void main(String[] args) throws NoSuchMethodException {
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();

//        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
//        resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
//        resolver.afterPropertiesSet();
        // 上面3行改写成下面,交给容器处理:
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);

        Exception e1 = new Exception("e1");

        HandlerMethod handlerMethod = new HandlerMethod(new Controller5(), Controller5.class.getMethod("foo"));
        resolver.resolveException(request, response, handlerMethod, e1);

        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }
}

结果:
...
{"error":"e1"}

14.3、Tomcat 异常处理

代码演示:

@Configuration
public class WebConfig {
    // ------------------------ web 开发三件套--------------------------
    @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, "/");
        return registrationBean;
    }

    // ----------------------------------------------------------

    @Bean // 解析 @RequestMapping,进行路径映射
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    @Bean // 注意默认的 RequestMappingHandlerAdapter 不会带有 jackson 转换器,需要我们手动添加
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
        adapter.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter())); // 手动添加
        return adapter;
    }

    @Bean // 这个 bean 可以修改 服务器的默认错误地址,会使用请求转发 forward 跳转至 /error 请求
    public ErrorPageRegistrar errorPageRegistrar() {
        return new ErrorPageRegistrar() {
            @Override
            public void registerErrorPages(ErrorPageRegistry registry) {
                registry.addErrorPages(new ErrorPage("/error"));
            }
        };
    }

    @Bean
    public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
        // 会在 TomcatServletWebServerFactory 创建后,初始化之前 执行这个bean,并 回调 ErrorPageRegistrar 的 registerErrorPages()方法
        return new ErrorPageRegistrarBeanPostProcessor();
    }

    @Controller
    public static class MyController {
        @RequestMapping("test")
        public ModelAndView test() {
            int i = 1 / 0;
            return null;
        }

        // 提供错误跳转请求处理,这里不使用自定义的,因为SpringBoot 给我们提供了现成的,叫 BasicErrorController
//        @RequestMapping("error")
//        @ResponseBody
//        public Map<String, Object> error(HttpServletRequest request) {
//            Throwable e = ((Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
//            return Collections.singletonMap("error", e.getMessage());
//        }
    }

    @Bean
    public BasicErrorController basicErrorController() {
        ErrorProperties errorProperties = new ErrorProperties();
        errorProperties.setIncludeException(true); // 添加错误属性 - includeException
        return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
    }

    // BasicErrorController 需要提供额外的2个对象: View 和 ViewResolver
    @Bean
    public View error() {
        return new View() {
            @Override
            public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
                System.out.println(model);
                response.setContentType("text/html;charset=utf-8");
                response.getWriter().print(
                        "<h3>服务器内部错误</h3>"
                );
            }
        };
    }

    @Bean // 提供基于 bean 名称的 view 解析器,会解析到上面的 view bean
    public ViewResolver viewResolver() {
        return new BeanNameViewResolver();
    }
}

public class Test26Application {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        handlerMapping.getHandlerMethods().forEach((RequestMappingInfo i, HandlerMethod m) -> {
            System.out.println("映射路径:" + i + "\t方法信息:" + m);
        });
    }
}

结果(浏览器访问 localhost:8080/test):
服务器内部错误

15、HandlerMapping 与 HandlerAdapter

15.1、BeanNameUrlHandlerMapping 与 SimpleControllerHandlerAdapter

 代码演示:

@Configuration
public class WebConfig {
    // ------------------------ web 开发三件套--------------------------
    @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, "/");
        return registrationBean;
    }

    // ----------------------------------------------------------
    // 比如请求url路径为:/c1,则会在容器中找一个名为 "/c1" 的 bean 来处理这个请求,要求这个bean必须以 / 开头,不然就是普通的bean
    // 这里不使用这个,使用我们自定义的 MyHandlerMapping 实现相同功能
//    @Bean
//    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
//        return new BeanNameUrlHandlerMapping();
//    }

    // 自定义 HandlerMapping:收集所有以 / 开头的 bean
    @Component
    static class MyHandlerMapping implements HandlerMapping {
        @Autowired // 1、注入容器对象
        private ApplicationContext context;

        private Map<String, Controller> controllerMap;

        @PostConstruct // 2、初始化
        public void init() {
            // 查找所有实现 Controller 接口的类
            controllerMap = context.getBeansOfType(Controller.class).entrySet().stream()
                    .filter(e -> e.getKey().startsWith("/"))
                    .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
            System.out.println(controllerMap);
        }

        @Override
        public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            String key = request.getRequestURI();
            Controller controller = controllerMap.get(key);
            if (controller == null) return null; // 返回null,会抛出404异常
            return new HandlerExecutionChain(controller);
        }
    }

    // 这里不使用这个 Adapter,而是使用我们自定义的 MyHandlerAdapter
//    @Bean
//    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
//        return new SimpleControllerHandlerAdapter();
//    }

    @Component
    public static class MyHandlerAdapter implements HandlerAdapter {
        // 该 handlerAdapter 是否支持这个 handler(这里需要支持 Controller)
        @Override
        public boolean supports(Object handler) {
            return handler instanceof Controller;
        }

        @Override
        public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (handler instanceof Controller) {
                Controller controller = (Controller) handler;
                controller.handleRequest(request, response);
            }
            return null; // 表示不走视图渲染流程
        }

        @Override
        public long getLastModified(HttpServletRequest request, Object handler) {
            return 0;
        }
    }

    // 作为控制器的类 还需要实现 Controller 接口
    @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") // 这里 c2 没有加 / ,因此不会找到 Controller2
    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 new Controller() {
            @Override
            public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
                response.getWriter().print("this is c3");
                return null;
            }
        };
    }
}

/**
 * BeanNameUrlHandlerMapping:以 / 开头的 bean 的名字会被当做映射路径
 * 这些 bean 本身当做 handler,要求实现 Controller 接口
 * SimpleControllerHandlerAdapter:调用 handler
 *
 * SimpleControllerHandlerAdapter 与 RequestMappingHandlerAdapter 对比:
 *      1、RequestMappingHandlerAdapter:以 @RequestMapping 作为映射路径
 *      2、控制器的具体方法会被当作 handler
 *      3、RequestMappingHandlerAdapter:调用 handler
 */
public class Test27Application {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);


    }
}

结果(浏览区访问 localhost:8080/c1):
this is c1

15.2、RouterFunctionMapping 与 HandlerFunctionAdapter

  • RouterFunctionMapping:收集所有 RouterFunction,它包括两部分:① RequestPredicate 设置映射条件;② HandlerFunction 包含处理逻辑。
  • 请求到达,根据映射条件找到 HandlerFunction,即 handler;
  • HandlerFunctionAdapter:调用 handler。

代码演示:

@Configuration
public class WebConfig {
    // ------------------------ web 开发三件套--------------------------
    @Bean // 内嵌 web 容器工厂
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    @Bean // 创建 DispatcherServlet
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    @Bean // 注册 DispatcherServlet,SpringMVC 的入口
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        return registrationBean;
    }

    // ----------------------------------------------------------
    // 会收集所有 RouterFunction<> 并记录下来。当请求来了之后,会根据请求路径找到匹配的 RouterFunction<>
    // 找到之后,交给 HandlerFunctionAdapter 来调用处理器 HandlerFunction,执行完之后将相应返回给浏览器
    @Bean
    public RouterFunctionMapping routerFunctionMapping() {
        return new RouterFunctionMapping();
    }

    // 用来调用处理器 HandlerFunction
    @Bean
    public HandlerFunctionAdapter handlerFunctionAdapter() {
        return new HandlerFunctionAdapter();
    }

    @Bean
    public RouterFunction<ServerResponse> r1() {
        // 表示如果请求路径为 /r1 ,则由后面的 HandlerFunction<> 来处理请求
        return RouterFunctions.route(RequestPredicates.GET("/r1"), new HandlerFunction<ServerResponse>() {
            @Override
            public ServerResponse handle(ServerRequest request) throws Exception {
                return ServerResponse.ok().body("this is r1");
            }
        });
    }

    @Bean
    public RouterFunction<ServerResponse> r2() {
        // 表示如果请求路径为 /r2 ,则由后面的 HandlerFunction<> 来处理请求
        return RouterFunctions.route(RequestPredicates.GET("/r2"), new HandlerFunction<ServerResponse>() {
            @Override
            public ServerResponse handle(ServerRequest request) throws Exception {
                return ServerResponse.ok().body("this is r2");
            }
        });
    }
}

/**
 * 函数式控制器:
 *      1、RouterFunctionMapping:通过 RequestPredicate 映射路径
 *      2、handler 要实现 HandlerFunction 接口
 *      3、HandlerFunctionAdapter:调用 handler
 * RequestHandlerMapping 与 HandlerFunctionAdapter 对比:
 *      1、RequestMappingHandlerMapping:以 @RequestMapping 作为映射路径
 *      2、控制器的具体方法会被当做 handler
 *      3、RequestMappingHandlerAdapter:调用 handler
 */
public class Test28Application {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

结果(浏览器访问 localhost:8080/r1):
this is r1

15.3、SimpleUrlHandlerMapping 与 HttpRequestHandlerAdapter

静态资源处理:

  • SimpleUrlHandlerMapping 做映射;
  • ResourceHttpRequestHandler 作为处理器处理静态资源;
  • HttpRequestHandlerAdapter 调用处理器。

代码演示:

@Configuration
public class WebConfig {
    // ------------------------ web 开发三件套--------------------------
    @Bean // 内嵌 web 容器工厂
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    @Bean // 创建 DispatcherServlet
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    @Bean // 注册 DispatcherServlet,SpringMVC 的入口
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        return registrationBean;
    }

    // ----------------------------------------------------------
    @Bean // 这个类没有实现 InitializingBean 接口,没法初始化,需要我们手动添加 ResourceHttpRequestHandler
    public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {
        SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
        Map<String, ResourceHttpRequestHandler> map = context.getBeansOfType(ResourceHttpRequestHandler.class);
        // 记录所有的静态资源路径和处理器映射关系
        handlerMapping.setUrlMap(map);
        return handlerMapping;
    }

    @Bean
    public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
        return new HttpRequestHandlerAdapter();
    }

    @Bean("/**") // 访问 /index.html 或者 /some-page.html 都会匹配到这个处理器
    public ResourceHttpRequestHandler handler1() {
        ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
        handler.setLocations(Arrays.asList(new ClassPathResource("static/")));
        // 添加资源处理器,所有解析器形成责任链(依次调用这些解析器)
        handler.setResourceResolvers(Arrays.asList(
                new CachingResourceResolver(new ConcurrentMapCache("cache1")), // 读取资源时可以加入缓存
                new EncodedResourceResolver(), // 可以读压缩资源
                new PathResourceResolver() // 类路径资源解析器
        ));
        return handler;
    }

    @Bean("/img/**") // 访问 /img/1.jpg 、 /img/2.jpg、... 都会匹配到这个处理器
    public ResourceHttpRequestHandler handler2() {
        ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
        handler.setLocations(Arrays.asList(new ClassPathResource("images/")));
        return handler;
    }

//    // 创建一个欢迎页映射器(SpringBoot提供)
//    @Bean
//    public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
//        Resource resource = applicationContext.getResource("classpath:static/index.html");
//        return new WelcomePageHandlerMapping(null, applicationContext, resource, "/**");
//        // 会生成一个处理器,实现了 Controller 接口,由 SimpleControllerHandlerAdapter 来执行
//    }
//
//    @Bean
//    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
//        return new SimpleControllerHandlerAdapter();
//    }

    // 需要我们自己提供压缩文件
    @PostConstruct
    @SuppressWarnings("all")
    public void initGzip() throws IOException {
        ClassPathResource 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.getPath() + ".gz"))) {
                byte[] bytes = new byte[8 * 1024];
                int len;
                while((len = fis.read(bytes)) != -1) {
                    fos.write(bytes, 0, len);
                }
            }
        }
    }
}

public class Test29Application {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

15.4、总结

HandlerMapping 负责建立请求与控制器之间的关系(有顺序问题,顺序从上到下,上一个映射器无法处理则交给下一个):

  1. RequestMappingHandlerMapping:与 @RequestMapping 匹配;
  2. WelcomePageHandleMapping: / 匹配欢迎页;
  3. BeanNameHandlerMapping:与 Bean 的名字匹配,以 / 开头;
  4. RouterFunctionMapping:函数式 RequestPredicate, HandlerFunction;
  5. SimpleUrlHandlerMapping:静态资源,如通配符 /**、/img/** 。

HandlerAdapter 负责实现对各种各样的 handler 的适配调用(典型适配器模式体现):

  • RequestMappingHandlerAdapter 处理:@RequestMapping 方法。参数解析器、返回值处理器体现了组合模式。
  • SimpleControllerHandlerAdapter 处理:Controller 接口。
  • HandlerFunctionAdapter 处理:HandlerFunction 函数式接口。
  • HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口(静态资源处理)。

ResourceHttpRequestHandler.setResourceResolvers() 这是典型责任链模式体现。

16、MVC 处理流程

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

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

- 路径:默认映射路径为 / ,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器。例外:jsp 不会匹配到 DispatcherServlet(jsp资源匹配优先级更高)。

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

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

  • HandlerMappings:初始化时记录映射关系;
  • HandlerAdapter:初始化时准备参数解析器、返回值处理器、消息转换器;
  • HandlerExceptionResolver:初始化时准备参数解析器、返回值处理器、消息转换器;
  • ViewResolver。

2、DispatcherServlet 会利用 HandlerMapping 进行路径匹配,找到 @RequestMapping("/hello") 对应的控制器方法。

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

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

3、DispatcherServlet 接下来会:

1)调用拦截器的 preHandle() 方法;

2)HandlerAdapter 调用 handle() 方法,准备数据绑定工厂、模型工厂、将 HandlerMethod 完善为 ServletInvocableHandlerMethod,该方法的调用过程如下:

  1. @ControllerAdvice 增强1:补充模型数据;
  2. @ControllerAdvice 增强2:补充自定义类型转换器;
  3. 使用 HandlerMethodArgumentResovler 准备参数;
  4. @ControllerAdvice 增强3:RequestBody 增强;
  5. 调用 ServletInvocationHandlerMethod;
  6. 使用 HandlerMethodReturnValueHandler 处理返回值:① 如果返回的 ModelAndView 为 null,不走第 4) 步视图解析及渲染过程(例如,标注了 @ResponseBody 的控制器方法,调用 HttpMessageConverter 来讲结果转换为 json,这时返回的 ModelAndView 就为 null);② 如果返回的 ModelAndView 不为 null,会在第 4) 步走视图解析及渲染流程;③ ControllerAdvice 增强4:ResponseBody 曾增强。

3)调用拦截器的 postHandle() 方法;

4)处理异常或视图渲染:

  • 如果 第 1) 步 至 第 3) 步 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程:@ControllerAdvice 增强5:@ExceptionHandler 异常处理;
  • 如果正常,走视图解析及渲染流程。

5)调用拦截器的 afterCompletion() 方法。

posted on 2023-04-23 15:12  啊噢1231  阅读(49)  评论(0编辑  收藏  举报

导航

回到顶部