第三十三讲-HandlerMapping与HandlerAdapter-1

第三十三讲-HandlerMapping与HandlerAdapter

1. Spring提供的BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter

我们之前使用SpringMVC的HandlerMapping与HandlerAdapter分别是:RequestMappingHandlerMappingRequestMappingHandlerAdapter。接下来我们了解一组新的BeanNameUrlHandlerMapping与SimpleControllerHandlerAdapter。我们来看一下这两者的使用:

@Configuration
public class WebConfig {
    @Bean // ⬅️内嵌 web 容器工厂
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory(8080);
    }

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

    @Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }

    // 做路径映射,只是不再去找RequestMapping,而是去找bean的名字,根据bean名字做路径映射
    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
        return new BeanNameUrlHandlerMapping();
    }

    // 做控制器方法调用,专只不过调用规则稍显不同,需要控制器类实现`Controller`接口,在实现方法中做一些请求,响应相关处理,在调用到控制器时就会调用该方法.
    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
        return new SimpleControllerHandlerAdapter();
    }

    // /c1  -->  /c1
    @Component("/c1")
    public static class Controller1 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().print("this is c1");
            return null;
        }
    }

    // /c2  -->  /c2
    @Component("/c2")
    public static class Controller2 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().print("this is c2");
            return null;
        }
    }

    // /c3 --> /c3
    @Bean("/c3")
    public Controller controller3() {
        return (request, response) -> {
            response.getWriter().print("this is c3");
            return null;
        };
    }
}

接着我们编写主方法测试一下:

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

        /*
            学到了什么
                a. BeanNameUrlHandlerMapping, 以 / 开头的 bean 的名字会被当作映射路径
                b. 这些 bean 本身当作 handler, 要求实现 Controller 接口
                c. SimpleControllerHandlerAdapter, 调用 handler

            对比
                a. RequestMappingHandlerAdapter, 以 @RequestMapping 作为映射路径
                b. 控制器的具体方法会被当作 handler
                c. RequestMappingHandlerAdapter, 调用 handler
         */
    }
}

我们打开浏览器访问一下/c1

image-20240819181742678

当然,访问c2c3是同样的道理。这里呢,就不做演示了。

2. 自定义实现HandlerMapping和HandlerAdapter

接下来,我们自己实现一个HandlerMapping和HandlerAdapter。

首先自定义一个处理器映射器:

/**
 * 用来收集路由与控制器之间的映射,我们应该在HandlerMapping初始化的时候收集控制器与路径之间的映射,
 * 而不是作为的懒加载
 *
 */
@Component
static class MyHandlerMapping implements HandlerMapping {
    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // 获取请求中的URI
        String key = request.getRequestURI();
        // 从初始化方法中匹配URI,然后返回对应的Controller
        Controller controller = collect.get(key);
        // 如果没有找到Controller,直接return-->会抛出一个404,没有对应的控制器
        if (controller == null) {
            return null;
        }
        // 将获取到的controller包装为一个HandlerExecutionChain对象
        // 此处步骤是为了结合相关的拦截器
        return new HandlerExecutionChain(controller);
    }

    @Autowired
    private ApplicationContext context;
    private Map<String, Controller> collect;

    @PostConstruct
    public void init() {
        // 从容器中获取实现Controller接口的类
        collect = context.getBeansOfType(Controller.class).entrySet()
                // 筛选出以'/'开头的controller
                .stream().filter(e -> e.getKey().startsWith("/"))
                .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
        System.out.println(collect);
    }
}

接下来自定义一个处理器适配器

// 用于对控制器的处理
@Component
static class MyHandlerAdapter implements HandlerAdapter {

    // 判断传过来的控制器是不是我适配器所能支持的
    // 此处判断条件很简单,就是判断传过来的对象是不是实现了Controller接口,然后返回true执行后续的处理
    // 否则返回false
    @Override
    public boolean supports(Object handler) {
        return handler instanceof Controller;
    }

    // 如果传过来的控制器实现了Controller接口,将该控制器转为Controller接口
    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof Controller controller) {
            // 转换成功就调用控制器中的handleRequest(req, res)方法
            controller.handleRequest(request, response);
        }
        // 此处返回null表示不走view视图渲染过程
        return null;
    }

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

完整的代码如下;

@Configuration
public class WebConfig_1 {
    @Bean // ⬅️内嵌 web 容器工厂
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory(8080);
    }

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

    @Bean // ⬅️注册 DispatcherServlet, Spring MVC 的入口
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }

    // /c1  -->  /c1
    // /c2  -->  /c2

    /**
     * 用来收集路由与控制器之间的映射,我们应该在HandlerMapping初始化的时候收集控制器与路径之间的映射,
     * 而不是作为的懒加载
     *
     */
    @Component
    static class MyHandlerMapping implements HandlerMapping {
        @Override
        public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            // 获取请求中的URI
            String key = request.getRequestURI();
            // 从初始化方法中匹配URI,然后返回对应的Controller
            Controller controller = collect.get(key);
            // 如果没有找到Controller,直接return-->会抛出一个404,没有对应的控制器
            if (controller == null) {
                return null;
            }
            // 将获取到的controller包装为一个HandlerExecutionChain对象
            // 此处步骤是为了结合相关的拦截器
            return new HandlerExecutionChain(controller);
        }

        @Autowired
        private ApplicationContext context;
        private Map<String, Controller> collect;

        @PostConstruct
        public void init() {
            // 从容器中获取实现Controller接口的类
            collect = context.getBeansOfType(Controller.class).entrySet()
                    // 筛选出以'/'开头的controller
                    .stream().filter(e -> e.getKey().startsWith("/"))
                    .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
            System.out.println(collect);
        }
    }


    // 用于对控制器的处理
    @Component
    static class MyHandlerAdapter implements HandlerAdapter {

        // 判断传过来的控制器是不是我适配器所能支持的
        // 此处判断条件很简单,就是判断传过来的对象是不是实现了Controller接口,然后返回true执行后续的处理
        // 否则返回false
        @Override
        public boolean supports(Object handler) {
            return handler instanceof Controller;
        }

        // 如果传过来的控制器实现了Controller接口,将该控制器转为Controller接口
        @Override
        public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (handler instanceof Controller controller) {
                // 转换成功就调用控制器中的handleRequest(req, res)方法
                controller.handleRequest(request, response);
            }
            // 此处返回null表示不走view视图渲染过程
            return null;
        }

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

    @Component("/c1")
    public static class Controller1 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().print("this is c1");
            return null;
        }
    }

    @Component("c2")
    public static class Controller2 implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            response.getWriter().print("this is c2");
            return null;
        }
    }

    @Bean("/c3")
    public Controller controller3() {
        return (request, response) -> {
            response.getWriter().print("this is c3");
            return null;
        };
    }
}

测试代码如下:

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

启动测试访问

image-20240819184329550

image-20240819184350276

image-20240819184401754

这里为什么c2返回的是404,原因是c2的控制器上的Bean的值不是以"/"开头的。在处理器映射的时候并没有建立路由与控制器之间的映射。

18:42:21.249 [main] DEBUG org.springframework.boot.web.servlet.ServletContextInitializerBeans - Mapping filters: 
18:42:21.250 [main] DEBUG org.springframework.boot.web.servlet.ServletContextInitializerBeans - Mapping servlets: dispatcherServlet urls=[/]
18:42:21.272 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'c2'
18:42:21.273 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean '/c1'
18:42:21.274 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.cherry.a33.WebConfig_1$MyHandlerAdapter'
18:42:21.274 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.cherry.a33.WebConfig_1$MyHandlerMapping'
18:42:21.284 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean '/c3'
{/c1=com.cherry.a33.WebConfig_1$Controller1@447a020, /c3=com.cherry.a33.WebConfig_1$$Lambda$193/0x000001c88718b330@7f36662c}
18:42:21.310 [main] DEBUG org.springframework.context.support.DefaultLifecycleProcessor - Starting beans in phase 2147483646
八月 19, 2024 6:42:21 下午 org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]

我们同样在控制台日志看出只有c1和c3与对应控制器的映射,并没有c2与之对应的控制器的映射。

posted @   LilyFlower  阅读(8)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示