原创

Spring框架学习-Spring MVC


Spring框架学习-Spring MVC

1.DispatcherServlet

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.List;
import java.util.Map;

/
 * 测试Spring MVC中的DispatcherServlet
 *
 * @author lzlg
 * 2023/3/15 20:08
 */
public class TestSpringDispatcherServlet {
    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context
                = new AnnotationConfigServletWebServerApplicationContext(MyWebConfig.class);
        // handlerMapping处理请求路径和处理请求的处理器的映射关系,初始化就创建了映射关系
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 获取请求路径和请求处理方法的结果Map
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k, v) -> {
            System.out.println(k + "==" + v);
        });

        MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.PUT.name(), "/test4");
        request.addParameter("name", "李四");
        request.addHeader("token", "用户令牌");

        MockHttpServletResponse response = new MockHttpServletResponse();
        // 获取调用链,不仅有HandlerMethod,还有拦截器等等
        HandlerExecutionChain chain = handlerMapping.getHandler(request);
//        System.out.println(chain);
        System.out.println("<><><><><><><><><><><><><><><><><><><><><><>");
        // 处理器适配器,用于调用控制器方法
        MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
        // 调用控制器的方法
        handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());

        String content = response.getContentAsString();
        System.out.println(content);

    }

    /
     * 测试参数解析器和返回值解析器
     */
    private static void testArgAndReturnResolver(MyRequestMappingHandlerAdapter handlerAdapter) {
        // 参数解析器
        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);
        }
    }
}

import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.Collections;

/
 * @author lzlg
 * 2023/3/15 20:09
 */
@Configuration
@ComponentScan
// 导入配置文件
@PropertySource("classpath:application.properties")
// 启用配置类
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class MyWebConfig {
    // 1.注册Servlet容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactory(serverProperties.getPort());
    }

    // 2.注册DispatcherServlet
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    // 3.关联DispatcherServlet和Servlet容器
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(
                dispatcherServlet, "/");
        // 设置是否直接加载DispatcherServlet,默认值是-1(懒加载)
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }

    /
     * RequestMappingHandlerMapping用于生成路径和请求处理器的映射关系
     */
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    @Bean
    public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
        // 设置自定义参数解析器
        TokenArgumentResolver argumentResolver = new TokenArgumentResolver();
        handlerAdapter.setCustomArgumentResolvers(Collections.singletonList(argumentResolver));
        // 设置自定义返回值解析器
        YamlReturnValueResolver returnValueResolver = new YamlReturnValueResolver();
        handlerAdapter.setCustomReturnValueHandlers(Collections.singletonList(returnValueResolver));
        return handlerAdapter;
    }
}

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

/
 * @author lzlg
 * 2023/3/15 20:54
 */
@Slf4j
@Controller
public class TestController {

    @GetMapping("/test1")
    public ModelAndView test1() {
        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")
    @Yaml
    public User test4() {
        log.info("test4()");
        return new User("张三", 32);
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class User {
        private String name;
        private Integer age;
    }
}

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/
 * 自定义请求处理适配器
 *
 * @author lzlg
 * 2023/3/15 21:13
 */
public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {

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

自定义参数和返回值解析器:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/
 * 自定义注解
 *
 * @author lzlg
 * 2023/3/15 21:20
 */
@Target(value = ElementType.PARAMETER)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Token {
}

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

/
 * 自定义参数解析器,需将自定义的参数解析器加入到HandlerAdapter中
 *
 * @author lzlg
 * 2023/3/15 21:26
 */
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 {
        // 直接从请求头中获取token信息
        return webRequest.getHeader("token");
    }
}

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/
 * @author lzlg
 * 2023/3/15 21:46
 */
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Yaml {
}

import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletResponse;

/
 * Yaml返回值解析器
 *
 * @author lzlg
 * 2023/3/15 21:47
 */
public class YamlReturnValueResolver implements HandlerMethodReturnValueHandler {

    /
     * 判断是否进行返回值解析
     */
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        Yaml yaml = returnType.getMethodAnnotation(Yaml.class);
        return yaml != null;
    }

    /
     * 进行返回值解析
     */
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 1.对返回值进行处理为yaml格式字符串
        String str = new org.yaml.snakeyaml.Yaml().dump(returnValue);
        // 2.写入响应中
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().println(str);
        // 3.通知MVC不再进行视图解析
        mavContainer.setRequestHandled(true);
    }
}

2.参数解析器

import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

/
 * 测试Spring MVC中Controller上的参数主键处理器
 *
 * @author lzlg
 * 2023/3/28 20:34
 */
public class TestSpringMVCArgumentResolver {

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

        HttpServletRequest request = mockRequest();
        // 1.控制器方法被封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(new TestController(),
                TestController.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.准备ModelAndViewContainer存储中间数据
        ModelAndViewContainer container = new ModelAndViewContainer();

        // 4.解析每个参数
        for (MethodParameter methodParameter : handlerMethod.getMethodParameters()) {
            // Spring中使用组合模式,把一系列的参数解析器组合进行调用
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
            composite.addResolvers(
                    // @RequestParam注解参数解析器
                    new RequestParamMethodArgumentResolver(
                            // 添加beanFactory才能使用其中的EL表达式解析
                            beanFactory,
                            // 如果为false,则必须有@RequestParam注解才解析
                            false),
                    // @PathVariable注解参数解析器
                    new PathVariableMethodArgumentResolver(),
                    // @RequestHeader注解参数解析器
                    new RequestHeaderMethodArgumentResolver(beanFactory),
                    // @CookieValue注解参数解析器
                    new ServletCookieValueMethodArgumentResolver(beanFactory),
                    // EL表达式解析器
                    new ExpressionValueMethodArgumentResolver(beanFactory),
                    // 特殊对象HttpServletRequest解析器
                    new ServletRequestMethodArgumentResolver(),
                    // @ModelAttribute注解解析器,如果为false则必须有@ModelAttribute注解才解析
                    new ServletModelAttributeMethodProcessor(false),
                    // @RequestBody注解解析器
                    new RequestResponseBodyMethodProcessor(Collections
                            .singletonList(new MappingJackson2HttpMessageConverter())),
                    // @ModelAttribute注解解析器,如果为true则没有@ModelAttribute注解也解析
                    // 注意顺序要在参数为false的之后,否则会覆盖@RequestBody注解解析器的解析结果
                    new ServletModelAttributeMethodProcessor(true),
                    // @RequestParam注解参数解析器
                    new RequestParamMethodArgumentResolver(
                            // 添加beanFactory才能使用其中的EL表达式解析
                            beanFactory,
                            // 如果为true,则没有@RequestParam注解也可以解析
                            // 注意顺序要在参数为false的之后
                            true)
            );

            // 解析参数名称需要解析器
            methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            // 解析参数上的注解数据(转换为字符串进行打印)
            String annotations = Arrays.stream(methodParameter.getParameterAnnotations())
                    .map(a -> a.annotationType().getSimpleName())
                    .collect(Collectors.joining());
            // 注解字符串
            String annotationStr = annotations.length() > 0 ? " @" + annotations + " " : " ";

            // 判断是否支持注解解析器
            if (composite.supportsParameter(methodParameter)) {
                // 解析出请求参数的值
                Object arg = composite.resolveArgument(methodParameter,
                        // 存储解析结果的容器
                        container,
                        // 请求数据
                        new ServletWebRequest(request),
                        // 数据类型绑定解析工厂,将String类型转换为指定的类型比如age字段
                        binderFactory);
                System.out.println("[" + methodParameter.getParameterIndex() + "] " + annotationStr
                        + methodParameter.getParameterType().getSimpleName() + " "
                        + methodParameter.getParameterName()
                        + "=====>>>>>" + arg);
                System.out.println("======>>>>>>>>>>模型数据有: " + container.getModel());
            } else {
                System.out.println("[" + methodParameter.getParameterIndex() + "] " + annotationStr
                        + methodParameter.getParameterType().getSimpleName() + " "
                        + methodParameter.getParameterName());
            }
        }

    }

    private static HttpServletRequest mockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1", "zhangsan");
        request.setParameter("name2", "lisi");
        request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
        Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
        request.setContentType("application/json");
        request.setCookies(new Cookie("token", "123456"));
        request.setParameter("name", "张三");
        request.setParameter("age", "18");
        request.setContent("{\n    \"name\": \"李四\",\n    \"age\": 20\n}".getBytes(StandardCharsets.UTF_8));
        return new StandardServletMultipartResolver().resolveMultipart(request);
    }

    static class TestController {

        public void test(@RequestParam("name1") String name1,
                         String name2,
                         @RequestParam("age") int age,
                         @RequestParam(name = "home1", defaultValue = "${JAVA_HOME}") String home1,
                         @RequestParam("file") MultipartFile file,
                         @PathVariable("id") int id,
                         @RequestHeader("Content-Type") String header,
                         @CookieValue("token") String token,
                         @Value("${JAVA_HOME}") String home2,
                         HttpServletRequest request,
                         @ModelAttribute("abc") User user1,
                         User user2,
                         @RequestBody User user3) {

        }
    }

    static class User {

        private String name;

        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

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

    @Configuration
    static class WebConfig {

    }
}

import org.springframework.core.LocalVariableTableParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Objects;

/
 * 测试Spring如何获取参数名称
 *
 * @author lzlg
 * 2023/3/29 21:11
 */
public class TestSpringGetArgumentName {
    public static void main(String[] args) throws Exception {
        // 1.通过反射进行获取,此时编译时需加上 -parameters 命令
        // 反射只能获取Method Parameters中的参数名称
        // javac -parameters TestBean.java
        Method method = TestBean.class.getMethod("foo", String.class, int.class);
        for (Parameter parameter : method.getParameters()) {
            System.out.println(parameter.getName());
        }
        // 2.通过Spring提供的工具从LocalVariableTable中获取
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        if (Objects.nonNull(parameterNames)) {
            for (String parameterName : parameterNames) {
                System.out.println(parameterName);
            }
        }
    }
}

/
 * 接口的参数名称只能通过Method Parameter进行保存
 *
 * @author lzlg
 * 2023/3/29 21:15
 */
public interface TestInterface {
    public void foo(String name, int age);
}

/
 * 1.将参数名称写入Method Parameter表中
 * javac -parameters TestBean.java
 * 2.将参数名称写入LocalVariableTable中
 * javac -g TestBean.java
 * 反编译
 * javap -c -v TestBean.class
 *
 * @author lzlg
 * 2023/3/29 21:13
 */
public class TestBean {
    public void foo(String name, int age) {

    }
}

3.对象绑定和类型转换

import org.springframework.beans.SimpleTypeConverter;

import java.util.Date;

/
 * 测试简单的类型转换器
 *
 * @author lzlg
 * 2023/3/29 21:48
 */
public class TestSimpleConverter {
    public static void main(String[] args) {
        SimpleTypeConverter converter = new SimpleTypeConverter();
        Integer number = converter.convertIfNecessary("13", int.class);
        Date date = converter.convertIfNecessary("2020/10/01", Date.class);
        System.out.println(number);
        System.out.println(date);
    }
}

import org.springframework.beans.BeanWrapperImpl;

import java.util.Date;

/
 * 测试Bean包装器中的类型转换
 * 适用反射调用get和set方法来实现
 *
 * @author lzlg
 * 2023/3/29 21:51
 */
public class TestBeanWrapper {
    public static void main(String[] args) {
        MyBean bean = new MyBean();
        BeanWrapperImpl wrapper = new BeanWrapperImpl(bean);
        wrapper.setPropertyValue("a", "13");
        wrapper.setPropertyValue("b", "hello");
        wrapper.setPropertyValue("c", new Date() + "");
        System.out.println(bean);
    }

    static class MyBean {

        private int a;

        private String b;

        private Date c;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public String getB() {
            return b;
        }

        public void setB(String b) {
            this.b = b;
        }

        public Date getC() {
            return c;
        }

        public void setC(Date c) {
            this.c = c;
        }

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}

import org.springframework.beans.DirectFieldAccessor;

import java.util.Date;

/
 * 通过访问字段来实现类型转换
 * 也是通过反射使用Field对象来实现,不用get,set方法
 *
 * @author lzlg
 * 2023/3/29 21:55
 */
public class TestFieldAccessor {
    public static void main(String[] args) {
        MyBean bean = new MyBean();
        DirectFieldAccessor accessor = new DirectFieldAccessor(bean);
        accessor.setPropertyValue("a", "14");
        accessor.setPropertyValue("b", "hello,world");
        accessor.setPropertyValue("c", new Date().toString());
        System.out.println(bean);

    }

    static class MyBean {

        private int a;

        private String b;

        private Date c;

        @Override
        public String toString() {
            return "MyBean{" +
                    "a=" + a +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    '}';
        }
    }
}

import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;

import java.util.Date;

/
 * 测试Web环境下的数据绑定
 *
 * @author lzlg
 * 2023/3/29 22:04
 */
public class TestWebServletBinder {
    public static void main(String[] args) {
        MyBean bean = new MyBean();
        ServletRequestDataBinder binder = new ServletRequestDataBinder(bean);
        // 设置直接访问Field字段对象
//        binder.initDirectFieldAccess();
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addParameter("a", "15");
        request.addParameter("b", "world");
        request.addParameter("c", "2023/02/22");
        ServletRequestParameterPropertyValues mpv = new ServletRequestParameterPropertyValues(request);
        binder.bind(mpv);
        System.out.println(bean);
    }

    static class MyBean {

        private int a;

        private String b;

        private Date c;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public String getB() {
            return b;
        }

        public void setB(String b) {
            this.b = b;
        }

        public Date getC() {
            return c;
        }

        public void setC(Date c) {
            this.c = c;
        }

        @Override
        public String toString() {
            return "MyBean{" + "a=" + a + ", b='" + b + '\'' + ", c=" + c + '}';
        }
    }
}

数据绑定工厂

import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;

import java.util.Collections;
import java.util.Date;

/
 * 测试Web环境下的绑定工厂
 *
 * @author lzlg
 * 2023/3/29 22:22
 */
public class TestWebServletBinderFactory {
    public static void main(String[] args) throws Exception {
        // 被绑定对象
        MyUser user = new MyUser();
        // 模拟请求信息
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addParameter("date", "2022|10|09");
        request.addParameter("address.name", "上海市青浦区");

        // 1.使用转换器工厂
//        testBinderFactory(user, request);

        // 2.使用注解@InitBinder进行绑定
//        testInitBinderAnnotation(user, request);

        // 3.使用ConversionService进行绑定
//        testConversionService(user, request);

        // 4.同时使用注解@InitBinder和ConversionService,注解的优先级较高
//        testBinderFirst(user, request);


        // 5.使用默认的ConversionService,可解析@DateTimeFormat注解上标注的格式

        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();

        DefaultFormattingConversionService service = new DefaultFormattingConversionService();
        initializer.setConversionService(service);

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
        WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
        binder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(user);
    }

    /
     * 同时使用注解@InitBinder和ConversionService,注解的优先级较高
     */
    private static void testBinderFirst(MyUser user, MockHttpServletRequest request) throws Exception {
        // 4.同时使用注解@InitBinder和ConversionService,注解的优先级较高
        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(),
                MyController.class.getMethod("binder", WebDataBinder.class));

        ApplicationConversionService service = new ApplicationConversionService();
        service.addFormatter(new MyDateFormatter("SpringBoot用 ApplicationConversionService 进行绑定 ==>>> "));
        // 初始化器
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(service);

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(
                Collections.singletonList(method),
                initializer);

        WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
        binder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(user);
    }

    /
     * 使用ConversionService进行绑定
     */
    private static void testConversionService(MyUser user, MockHttpServletRequest request) throws Exception {
        // 3.使用ConversionService进行绑定
        FormattingConversionService service = new FormattingConversionService();
        // 如果是SpringBoot环境可使用ApplicationConversionService
//        ApplicationConversionService service = new ApplicationConversionService();
        // 添加自定义格式化器
        service.addFormatter(new MyDateFormatter("用 ConversionService 进行绑定 ==>>> "));

        // 初始化器
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(service);

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);
        // 生成绑定对象
        WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
        // 进行绑定
        binder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(user);
    }

    /
     * 使用注解@InitBinder进行绑定
     */
    private static void testInitBinderAnnotation(MyUser user, MockHttpServletRequest request) throws Exception {
        // 2.使用注解@InitBinder进行绑定
        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(),
                MyController.class.getMethod("binder", WebDataBinder.class));

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(
                Collections.singletonList(method), null);
        WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");
        // 进行绑定
        binder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(user);
    }

    /
     * 使用转换器工厂
     */
    private static void testBinderFactory(MyUser user, MockHttpServletRequest request) throws Exception {
        // 1.使用转换器工厂
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
        WebDataBinder binder = factory.createBinder(new ServletWebRequest(request), user, "user");

        // 进行绑定
        binder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(user);
    }

    static class MyController {

        @InitBinder
        public void binder(WebDataBinder binder) {
            binder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 进行绑定 ==>>> "));
        }
    }

    static class MyUser {

        @DateTimeFormat(pattern = "yyyy|MM|dd")
        private Date date;

        private Address address;

        public Date getDate() {
            return date;
        }

        public void setDate(Date date) {
            this.date = date;
        }

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }

        @Override
        public String toString() {
            return "MyUser{" +
                    "date=" + date +
                    ", address=" + address +
                    '}';
        }
    }

    static class Address {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Address{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

Spring中获取泛型API

import org.springframework.core.GenericTypeResolver;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/
 * 测试Spring泛型API
 *
 * @author lzlg
 * 2023/3/29 22:54
 */
public class TestSpringGeneric {
    public static void main(String[] args) {
        // 1.使用JDK的API
        Type type = StudentDao.class.getGenericSuperclass();
        System.out.println(type);
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            System.out.println(parameterizedType.getActualTypeArguments()[0]);
        }

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        // 2.使用Spring的API
        Class<?> typeArgument = GenericTypeResolver.resolveTypeArgument(StudentDao.class, BaseDao.class);
        System.out.println(typeArgument);
    }

    static class BaseDao<T> {
        T find() {
            return null;
        }
    }

    static class StudentDao extends BaseDao<Student> {

    }

    static class Student {

    }
}

注解@InitialBinder

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/
 * 测试@InitBinder注解的时机
 *
 * @author lzlg
 * 2023/3/29 23:15
 */
public class TestInitBinderController {

    private static final Logger log = LoggerFactory.getLogger(TestInitBinderController.class);

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        // 请求映射处理器适配器
        RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
        handlerAdapter.setApplicationContext(context);
        handlerAdapter.afterPropertiesSet();
        System.out.println("1.系统初始化...");
        showBindMethods(handlerAdapter);

        System.out.println("2.请求控制器Controller1的foo方法");
        Method method = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory",
                HandlerMethod.class);
        method.setAccessible(true);
        method.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(),
                WebConfig.Controller1.class.getMethod("foo")));
        showBindMethods(handlerAdapter);

        System.out.println("3.请求控制器Controller2的bar方法");
        method.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(),
                WebConfig.Controller2.class.getMethod("bar")));
        showBindMethods(handlerAdapter);

        context.close();
    }

    @SuppressWarnings("all")
    private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException,
            IllegalAccessException {
        // ControllerAdvice中的InitBinder缓存
        Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
        initBinderAdviceCache.setAccessible(true);
        Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>)
                initBinderAdviceCache.get(handlerAdapter);
        System.out.printf("全局的 @InitBinder 方法 %s\n",
                globalMap.values().stream()
                        .flatMap(ms -> ms.stream().map(m -> m.getName()))
                        .collect(Collectors.toList())
        );

        // Controller中的InitBinder缓存
        Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
        initBinderCache.setAccessible(true);
        Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>)
                initBinderCache.get(handlerAdapter);
        System.out.printf("控制器的 @InitBinder 方法 %s\n",
                controllerMap.entrySet().stream()
                        .flatMap(e -> e.getValue().stream()
                                .map(v -> e.getKey().getSimpleName() + "." + v.getName()))
                        .collect(Collectors.toList())
        );
    }
}

import com.lzlg.study.spring.converter.MyDateFormatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;

/
 * @author lzlg
 * 2023/3/29 23:09
 */
@Configuration
public class WebConfig {

    /
     * 注解@ControllerAdvice上标注的@InitBinder是全局的绑定器
     */
    @ControllerAdvice
    static class MyControllerAdvice {

        @InitBinder
        public void binder(WebDataBinder dataBinder) {
            dataBinder.addCustomFormatter(new MyDateFormatter(" ControllerAdvice @InitBinder 转换器"));
        }
    }

    /
     * 注解@Controller上标注的@InitBinder只在当前Controller中生效
     */
    @Controller
    static class Controller1 {
        @InitBinder
        public void binder(WebDataBinder dataBinder) {
            dataBinder.addCustomFormatter(new MyDateFormatter(" Controller1 @InitBinder 转换器"));
        }

        public void foo() {

        }
    }

    @Controller
    static class Controller2 {
        @InitBinder
        public void binder1(WebDataBinder dataBinder) {
            dataBinder.addCustomFormatter(new MyDateFormatter(" Controller2 @InitBinder binder1 转换器"));
        }

        @InitBinder
        public void binder2(WebDataBinder dataBinder) {
            dataBinder.addCustomFormatter(new MyDateFormatter(" Controller2 @InitBinder binder2 转换器"));
        }

        public void bar() {

        }
    }
}

import lombok.extern.slf4j.Slf4j;
import org.springframework.format.Formatter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/
 * 自定义日期格式转换器
 *
 * @author lzlg
 * 2023/3/29 22:22
 */
@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, text);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.parse(text);
    }

    @Override
    public String print(Date object, Locale locale) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        log.info("{} 转换日期: {}", desc, object);
        return sdf.format(object);
    }
}

注解@ModelAttribute

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.ModelFactory;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.*;

import java.lang.reflect.Method;
import java.util.Collections;

/
 * 测试Spring中MVC的handleMethod执行流程
 *
 * @author lzlg
 * 2023/3/30 21:07
 */
public class TestSpringHandleMethod {
    public static void main(String[] args) throws Exception {
        // 模拟请求对象
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addParameter("name", "张三");

        // 容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();

        // 请求映射处理适配器,能解析@ModelAttribute注解,并将返回数据放入ModelAndViewContainer中
        RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
        handlerAdapter.setApplicationContext(context);
        handlerAdapter.afterPropertiesSet();

        // 控制器方法调用对象
        ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
                new WebConfig.TestController(),
                WebConfig.TestController.class.getMethod("foo", WebConfig.User.class)
        );
        // 设置参数解析器
        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(beanFactory));

        // 绑定数据类型转换器
        ServletRequestDataBinderFactory binderFactory = new ServletRequestDataBinderFactory(null, null);
        handlerMethod.setDataBinderFactory(binderFactory);

        // 控制器方法参数名称解析器
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());

        // ModelAndView容器,存储请求中的一些对象
        ModelAndViewContainer container = new ModelAndViewContainer();

        // ==========RequestMappingHandlerAdapter===========
        // 获取模型工厂方法
        Method getModelFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod(
                "getModelFactory", HandlerMethod.class, WebDataBinderFactory.class);
        getModelFactory.setAccessible(true);
        ModelFactory modelFactory = (ModelFactory) getModelFactory.invoke(handlerAdapter,
                handlerMethod, binderFactory);
        // 初始化模型数据,将解析@ModelAttribute中的注解
        modelFactory.initModel(new ServletWebRequest(request), container, handlerMethod);

        // 调用控制器方法
        handlerMethod.invokeAndHandle(new ServletWebRequest(request), container);

        // 打印模型数据
        System.out.println(container.getModel());

        context.close();
    }

    /
     * 常见参数解析器组合
     */
    private static HandlerMethodArgumentResolverComposite getArgumentResolvers(DefaultListableBeanFactory beanFactory) {
        HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
        composite.addResolvers(
                // @RequestParam注解参数解析器,如果为false,则必须有@RequestParam注解才解析
                new RequestParamMethodArgumentResolver(beanFactory, false),
                // @PathVariable注解参数解析器
                new PathVariableMapMethodArgumentResolver(),
                // @RequestHeader注解参数解析器
                new RequestHeaderMethodArgumentResolver(beanFactory),
                // @CookieValue注解参数解析器
                new ServletCookieValueMethodArgumentResolver(beanFactory),
                // EL表达式解析器
                new ExpressionValueMethodArgumentResolver(beanFactory),
                // 特殊对象HttpServletRequest解析器
                new ServletRequestMethodArgumentResolver(),

                // @ModelAttribute注解解析器,如果为false则必须有@ModelAttribute注解才解析
                new ServletModelAttributeMethodProcessor(false),

                // @RequestBody注解解析器
                new RequestResponseBodyMethodProcessor(Collections
                        .singletonList(new MappingJackson2HttpMessageConverter())),

                // @ModelAttribute注解解析器,如果为true则没有@ModelAttribute注解也解析
                new ServletModelAttributeMethodProcessor(true),

                // 如果为true,则没有@RequestParam注解也可以解析
                new RequestParamMethodArgumentResolver(beanFactory, true)
        );

        return composite;
    }
}

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

/
 * @author lzlg
 * 2023/3/30 21:04
 */
@Configuration
public class WebConfig {

    @Controller
    static class TestController {

        @ModelAttribute("hello")
        public String hello() {
            return "hello";
        }

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

    @ControllerAdvice
    static class TestControllerAdvice {

        @ModelAttribute("test")
        public String test() {
            return "test";
        }

    }

    static class User {

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

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

4.返回值处理器

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.*;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.util.UrlPathHelper;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Locale;

/
 * 测试Spring MVC中对返回值的处理器
 *
 * @author lzlg
 * 2023/3/30 21:55
 */
public class TestSpringReturnValueHandler {

    /
     * 返回值解析器组合
     */
    private static HandlerMethodReturnValueHandlerComposite returnValueHandlerComposite() {
        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();

        // 解析返回ModelAndView返回值的处理器
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        // 解析返回String视图的处理器
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        // 解析注解@ModeAndView的返回值处理器,如果为false,则必须有@ModelAttribute注解
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));

        // 解析返回值是HttpEntity的处理器
        composite.addHandler(new HttpEntityMethodProcessor(Collections
                .singletonList(new MappingJackson2HttpMessageConverter())));

        // 解析返回值是HttpHeaders的处理器
        composite.addHandler(new HttpHeadersReturnValueHandler());

        // 解析@ResponseBody注解返回json数据的处理器
        composite.addHandler(new RequestResponseBodyMethodProcessor(Collections
                .singletonList(new MappingJackson2HttpMessageConverter())));

        // 解析注解@ModeAndView的返回值处理器,如果为true,则可以没有@ModelAttribute注解
        composite.addHandler(new ServletModelAttributeMethodProcessor(true));

        return composite;
    }

    public static void main(String[] args) throws Exception {
        // Spring容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FreeMarkerConfig.class);

        // 1.测试解析ModelAndView返回值
//        testModelAndView(context);

        // 2.测试返回值是String(视图)
//        testStringView(context);

        // 3.测试@ModelAttribute注解返回值
//        testModelAttribute(context);

        // 4.测试没有@ModelAttribute注解返回值
//        testNoModelAttribute(context);

        // 5.测试HttpEntity返回值
//        testHttpEntity(context);

        // 6.测试HttpHeaders返回值
//        testHttpHeaders(context);

        // 7.测试@ResponseBody返回值
        testResponseBody(context);

        context.close();
    }

    /
     * 6.测试HttpHeaders返回值
     */
    private static void testResponseBody(AnnotationConfigApplicationContext context) throws Exception {
        // 控制器对象
        ReturnController controller = new ReturnController();
        // 请求方法
        Method method = ReturnController.class.getMethod("test7");
        // 反射调用生成返回值
        Object returnValue = method.invoke(controller);

        // 请求对象
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 响应对象
        MockHttpServletResponse response = new MockHttpServletResponse();
        // 封装请求和响应信息
        ServletWebRequest webRequest = new ServletWebRequest(request, response);


        // 将对控制器方法的请求封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        // 模型和视图容器
        ModelAndViewContainer container = new ModelAndViewContainer();
        // 获取返回值解析器组合
        HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
        // 是否支持返回的类型
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue,
                    handlerMethod.getReturnType(),
                    container,
                    webRequest);

            System.out.println("模型数据: " + container.getModel());
            System.out.println("视图名称: " + container.getViewName());

            // 返回值处理器通过设置requestHandled为true来表示请求已处理完成,不再进行视图渲染
            if (container.isRequestHandled()) {
                for (String headerName : response.getHeaderNames()) {
                    System.out.println(headerName + " = " + response.getHeader(headerName));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            } else {
                renderView(context, container, webRequest);
            }

        }
    }


    /
     * 6.测试HttpHeaders返回值
     */
    private static void testHttpHeaders(AnnotationConfigApplicationContext context) throws Exception {
        // 控制器对象
        ReturnController controller = new ReturnController();
        // 请求方法
        Method method = ReturnController.class.getMethod("test6");
        // 反射调用生成返回值
        Object returnValue = method.invoke(controller);

        // 请求对象
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 响应对象
        MockHttpServletResponse response = new MockHttpServletResponse();
        // 封装请求和响应信息
        ServletWebRequest webRequest = new ServletWebRequest(request, response);


        // 将对控制器方法的请求封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        // 模型和视图容器
        ModelAndViewContainer container = new ModelAndViewContainer();
        // 获取返回值解析器组合
        HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
        // 是否支持返回的类型
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue,
                    handlerMethod.getReturnType(),
                    container,
                    webRequest);

            System.out.println("模型数据: " + container.getModel());
            System.out.println("视图名称: " + container.getViewName());

            // 返回值处理器通过设置requestHandled为true来表示请求已处理完成,不再进行视图渲染
            if (container.isRequestHandled()) {
                for (String headerName : response.getHeaderNames()) {
                    System.out.println(headerName + " = " + response.getHeader(headerName));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            } else {
                renderView(context, container, webRequest);
            }

        }
    }

    /
     * 5.测试HttpEntity返回值
     */
    private static void testHttpEntity(AnnotationConfigApplicationContext context) throws Exception {
        // 控制器对象
        ReturnController controller = new ReturnController();
        // 请求方法
        Method method = ReturnController.class.getMethod("test5");
        // 反射调用生成返回值
        Object returnValue = method.invoke(controller);

        // 请求对象
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 响应对象
        MockHttpServletResponse response = new MockHttpServletResponse();
        // 封装请求和响应信息
        ServletWebRequest webRequest = new ServletWebRequest(request, response);


        // 将对控制器方法的请求封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        // 模型和视图容器
        ModelAndViewContainer container = new ModelAndViewContainer();
        // 获取返回值解析器组合
        HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
        // 是否支持返回的类型
        if (composite.supportsReturnType(handlerMethod.getReturnType())) {
            composite.handleReturnValue(returnValue,
                    handlerMethod.getReturnType(),
                    container,
                    webRequest);

            System.out.println("模型数据: " + container.getModel());
            System.out.println("视图名称: " + container.getViewName());

            // 返回值处理器通过设置requestHandled为true来表示请求已处理完成,不再进行视图渲染
            if (container.isRequestHandled()) {
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            } else {
                renderView(context, container, webRequest);
            }

        }
    }

    /
     * 4.测试没有@ModelAttribute注解时返回值
     */
    private static void testNoModelAttribute(AnnotationConfigApplicationContext context) throws Exception {
        // 控制器对象
        ReturnController controller = new ReturnController();
        // 请求方法
        Method method = ReturnController.class.getMethod("test4");
        // 反射调用生成返回值
        Object returnValue = method.invoke(controller);

        // 请求对象
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 设置请求路径,请求路径映射到视图上
        request.setRequestURI("/test4");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        // 响应对象
        MockHttpServletResponse response = new MockHttpServletResponse();
        // 封装请求和响应信息
        ServletWebRequest webRequest = new ServletWebRequest(request, response);


        // 将对控制器方法的请求封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        // 模型和视图容器
        ModelAndViewContainer container = new ModelAndViewContainer();
        // 获取返回值解析器组合
        HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
        // 是否支持返回的类型
        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);
        }
    }

    /
     * 3.测试@ModelAttribute注解返回值
     */
    private static void testModelAttribute(AnnotationConfigApplicationContext context) throws Exception {
        // 控制器对象
        ReturnController controller = new ReturnController();
        // 请求方法
        Method method = ReturnController.class.getMethod("test3");
        // 反射调用生成返回值
        Object returnValue = method.invoke(controller);

        // 请求对象
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 设置请求路径,请求路径映射到视图上
        request.setRequestURI("/test3");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        // 响应对象
        MockHttpServletResponse response = new MockHttpServletResponse();
        // 封装请求和响应信息
        ServletWebRequest webRequest = new ServletWebRequest(request, response);


        // 将对控制器方法的请求封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        // 模型和视图容器
        ModelAndViewContainer container = new ModelAndViewContainer();
        // 获取返回值解析器组合
        HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
        // 是否支持返回的类型
        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);
        }
    }


    /
     * 2.测试返回值是String(视图)
     */
    private static void testStringView(AnnotationConfigApplicationContext context) throws Exception {
        // 控制器对象
        ReturnController controller = new ReturnController();
        // 请求方法
        Method method = ReturnController.class.getMethod("test2");
        // 反射调用生成返回值
        Object returnValue = method.invoke(controller);

        // 请求对象
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 响应对象
        MockHttpServletResponse response = new MockHttpServletResponse();
        // 封装请求和响应信息
        ServletWebRequest webRequest = new ServletWebRequest(request, response);


        // 将对控制器方法的请求封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        // 模型和视图容器
        ModelAndViewContainer container = new ModelAndViewContainer();
        // 获取返回值解析器组合
        HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
        // 是否支持返回的类型
        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);
        }
    }

    /
     * 1.测试解析ModelAndView返回值
     */
    private static void testModelAndView(AnnotationConfigApplicationContext context) throws Exception {
        // 控制器对象
        ReturnController controller = new ReturnController();
        // 请求方法
        Method method = ReturnController.class.getMethod("test1");
        // 反射调用生成返回值
        Object returnValue = method.invoke(controller);

        // 请求对象
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 响应对象
        MockHttpServletResponse response = new MockHttpServletResponse();
        // 封装请求和响应信息
        ServletWebRequest webRequest = new ServletWebRequest(request, response);


        // 将对控制器方法的请求封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(controller, method);
        // 模型和视图容器
        ModelAndViewContainer container = new ModelAndViewContainer();
        // 获取返回值解析器组合
        HandlerMethodReturnValueHandlerComposite composite = returnValueHandlerComposite();
        // 是否支持返回的类型
        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);
        }
    }

    /
     * FreeMarker视图渲染
     */
    @SuppressWarnings("all")
    private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
                                   ServletWebRequest webRequest) throws Exception {
        System.out.println(">>>>>> 渲染视图");
        FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);
        String viewName = container.getViewName() != null ? container.getViewName() :
                new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());
        System.out.println("没有获取到视图名, 采用默认视图名: " + viewName);
        // 每次渲染时,会产生新的视图对象,它并非被 Spring 所管理,但确实借助了Spring容器来执行初始化
        View view = resolver.resolveViewName(viewName, Locale.getDefault());
        // 进行渲染
        view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());
        // 打印渲染后的html内容
        System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse())
                .getContentAsByteArray(),
                StandardCharsets.UTF_8));
    }


    static class ReturnController {

        // 1.测试ModelAndView
        public ModelAndView test1() {
            System.out.println("ReturnController test1()...");
            ModelAndView view = new ModelAndView("view1");
            view.addObject("name", "张三");
            return view;
        }

        // 2.测试直接返回视图名
        public String test2() {
            System.out.println("ReturnController test2()...");
            return "view2";
        }

        // 3.测试@ModelAttribute,有模型数据,视图通过@RequestMapping(请求路径)来确定
        @ModelAttribute("user")
        public User test3() {
            System.out.println("ReturnController test3()...");
            return new User("李四", 32);
        }

        // 4.测试默认的,走@ModelAttribute逻辑
        public User test4() {
            System.out.println("ReturnController test4()...");
            return new User("王五", 18);
        }

        // 5.直接返回数据HttpEntity
        public HttpEntity<User> test5() {
            System.out.println("ReturnController test5()...");
            return new HttpEntity<>(new User("赵六", 21));
        }

        // 6.直接返回数据HttpHeaders
        public HttpHeaders test6() {
            System.out.println("ReturnController test6()...");
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-type", "text/html");
            return headers;
        }

        // 6.直接返回Json数据@ResponseBody
        @ResponseBody
        public User test7() {
            return new User("钱七", 27);
        }
    }

    public static class User {

        private String name;

        private int age;

        public User() {
        }

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

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

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

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;

import java.nio.charset.StandardCharsets;

/
 * @author lzlg
 * 2023/3/30 21:58
 */
@Configuration
public class FreeMarkerConfig {

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setDefaultEncoding(StandardCharsets.UTF_8.name());
        configurer.setTemplateLoaderPath("classpath:templates");
        return configurer;
    }

    @Bean
    public FreeMarkerViewResolver freeMarkerViewResolver(FreeMarkerConfigurer freeMarkerConfigurer) {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver() {
            @Override
            protected AbstractUrlBasedView instantiateView() {
                FreeMarkerView view = new FreeMarkerView() {
                    @Override
                    protected boolean isContextRequired() {
                        return false;
                    }
                };
                view.setConfiguration(freeMarkerConfigurer.getConfiguration());
                return view;
            }
        };
        resolver.setContentType("text/html;charset=utf-8");
        resolver.setPrefix("/");
        resolver.setSuffix(".ftl");
        resolver.setExposeSpringMacroHelpers(false);
        return resolver;
    }
}

5.消息转换器

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.*;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/
 * 测试Spring MVC中消息做统一处理
 *
 * @author lzlg
 * 2023/3/31 19:42
 */
public class TestSpringMessageAdvice {
    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("test", WebConfig.User.class)
        );
        // 设置数据绑定转换工厂
        handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(Collections.emptyList(), null));
        // 设置参数名称查找器
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
        // 设置请求参数解析器
        handlerMethod.setHandlerMethodArgumentResolvers(argumentResolverComposite(context));
        // 设置返回值处理器
        handlerMethod.setHandlerMethodReturnValueHandlers(returnValueHandlerComposite(context));

        // 模拟请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        String json = "{\n" +
                "    \"method\": \"test\",\n" +
                "    \"LocalDateTime\": \"2022-03-31 20:43:00\",\n" +
                "    \"requestId\": \"1234567890\",\n" +
                "    \"data\": {\n" +
                "        \"name\": \"王五\",\n" +
                "        \"age\": 20\n" +
                "    }\n" +
                "}";
        // 模拟Json
        request.setContent(json.getBytes(StandardCharsets.UTF_8));
        request.setContentType(MediaType.APPLICATION_JSON_VALUE);
        // 模拟响应
        MockHttpServletResponse response = new MockHttpServletResponse();
        // ModelAndView容器
        ModelAndViewContainer container = new ModelAndViewContainer();
        // Web请求响应封装
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        // 调用控制器方法,进行请求处理
        handlerMethod.invokeAndHandle(webRequest, container);

        // 打印响应结果
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));

        context.close();
    }

    /
     * 设置常见的参数解析器组合
     */
    private static HandlerMethodArgumentResolverComposite argumentResolverComposite(AnnotationConfigApplicationContext context) {
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();

        // 添加请求处理advice
        List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);
        List<Object> requestAdvices = annotatedBeans.stream()
                .filter(b -> RequestBodyAdvice.class.isAssignableFrom(Objects.requireNonNull(b.getBeanType())))
                .collect(Collectors.toList());

        HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
        composite.addResolvers(
                new RequestParamMethodArgumentResolver(beanFactory, false),
                new PathVariableMethodArgumentResolver(),
                new RequestHeaderMethodArgumentResolver(beanFactory),
                new ServletCookieValueMethodArgumentResolver(beanFactory),
                new ExpressionValueMethodArgumentResolver(beanFactory),
                new ServletRequestMethodArgumentResolver(),
                new ServletModelAttributeMethodProcessor(false),

                new RequestResponseBodyMethodProcessor(Collections
                        .singletonList(new MappingJackson2HttpMessageConverter()), requestAdvices),

                new ServletModelAttributeMethodProcessor(true),
                new RequestParamMethodArgumentResolver(beanFactory, true)
        );
        return composite;
    }

    /
     * 设置常见的返回值处理器组合
     */
    private static HandlerMethodReturnValueHandlerComposite returnValueHandlerComposite(AnnotationConfigApplicationContext context) {
        // 添加响应advice
        List<ControllerAdviceBean> annotatedBeans = ControllerAdviceBean.findAnnotatedBeans(context);
        List<Object> responseAdvices = annotatedBeans.stream()
                .filter(b -> ResponseBodyAdvice.class.isAssignableFrom(Objects.requireNonNull(b.getBeanType())))
                .collect(Collectors.toList());

        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
        composite.addHandler(new HttpEntityMethodProcessor(Collections
                .singletonList(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new HttpHeadersReturnValueHandler());

        composite.addHandler(new RequestResponseBodyMethodProcessor(Collections
                .singletonList(new MappingJackson2HttpMessageConverter()), responseAdvices));

        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
        return composite;
    }
}

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/
 * 测试Spring MVC中的消息转换器
 *
 * @author lzlg
 * 2023/3/31 19:01
 */
public class TestSpringMVCMessageConverter {
    public static void main(String[] args) throws Exception {
        // 测试Json消息格式的消息转换器
//        testJsonConverter();

        // 测试XML消息格式的消息转换器
//        testXmlConverter();


        // 测试输入时的消息转换器
//        testInputMessageConverter();

        // 测试消息转换器的优先级
        testMessageConverterRank();
    }


    /
     * 测试消息转换器的优先级,默认按照添加的顺序指定
     * 优先: 如果响应指定Content-Type,则按照Content-Type进行转换,同@RequestMapping中使用produces
     * 其次: 如果请求头Accept指定,则按照Accept进行转换
     * 最后: 按照转换器添加的顺序指定
     */
    private static void testMessageConverterRank() throws Exception {
        // 模拟请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("Accept", "application/xml");

        // 模拟响应
        MockHttpServletResponse response = new MockHttpServletResponse();
        response.setContentType("application/json");

        // Web请求封装
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        // 请求体和响应体处理器
        RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
                Arrays.asList(
                        new MappingJackson2HttpMessageConverter(),
                        new MappingJackson2XmlHttpMessageConverter()
                )
        );

        // 处理返回值
        processor.handleReturnValue(
                new User("赵六", 25),
                new MethodParameter(TestSpringMVCMessageConverter.class.getMethod("user"), -1),
                new ModelAndViewContainer(),
                webRequest
        );
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));

    }

    @ResponseBody
    @RequestMapping(produces = "application/json")
    public User user() {
        return null;
    }

    /
     * 测试输入时的消息转换器
     */
    private static void testInputMessageConverter() throws IOException {
        // 创建模拟输入消息
        MockHttpInputMessage message = new MockHttpInputMessage(
                "{\n\"name\": \"王五\",\n\"age\": 20\n}".getBytes(StandardCharsets.UTF_8));
        // 消息转换器
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        // 查看是否支持读取数据
        if (converter.canRead(User.class, MediaType.APPLICATION_JSON)) {
            // 进行读取
            User user = (User) converter.read(User.class, message);
            System.out.println(user);
        }
    }

    /
     * 测试XML消息格式的消息转换器
     */
    private static void testXmlConverter() throws IOException {
        // 模拟Http输出消息
        MockHttpOutputMessage message = new MockHttpOutputMessage();
        // 转换为XML格式的消息转换器
        MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
        // 判断能够输出为XML格式
        if (converter.canWrite(User.class, MediaType.APPLICATION_XML)) {
            converter.write(
                    new User("李四", 15),
                    MediaType.APPLICATION_XML,
                    message
            );
            System.out.println(message.getBodyAsString());
        }
    }

    /
     * 测试Json消息格式的消息转换器
     */
    private static void testJsonConverter() throws IOException {
        // 模拟Http输出消息
        MockHttpOutputMessage message = new MockHttpOutputMessage();
        // 转换为json格式的消息转换器
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        // 判断能否输出
        if (converter.canWrite(User.class, MediaType.APPLICATION_JSON)) {
            converter.write(
                    new User("张三", 12),
                    MediaType.APPLICATION_JSON,
                    message
            );
            System.out.println(message.getBodyAsString());
        }
    }


    static class User {

        private String name;

        private int age;

        @JsonCreator
        public User(@JsonProperty("name") String name, @JsonProperty("age") int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

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

import com.alibaba.fastjson.JSON;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

/
 * @author lzlg
 * 2023/3/31 19:42
 */
@Configuration
public class WebConfig {


    /
     * 定义请求消息处理
     */
    @ControllerAdvice
    public static class MyRequestAdvice implements RequestBodyAdvice {

        /
         * 是否进行请求信息的处理
         */
        @Override
        public boolean supports(MethodParameter methodParameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
            if (methodParameter.hasParameterAnnotation(RequestBody.class)) {
                System.out.println("方法参数上有@RequestBoday注解...");
                return true;
            }
            return false;
        }

        @Override
        public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
                                               MethodParameter parameter, Type targetType,
                                               Class<? extends HttpMessageConverter<?>> converterType) {
            System.out.println(inputMessage.getClass());
            try (InputStream is = inputMessage.getBody()) {
                byte[] bytes = new byte[is.available()];
                int read = is.read(bytes, 0, bytes.length);
                System.out.println("read===>" + read);

                String content = new String(bytes, StandardCharsets.UTF_8);
                System.out.println(content);

                Result<?> result = JSON.parseObject(content, Result.class);

                return new MockHttpInputMessage(JSON.toJSONString(result.getData()).getBytes(StandardCharsets.UTF_8));

            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }

        }

        @Override
        public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
                                    MethodParameter parameter, Type targetType,
                                    Class<? extends HttpMessageConverter<?>> converterType) {
            System.out.println("afterBodyRead========>>>>>>>>>" + body);
            return body;
        }

        @Override
        public Object handleEmptyBody(Object body, HttpInputMessage inputMessage,
                                      MethodParameter parameter, Type targetType,
                                      Class<? extends HttpMessageConverter<?>> converterType) {
            return body;
        }
    }

    /
     * 自定义响应处理器
     */
    @ControllerAdvice
    public static class MyResponseAdvice implements ResponseBodyAdvice<Object> {

        /
         * 是否进行响应消息处理,返回true则进行消息处理
         */
        @Override
        public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
            // 情况1: 请求方法上有注解@ResponseBody
            if (returnType.hasMethodAnnotation(ResponseBody.class)
                    // 情况2: 控制器方法上有注解@ResponseBody
                    || returnType.getContainingClass().isAnnotationPresent(ResponseBody.class)
                    // 情况3: 使用组合注解(组合注解中包含@ResponseBody)@RestController
                    || AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null) {
                System.out.println("进行响应消息的处理.....");

                return true;
            }
            return false;
        }

        /
         * 进行消息处理
         */
        @Override
        public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                      Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                      ServerHttpRequest request, ServerHttpResponse response) {
            if (body instanceof Result) {
                System.out.println("返回结果已经是Result类型");
                return body;
            }
            return Result.ok(body);
        }
    }

    /
     * 控制器
     */
//    @Controller
//    @ResponseBody
    @RestController
    public static class MyController {

        //      @ResponseBody
        public User user() {
            return new User("张三", 23);
        }

        public Result<User> test(@RequestBody User user) {
            System.out.println("解析请求信息信息成功..." + user);
            return Result.ok(user);
        }
    }

    public static class User {

        private String name;

        private int age;

        public User() {
        }

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

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

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }
}

/
 * 统一返回结果
 *
 * @author lzlg
 * 2023/3/31 19:42
 */
public class Result<T> {

    private int code;

    private String message;

    private T data;

    public Result() {
    }

    private Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

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

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

import java.time.LocalDateTime;
import java.util.UUID;

/
 * 统一请求结果
 *
 * @author lzlg
 * 2023/3/31 19:43
 */
public class Request<T> {

    private String method;

    private LocalDateTime requestTime;

    private String requestId;

    private T data;

    private Request(String method, LocalDateTime requestTime, String requestId, T data) {
        this.method = method;
        this.requestTime = requestTime;
        this.requestId = requestId;
        this.data = data;
    }

    public static <T> Request<T> build(T data) {
        return new Request<>("method",
                LocalDateTime.now(),
                UUID.randomUUID().toString(),
                data);
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public LocalDateTime getRequestTime() {
        return requestTime;
    }

    public void setRequestTime(LocalDateTime requestTime) {
        this.requestTime = requestTime;
    }

    public String getRequestId() {
        return requestId;
    }

    public void setRequestId(String requestId) {
        this.requestId = requestId;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

6.异常解析器

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/
 * 测试Spring MVC中的异常处理器
 *
 * @author lzlg
 * 2023/3/31 21:36
 */
public class TestSpringExceptionResolver {

    public static void main(String[] args) throws Exception {
        // 异常处理器
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        resolver.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
        resolver.afterPropertiesSet();

        // 1.测试解析JSON的异常处理器
//        testJsonExceptionHandler(resolver);


        // 2.测试解析ModelAndView的异常处理器
//        testModelAndViewExceptionHandler(resolver);


        // 3.测试嵌套异常
//        testNestExceptionHandler(resolver);


        // 4.测试参数解析
        testExceptionArgumentResolver(resolver);
    }

    /
     * 测试参数解析
     */
    private static void testExceptionArgumentResolver(ExceptionHandlerExceptionResolver resolver) throws NoSuchMethodException {
        // 模拟请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 模拟响应
        MockHttpServletResponse response = new MockHttpServletResponse();

        // 请求方法
        HandlerMethod handlerMethod = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));

        // 进行异常嵌套
        Exception e = new RuntimeException("运行时", new IOException("IO异常", new ArithmeticException("算术异常")));
        // 嵌套中的出现的异常都能进行相应的处理
        resolver.resolveException(request, response, handlerMethod, e);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    /
     * 测试嵌套异常
     */
    private static void testNestExceptionHandler(ExceptionHandlerExceptionResolver resolver) throws NoSuchMethodException {
        // 模拟请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 模拟响应
        MockHttpServletResponse response = new MockHttpServletResponse();

        // 请求方法
        HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));

        // 进行异常嵌套
        Exception e = new RuntimeException("运行时", new IOException("IO异常", new ArithmeticException("算术异常")));
        // 嵌套中的出现的异常都能进行相应的处理
        resolver.resolveException(request, response, handlerMethod, e);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    /
     * 测试解析ModelAndView的异常处理器
     */
    private static void testModelAndViewExceptionHandler(ExceptionHandlerExceptionResolver resolver)
            throws NoSuchMethodException {
        // 模拟请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 模拟响应
        MockHttpServletResponse response = new MockHttpServletResponse();

        // 请求方法
        HandlerMethod handlerMethod = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo"));

        Exception e = new ArithmeticException("算术异常");
        ModelAndView mav = resolver.resolveException(request, response, handlerMethod, e);
        System.out.println(mav.getModel());
        System.out.println(mav.getViewName());
    }

    /
     * 测试异常处理器
     */
    private static void testJsonExceptionHandler(ExceptionHandlerExceptionResolver resolver)
            throws NoSuchMethodException {
        // 模拟请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        // 模拟响应
        MockHttpServletResponse response = new MockHttpServletResponse();

        // 请求方法
        HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));

        Exception e = new ArithmeticException("算术异常");
        resolver.resolveException(request, response, handlerMethod, e);

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


    static class Controller1 {

        public void foo() {

        }

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

    static class Controller2 {

        public void foo() {

        }

        @ExceptionHandler
        public ModelAndView test(ArithmeticException e) {
            Map<String, Object> map = new HashMap<>(1);
            map.put("error2", e.getMessage());
            return new ModelAndView("view1", map);
        }
    }

    static class Controller3 {

        public void foo() {

        }

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

    static class Controller4 {

        public void foo() {

        }

        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> test(ArithmeticException e, HttpServletRequest request) {
            System.out.println("解析到request参数: " + request);
            Map<String, Object> map = new HashMap<>(1);
            map.put("error4", e.getMessage());
            return map;
        }
    }

}

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import java.nio.charset.StandardCharsets;

/
 * 测试Spring MVC中全局异常处理
 *
 * @author lzlg
 * 2023/3/31 21:55
 */
public class TestSpringExceptionHandlerAdvice {
    public static void main(String[] args) throws Exception {
        // 容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ExceptionWebConfig.class);
        // 从容器中获取异常解析处理器
        ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);

        // 模拟请求/响应
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();

        // 控制器方法
        HandlerMethod handlerMethod = new HandlerMethod(new TestController(), TestController.class.getMethod("foo"));

        Exception e = new Exception("hello异常");
        resolver.resolveException(request, response, handlerMethod, e);

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

        context.close();
    }

    static class TestController {

        public void foo() {

        }
    }
}

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/
 * @author lzlg
 * 2023/3/31 21:55
 */
@Configuration
public class ExceptionWebConfig {

    @ControllerAdvice
    public static class ExceptionHandlerControllerAdvice {

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

    // 注册全局异常处理解析器
    @Bean
    public ExceptionHandlerExceptionResolver exceptionResolver() {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        resolver.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
        // 不需要调用afterPropertiesSet方法,因为ExceptionHandlerExceptionResolver实现了InitialingBean接口
        return resolver;
    }

}

7.异常处理器

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/
 * 测试Spring Tomcat容器异常处理
 *
 * @author lzlg
 * 2023/4/2 20:46
 */
public class TestSpringTomcatException {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(TomcatWebConfig.class);

        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        mapping.getHandlerMethods().forEach(((RequestMappingInfo k, HandlerMethod v) ->
                System.out.println("映射路径: " + k + "\t方法信息: " + v)
        ));
    }
}

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.view.BeanNameViewResolver;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/
 * @author lzlg
 * 2023/4/2 20:47
 */
@Configuration
// 导入配置文件
@PropertySource("classpath:application.properties")
// 启用配置类
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class TomcatWebConfig {

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

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

    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(
                dispatcherServlet, "/");
        // 设置是否直接加载DispatcherServlet,默认值是-1(懒加载)
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }

    /
     * RequestMappingHandlerMapping用于生成路径和请求处理器的映射关系
     */
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    /
     * 请求处理适配器
     */
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
        adapter.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
        return adapter;
    }

    /
     * 注册错误处理页面
     */
    @Bean
    public ErrorPageRegistrar errorPageRegistrar() {
        // 将错误请求[转发]到error路径下
        return registry -> registry.addErrorPages(new ErrorPage("/error"));
    }

    /
     * 注册错误页面后置处理器
     */
    @Bean
    public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
        return new ErrorPageRegistrarBeanPostProcessor();
    }

    @Controller
    public static class TestController {

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

        /
         * 处理error请求
         */
        @RequestMapping("/error-666")
        @ResponseBody
        public Map<String, Object> error(HttpServletRequest request) {
            Exception error = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
            System.out.println(error + ">>>>" + error.getClass());
            Map<String, Object> map = new HashMap<>(1);
            map.put("error", "服务器错误异常:" + error.getMessage());
            return map;
        }
    }

    /
     * Spring Boot中处理异常方式
     * 使用BasicErrorController进行处理的
     */
    @Bean
    public BasicErrorController basicErrorController() {
        ErrorProperties errorProperties = new ErrorProperties();
        // 将异常对象添加到响应中
        errorProperties.setIncludeException(true);

        BasicErrorController controller = new BasicErrorController(
                // 错误信息封装在DefaultErrorAttributes中
                new DefaultErrorAttributes(),
                // 错误处理的参数配置在ErrorProperties中
                errorProperties);
        return controller;
    }

    /
     * 定制错误响应页面
     * 对浏览器Accept返回Html格式的请求进行处理
     * 1.定义视图名称,方法名称必须和转发的错误处理路径(error)一致
     * 2.定义视图解析器,根据Bean名称进行视图解析的解析器
     */
    @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().write("<h3>服务器响应错误" + model.get("exception") + "</h3>");
            }
        };
    }

    @Bean
    public ViewResolver viewResolver() {
        // 根据Bean名称进行视图解析的解析器
        return new BeanNameViewResolver();
    }

}

8.处理器映射器和处理器适配器

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;

/
 * 测试Spring中的HandleMapping
 *
 * @author lzlg
 * 2023/4/3 19:00
 */
public class TestSpringHandleMapping {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context =
//                new AnnotationConfigServletWebServerApplicationContext(TomcatWebConfig.class);
//                new AnnotationConfigServletWebServerApplicationContext(CustomWebConfig.class);
//                new AnnotationConfigServletWebServerApplicationContext(FunctionWebConfig.class);
                new AnnotationConfigServletWebServerApplicationContext(StaticResourceWebConfig.class);
        /
         * HandlerMapping: 负责建立请求与控制器之间的映射关系,有顺序要求,优先级如下:
         *   1.RequestMappingHandlerMapping (处理@RequestMapping注解)
         *   2.WelcomePageHandlerMapping (处理/的欢迎页请求)
         *   3.BeanNameUrlHandlerMapping (与Bean的名字匹配,Bean的名字以/开头)
         *   4.RouterFunctionMapping (函数式RequestPredicate, HandlerFunction)
         *   5.SimpleUrlHandlerMapping (处理静态资源,通配符 /, /img/)
         *
         * HandlerAdapter: 负责实现对各种各样的Handler的适配调用,典型的适配器模式体现
         *   1.RequestMappingHandlerAdapter: 处理@RequestMapping注解标注的方法,参数解析器,返回值处理器体系了组合模式
         *   2.SimpleControllerHandlerAdapter: 处理实现Controller接口的控制器
         *   3.HandlerFunctionAdapter: 处理HandlerFunction函数式接口
         *   4.HttpRequestHandlerAdapter: 处理HttpRequestHandler接口(静态资源处理)
         */
    }
}

import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/
 * Web配置类
 *
 * @author lzlg
 * 2023/4/3 19:01
 */
@Configuration
public class TomcatWebConfig {
    /
     * Tomcat的Servlet容器
     */
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    /
     * Spring中转发请求的Servlet
     */
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    /
     * 将DispatcherServlet注入到Tomcat的容器中的Bean
     */
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        // 启动时就加载
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }

    /
     * 根据Bean的名称作为映射的HandlerMapping
     */
    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
        return new BeanNameUrlHandlerMapping();
    }

    /
     * 简单的控制器处理适配器,处理实现Controller接口的控制器方法的调用
     */
    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
        return new SimpleControllerHandlerAdapter();
    }

    @Component("/c1")
    static class TestController1 implements Controller {

        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().write("This is c1.");
            return null;
        }
    }

    @Component("/c2")
    static class TestController2 implements Controller {

        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().write("This is c2.");
            return null;
        }
    }

    @Bean("/c3")
    public Controller controller() {
        return (request, response) -> {
            response.getWriter().write("This is c3.");
            return null;
        };
    }
}

import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.function.*;
import org.springframework.web.servlet.function.support.HandlerFunctionAdapter;
import org.springframework.web.servlet.function.support.RouterFunctionMapping;

/
 * 函数式Web配置类
 *
 * @author lzlg
 * 2023/4/3 19:01
 */
@Configuration
public class FunctionWebConfig {
    /
     * Tomcat的Servlet容器
     */
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    /
     * Spring中转发请求的Servlet
     */
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    /
     * 将DispatcherServlet注入到Tomcat的容器中的Bean
     */
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        // 启动时就加载
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }

    /
     * 根据函数式路由匹配规则的处理器映射
     */
    @Bean
    public RouterFunctionMapping routerFunctionMapping() {
        return new RouterFunctionMapping();
    }

    /
     * 处理函数式匹配规则的控制器适配器
     */
    @Bean
    public HandlerFunctionAdapter handlerFunctionAdapter() {
        return new HandlerFunctionAdapter();
    }

    /
     * 注册函数式路由规则,请求路径为/r1
     */
    @Bean
    public RouterFunction<ServerResponse> r1() {
        // RequestPredicates.GET("/r1")表示匹配GET请求的/r1路径
        // HandlerFunction真正的处理请求方法(相当于Controller接口)
        return RouterFunctions.route(RequestPredicates.GET("/r1"), new HandlerFunction<ServerResponse>() {
            @Override
            public ServerResponse handle(ServerRequest request) throws Exception {
                return ServerResponse.ok().body("This is r1.");
            }
        });
    }

    /
     * 注册函数式路由规则,请求路径为/r2
     */
    @Bean
    public RouterFunction<ServerResponse> r2() {
        // RequestPredicates.GET("/r2")表示匹配GET请求的/r1路径
        // HandlerFunction真正的处理请求方法(相当于Controller接口)
        return RouterFunctions.route(RequestPredicates.GET("/r2"), new HandlerFunction<ServerResponse>() {
            @Override
            public ServerResponse handle(ServerRequest request) throws Exception {
                return ServerResponse.ok().body("This is r2.");
            }
        });
    }
}

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.resource.CachingResourceResolver;
import org.springframework.web.servlet.resource.EncodedResourceResolver;
import org.springframework.web.servlet.resource.PathResourceResolver;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.zip.GZIPOutputStream;

/
 * 静态资源处理Web配置类
 *
 * @author lzlg
 * 2023/4/3 19:01
 */
@Configuration
public class StaticResourceWebConfig {
    /
     * Tomcat的Servlet容器
     */
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    /
     * Spring中转发请求的Servlet
     */
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    /
     * 将DispatcherServlet注入到Tomcat的容器中的Bean
     */
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        // 启动时就加载
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }

    /
     * 用于处理静态资源的处理器映射,此映射没有实现初始化接口,故需自己添加请求处理器
     */
    @Bean
    public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        // 把ResourceHttpRequestHandler添加到URL集合中
        mapping.setUrlMap(context.getBeansOfType(ResourceHttpRequestHandler.class));
        return mapping;
    }

    /
     * 用于处理静态资源请求的处理器适配器
     */
    @Bean
    public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
        return new HttpRequestHandlerAdapter();
    }


    /
     * 欢迎页设置,通过请求根路径http://localhost:8080能转发到http://localhost:8080/index.html
     */
    @Bean
    public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {
        Resource resource = context.getResource("classpath:static/index.html");
        return new WelcomePageHandlerMapping(null, context, resource, "/");
    }

    /
     * 简单控制器处理器适配器,能够处理欢迎页的请求
     * 因WelcomePageHandlerMapping内置了ParameterizableViewController
     * 通过SimpleControllerHandlerAdapter能调用到ParameterizableViewController中处理请求的方法
     */
    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
        return new SimpleControllerHandlerAdapter();
    }

    /
     * 处理静态资源的处理器,用于处理类路径下的static目录下的静态资源文件
     * 注解@Bean标注的是处理器的请求路径
     */
    @Bean("/")
    public ResourceHttpRequestHandler handlerStatic() {
        ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
        handler.setLocations(Collections.singletonList(new ClassPathResource("static/")));
        // 添加资源请求解析器,是典型的责任链模式
        handler.setResourceResolvers(Arrays.asList(
                // 提供缓存静态资源的解析器
                new CachingResourceResolver(new ConcurrentMapCache("myCache")),
                // 提供压缩请求为gzip格式的资源解析器
                new EncodedResourceResolver(),
                // 资源路径解析器
                new PathResourceResolver()
        ));
        return handler;
    }

    /
     * 处理静态资源的处理器,用于处理类路径下的images目录下的静态资源文件
     * 注解@Bean标注的是处理器的请求路径
     */
    @Bean("/img/")
    public ResourceHttpRequestHandler handlerImage() {
        ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
        handler.setLocations(Collections.singletonList(new ClassPathResource("images/")));
        return handler;
    }

    @PostConstruct
    public void initGzip() {
        // 从类路径下获取资源信息
        ClassPathResource resource = new ClassPathResource("static");
        // 找到静态资源文件夹
        File dir;
        try {
            dir = resource.getFile();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (dir.exists()) {
            // 过滤出需要压缩的Html文件
            File[] htmlFiles = dir.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return pathname.getName().endsWith(".html");
                }
            });
            if (Objects.isNull(htmlFiles)) {
                System.out.println("类路径static下没有html文件");
                return;
            }
            for (File htmlFile : htmlFiles) {
                // 把html文件进行gzip压缩
                try (FileInputStream fis = new FileInputStream(htmlFile);
                     GZIPOutputStream gos = new GZIPOutputStream(Files
                             .newOutputStream(Paths.get(htmlFile.getAbsoluteFile() + ".gz")))) {
                    byte[] bytes = new byte[8 * 1024];
                    int len;
                    while ((len = fis.read(bytes)) != -1) {
                        gos.write(bytes, 0, len);
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }


    /
     * WelcomePageHandlerMapping
     */
    final static class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {

        private static final Log logger = LogFactory.getLog(WelcomePageHandlerMapping.class);

        private static final List<MediaType> MEDIA_TYPES_ALL = Collections.singletonList(MediaType.ALL);

        WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
                                  ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
            if (welcomePage != null && "/".equals(staticPathPattern)) {
                logger.info("Adding welcome page: " + welcomePage);
                setRootViewName("forward:index.html");
            } else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
                logger.info("Adding welcome page template: index");
                setRootViewName("index");
            }
        }

        private boolean welcomeTemplateExists(TemplateAvailabilityProviders templateAvailabilityProviders,
                                              ApplicationContext applicationContext) {
            return templateAvailabilityProviders.getProvider("index", applicationContext) != null;
        }

        private void setRootViewName(String viewName) {
            ParameterizableViewController controller = new ParameterizableViewController();
            controller.setViewName(viewName);
            setRootHandler(controller);
            setOrder(2);
        }

        @Override
        public Object getHandlerInternal(HttpServletRequest request) throws Exception {
            for (MediaType mediaType : getAcceptedMediaTypes(request)) {
                if (mediaType.includes(MediaType.TEXT_HTML)) {
                    return super.getHandlerInternal(request);
                }
            }
            return null;
        }

        private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) {
            String acceptHeader = request.getHeader(HttpHeaders.ACCEPT);
            if (StringUtils.hasText(acceptHeader)) {
                return MediaType.parseMediaTypes(acceptHeader);
            }
            return MEDIA_TYPES_ALL;
        }

    }
}

自定义处理器映射器和处理器适配器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.mvc.Controller;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/
 * 自定义Web配置类
 * 自定义实现HandlerMapping和HandlerAdapter接口
 *
 * @author lzlg
 * 2023/4/3 19:01
 */
@Configuration
public class CustomWebConfig {
    /
     * Tomcat的Servlet容器
     */
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    /
     * Spring中转发请求的Servlet
     */
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    /
     * 将DispatcherServlet注入到Tomcat的容器中的Bean
     */
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        // 启动时就加载
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }

    /
     * 自定义HandlerMapping
     * 扫描实现Controller接口的Bean,判断Bean名称是否前缀是否有/
     * 如果有则是合格的请求控制器,加入到控制器集合中
     */
    @Component
    static class MyHandlerMapping implements HandlerMapping {
        // 注入Spring容器对象,在调用MyHandlerMapping的初始化方法(@PostConstruct注解上的)之前已经实例化
        @Autowired
        private ApplicationContext context;

        private Map<String, Controller> controllerMap;

        /
         * 初始化方法获取Controller
         */
        @PostConstruct
        public void getController() {
            // 从容器中获取Controller这一类型的Bean的集合
            Map<String, Controller> map = context.getBeansOfType(Controller.class);
            // 找到那些Bean名称以/开头的Controller实例
            controllerMap = map.entrySet().stream()
                    .filter(e -> e.getKey().startsWith("/"))
                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        }

        @Override
        public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            // 获取请求URI
            String requestURI = request.getRequestURI();
            // 从Controller的集合中获取控制器
            Controller controller = controllerMap.get(requestURI);
            if (Objects.isNull(controller)) {
                return null;
            }
            // 返回控制器执行链对象
            return new HandlerExecutionChain(controller);
        }
    }

    /
     * 自定义HandlerAdapter
     * 找到指定的控制器进行请求的处理
     */
    @Component
    static class MyHandlerAdapter implements HandlerAdapter {
        @Override
        public boolean supports(Object handler) {
            // 实现Controller接口的都进行处理
            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);
            }
            // 返回null,表示不再进行视图渲染
            return null;
        }

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


    @Component("/c1")
    static class TestController1 implements Controller {

        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().write("This is c1.");
            return null;
        }
    }

    /
     * 这是不符合自定义HandlerMapping的控制器
     */
    @Component("c2")
    static class TestController2 implements Controller {

        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().write("This is c2.");
            return null;
        }
    }

    @Bean("/c3")
    public Controller controller() {
        return (request, response) -> {
            response.getWriter().write("This is c3.");
            return null;
        };
    }
}

9.Spring MVC处理流程

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

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

    • 路径:默认映射路径为 /,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器
      • jsp 不会匹配到 DispatcherServlet
      • 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
    • 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
    • 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
      • HandlerMapping,初始化时记录映射关系
      • HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
      • HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
      • ViewResolver
  2. DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法

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

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

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

  3. DispatcherServlet 接下来会:

    1. 调用拦截器的 preHandle 方法
    2. RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
      • @ControllerAdvice 全局增强点:补充模型数据
      • @ControllerAdvice 全局增强点:补充自定义类型转换器
      • 使用 HandlerMethodArgumentResolver 准备参数
        • @ControllerAdvice 全局增强点:RequestBody 增强
      • 调用 ServletInvocableHandlerMethod
      • 使用 HandlerMethodReturnValueHandler 处理返回值
        • @ControllerAdvice 全局增强点:ResponseBody 增强
      • 根据 ModelAndViewContainer 获取 ModelAndView
        • 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
          • 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null
        • 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程
    3. 调用拦截器的 postHandle 方法
    4. 处理异常或视图渲染
      • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
        • @ControllerAdvice 全局增强点:@ExceptionHandler 异常处理
      • 正常,走视图解析及渲染流程
    5. 调用拦截器的 afterCompletion 方法
Spring Boot
程序员内功
  • 作者:lzlg520
  • 发表时间:2023-04-11 17:45
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 公众号转载:请在文末添加作者公众号二维码