SpringMVC原理学习(一)RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter

一、 DispatcherServlet 初始化

1、DispatcherServlet 初始化时机

主启动类:

public class A20 {
    public static void main(String[] args) throws Exception {
        //支持内嵌 Tomcat 容器的 Spring 容器实现
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

配置类 

@Configuration
@ComponentScan
//指定资源文件读取的位置
@PropertySource("classpath:application.properties")
//让使用了 @ConfigurationProperties 注解的类生效,并且将该类注入到 IOC 容器中,交由 IOC 容器进行管理
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {

    // 内嵌 web 容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactory(serverProperties.getPort());
    }

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

    // 向 Tomcat 注册 DispatcherServlet, Spring MVC 的入口
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean =
                new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        return registrationBean;
    }
}

启动容器时:

第一次访问时:

DispatcherServlet初始化不是 Spring 管理 ,是 Tomcat 服务器在首次使用到 DispatcherServlet时,才由 Tomcat 服务器 进行初始化。

我们可以设置 LoadOnStartup ,让 Tomcat 启动时初始化 DispatcherServlet。

一旦设置大于 0 的值,就会在 Tomcat 启动时初始化。数字大小代表优先级,如果有多个 Servlet,数字小的优先级高。

    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean =
                new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }

2、DispatcherServlet 初始化都做了什么

DispatcherServlet 初始化时,会执行 onRefresh() 方法,从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化

public class DispatcherServlet extends FrameworkServlet {

    protected void onRefresh(ApplicationContext context) {
        this.initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }
}

二、RequestMappingHandlerMapping

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

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

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

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

1、初始化 HandlerMapping

​private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    // 该值默认为 true 但是可以设置为 false
    // 如果设置为 false 那  Spring MVC就只会查找名为“handlerMapping”的bean,并作为当前系统的唯一的HandlerMapping
    // 如果是 ture 则查询所有的..
    if (this.detectAllHandlerMappings) {
        //在ApplicationContext中查找所有handler映射,包括父类上下文。
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        //如果不为空
        if (!matchingBeans.isEmpty()) {
            // 将获取的 HandlerMapping 转换成集合..
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 排序
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            // 从容器中获取 HandlerMapping ,如果获取不到 下面则会添加默认的..
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }
 
    //通过注册,确保至少有一个HandlerMapping
    //如果找不到其他映射,则为默认的HandlerMapping。
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
 
    for (HandlerMapping mapping : this.handlerMappings) {
        if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }
}

​
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    if (defaultStrategies == null) {
        try {
            // 从配置文件加载默认的 简单的说 加载 DispatcherServlet.properties 这个文件...
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            // 默认的策略~~~
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
        }
    }
    //获取 org.springframework.web.servlet.HandlerMapping
    String key = strategyInterface.getName();
    // 配置文件中定义了三个 默认组件
    // org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
    // org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
    // org.springframework.web.servlet.function.support.RouterFunctionMapping
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        // 将 获取的 value 转换成数组
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        // 循环
        for (String className : classNames) {
            try {
                //  根据 路径获取 class
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                //  创建...
                Object strategy = createDefaultStrategy(context, clazz);
                // 添加到集合中
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {
               // 抛出异常
            }
            catch (LinkageError err) {
               // 抛出异常
            }
        }
        return strategies;
    }
    else {
        return new LinkedList<>();
    }
}
 

2、代码测试

配置类添加RequestMappingHandlerMapping,如果用 DispatcherServlet 初始化时默认添加的组件, 并不会放到 Spring 容器里,给测试带来困扰。

    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping(){
        return new RequestMappingHandlerMapping();
    }

 Controller类

@Controller
public class Controller1 {

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

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

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

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

    @RequestMapping("/test4")
    public User test4() {
        log.debug("test4");
        return new User("张三", 18);
    }

    public static class User {
        private String name;
        private int age;

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

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }

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

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

 主启动类

public class A20 {
    public static void main(String[] args) throws Exception {
        //支持内嵌 Tomcat 容器的 Spring 容器实现
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        //收集所有 @RequestMapping 映射信息,封装为 Map
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k, v) -> {
            System.out.println(k + " = " + v);
        });
    }
}

结果:

{GET [/test1]} = com.itheima.a20.Controller1#test1()
{PUT [/test3]} = com.itheima.a20.Controller1#test3(String)
{ [/test4]} = com.itheima.a20.Controller1#test4()
{POST [/test2]} = com.itheima.a20.Controller1#test2(String)

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

可以使用 MockHttpServletRequest 模拟请求, Spring 提供的用于测试使用

案例一

MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
request.setParameter("name", "张三");
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);

结果:

HandlerExecutionChain with [com.itheima.a20.Controller1#test2(String)] and 0 interceptors

案例二

MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
request.addHeader("token", "令牌");
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);

结果: 

HandlerExecutionChain with [com.itheima.a20.Controller1#test3(String)] and 0 interceptors

三、RequestMappingHandlerAdapter

RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:

  • HandlerMethodArgumentResolver 解析控制器方法参数
  • HandlerMethodReturnValueHandler 处理控制器方法返回值

1、初始化HandlerAdapter

源码和 初始化HandlerMapping 类似,默认添加的组件如下

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter

2、代码测试

继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个HandlerAdapter

    @Bean
    public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
        return handlerAdapter;
    }

使用 MyRequestMappingHandlerAdapter 原因:RequestMappingHandlerAdapter的 invokeHandlerMethod 方法作用域是 protected,反射调用太麻烦,就写了个子类,调用子类的方法即可。

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

主启动类

public class A20 {
    public static void main(String[] args) throws Exception {
        //支持内嵌 Tomcat 容器的 Spring 容器实现
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);

        System.out.println(">>>>>>>>>>>>>>所有的参数解析器");
        for (HandlerMethodArgumentResolver argumentResolver : handlerAdapter.getArgumentResolvers()) {
            System.out.println(argumentResolver);
        }

        System.out.println(">>>>>>>>>>>>>>所有的返回值解析器");
        for (HandlerMethodReturnValueHandler returnValueHandler : handlerAdapter.getReturnValueHandlers()) {
            System.out.println(returnValueHandler);
        }
    }
}

结果:

 调用 invokeHandlerMethod 执行 控制器方法

public class A20 {
    public static void main(String[] args) throws Exception {
        //支持内嵌 Tomcat 容器的 Spring 容器实现
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);

        //MockHttpServletRequest: Spring提供的模拟request对象,用于测试使用
        MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
        request.setParameter("name", "张三");

        MockHttpServletResponse response = new MockHttpServletResponse();
        //返回处理器执行链对象
        HandlerExecutionChain chain = handlerMapping.getHandler(request);
       
        handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());

    }
}

结果:

[DEBUG] 10:40:09.745 [main] com.itheima.a20.Controller1  - test2(张三) 

四、自定义参数与返回值处理器

自定义的注解:

// 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它
@Target(ElementType.PARAMETER) // 注解位置:方法参数上
@Retention(RetentionPolicy.RUNTIME) // 注解作用范围:运行期
public @interface Token {
}
// 与 @ResponseBody 注解功能类似,不过返回的是 yml 格式的字符串
@Target(ElementType.METHOD) // 注解位置:方法上
@Retention(RetentionPolicy.RUNTIME) //注解作用范围:运行期
public @interface Yml {
}

Controller类

@Controller
public class Controller1 {

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

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

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

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

    @RequestMapping("/test4")
    @Yml
    public User test4() {
        log.debug("test4");
        return new User("张三", 18);
    }

    public static class User {
        private String name;
        private int age;

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

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }

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

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

    }
}

1、自定义参数处理器

实现 HandlerMethodArgumentResolver 接口

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

    @Override
    //解析参数
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return webRequest.getHeader("token");
    }
}

将自定义的参数解析器加到handleAdapter 

    @Bean
    public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
        TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
        //将自定义的参数解析器加到handleAdapter
        handlerAdapter.setCustomArgumentResolvers(Arrays.asList(tokenArgumentResolver));
        return handlerAdapter;
    }

主启动类

public class A20 {
    public static void main(String[] args) throws Exception {
        //支持内嵌 Tomcat 容器的 Spring 容器实现
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);

        MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
        request.addHeader("token", "令牌");
        MockHttpServletResponse response = new MockHttpServletResponse();
        //返回处理器执行链对象
        HandlerExecutionChain chain = handlerMapping.getHandler(request);

        handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
    }
}

结果:

[DEBUG] 10:51:26.150 [main] com.itheima.a20.Controller1  - test3(令牌) 

2、自定义返回值处理器

实现 HandlerMethodReturnValueHandler 接口

public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        Yml yml = returnType.getMethodAnnotation(Yml.class);
        return yml != null;
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        //returnValue : 返回值对象
        //1. 转换返回结果为 yaml 字符串
        String str = new Yaml().dump(returnValue);

        //2. 将 yaml 字符串写入响应
        //获取原始的响应对象
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        //设置发送到客户端的响应的内容类型为文本格式
        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().print(str);

        //3.设置请求已经处理完毕
        mavContainer.setRequestHandled(true);
    }
}

将自定义的返回值解析器加到 handleAdapter

    @Bean
    public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter(){
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
        YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
        //将自定义的返回值解析器加到handleAdapter
        handlerAdapter.setCustomReturnValueHandlers(Arrays.asList(ymlReturnValueHandler));
        return handlerAdapter;
    }

主启动类

public class A20 {
    public static void main(String[] args) throws Exception {
        //支持内嵌 Tomcat 容器的 Spring 容器实现
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);

        MockHttpServletRequest request = new MockHttpServletRequest("Get", "/test4");
        MockHttpServletResponse response = new MockHttpServletResponse();
        //返回处理器执行链对象
        HandlerExecutionChain chain = handlerMapping.getHandler(request);

        handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());

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

    }
}

 结果:

!!com.itheima.a20.Controller1$User {age: 18, name: 张三}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring MVC是Spring框架中用于开发Web应用程序的模块,它的核心原理是基于前端控制器、处理器映射器、处理器适配器和视图解析器等组件的协作。 1. 前端控制器(Front Controller):Spring MVC的核心是一个Servlet,称为DispatcherServlet,它充当了应用程序的前端控制器。当请求到达应用程序时,DispatcherServlet负责接收请求并将其分派给相应的处理器。 2. 处理器映射器(Handler Mapping):处理器映射器负责将请求映射到相应的处理器(Controller)上。它根据请求的URL或其他条件来确定请求应该由哪个处理器来处理。 3. 处理器适配器(Handler Adapter):处理器适配器负责将请求发送给正确的处理器,并根据处理器的类型和方法来调用相应的方法进行处理。处理器适配器能够适配各种类型的处理器,包括带注解的控制器、基于接口的控制器等。 4. 控制器(Controller):控制器是Spring MVC应用程序的核心组件,它负责处理请求并返回相应的结果。控制器可以是一个普通的Java类,通过注解或配置来标识其为一个控制器,并定义处理请求的方法。 5. 视图解析器(View Resolver):视图解析器负责将处理器返回的逻辑视图名解析为实际的视图对象。它可以根据配置文件或约定来解析视图,将数据模型与视图进行合并,并最终生成响应结果。 6. 视图(View):视图是最终呈现给用户的界面,它可以是JSP、HTML、JSON、XML等格式。控制器将处理结果传递给视图,由视图负责生成最终的响应内容。 在整个请求处理过程中,前端控制器DispatcherServlet负责协调各个组件的工作,并提供了一种灵活的扩展机制,可以通过拦截器、异常处理器等进行功能增强和定制。Spring MVC原理是基于这些组件的协作,实现了请求的分发、处理和响应等功能,使得开发Web应用程序更加简单和高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鲁蛋儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值