第三十三讲-HandlerMapping与HandlerAdapter-1
第三十三讲-HandlerMapping与HandlerAdapter
1. Spring提供的BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter
我们之前使用SpringMVC的HandlerMapping与HandlerAdapter分别是:RequestMappingHandlerMapping
与RequestMappingHandlerAdapter
。接下来我们了解一组新的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
当然,访问c2
和c3
是同样的道理。这里呢,就不做演示了。
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);
}
}
启动测试访问
这里为什么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与之对应的控制器的映射。
分类:
Spring 高级49讲
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .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语句:使用策略模式优化代码结构