Spring MVC 中的适配器模式

文章目录

Spring MVC 中的适配器模式

为什么不直接调用?

DispatcherServlet 为什么不直接调用 Controller ,而是通过 Adapter 来『间接 』调用 Controller ?

原因在于:Spring MVC 现在对于 Controller 并没有『统一』的要求

Spring MVC 不强求 Controller 必须继承某个类,或实现某个接口。这样就导致,各个 Controller 中的『请求处理方法 』的『样子长的并不一样』,这样,DispatcherServlet 在做请求的分发时,无法写出一致性代码,只能『具体情况具体分析』,例如:

java 复制代码
// 假设 HandlerMapping 直接返回的就是 Controller 。
Handler handler = handlerMapping.getHandler();

if (handler instanceof AHandler) {  
   ((AHandler)handler).xxx();
} 
else if (handler instanceof BHandler) { 
   ((BHandler)handler).yyy();
} 
else if (handler instanceof CHandler) {
   ((CHandler)handler).zzz();
} 
else if (...) {  
   ...  
}  
...

更要命的是,当你去自定义一个 Controller 之后,你需要去改 Spring MVC 的 DispatcherServlet 的源码!!! 去多加一个 else if (...) { ... }

很显然,这么干是不可能的。

这违反了设计模式中的开闭原则:对扩展开放,对修改关闭。

解决方案一:统一 Controller

上述问题的最简单直接的解决方案,就如同 Servlet API 一样,定义处一套统一的接口,要求 Spring MVC 框架的使用在在自定义 Controller 时,必须实现这个接口。这样就约定了每种 Controller 都有一种统一的使用方式。

早期的 Spring MVC 就是这么干的,当然,现在你也可以按照这种方式来使用 Spring MVC 框架。

java 复制代码
public class HelloController implements Controller {

    @Override
    public ModelAndView handleRequest(
            HttpServletRequest req, 
            HttpServletResponse resp) 
            throws Exception {
        ...

        return mav;
    }
}

这样,DispacherServlet 中的代码就能简化成如下这样:

java 复制代码
// 假设 HandlerMapping 直接返回的就是 Controller 。
Handler handler = handlerMapping.getHandler();

handler.handleRequest(req, resp);

这么干的缺点在于使用时不灵活,Spring MVC 框架的使用者受限于 Controller 接口,必须把代码写成『这种样子』。

因此,Spring MVC 并没有采取这种解决办法,而是采用的下面这种方案。

解决方案二:使用适配器模式

使用适配器模式的好处在于,Controller 可以『放飞自我』,Spring MVC 的使用者可以不受 Controller 接口的限制,以更方便的方式来编写自定义的 Controller 类。

DispacherServlet 如何调用 Controller 的问题,就由适配器来解决。

使用 Adapter 模式有 2 点需要注意的:

  1. Adapter 和 Controller 总是成对出现的。也就是说,Controller 的作者写 Controller 时『放飞自我』的代价就是,他要『多』写个 Adapter 。当然,由于 Adapter 可能会具有通用性,因此,Controller 类和 Adapter 类的数量关系也不至于是 1 : 1 的关系。

  2. Controller 的编写虽然可以『放飞自我』,但是 Adapter 的编写则是有要求的。

以下代码是伪代码,大家重点体会 Adapter 的用法和使用场景。

Spring MVC 会给出 Adapter 的接口要求:

java 复制代码
public interface HandlerAdapter {  
    public boolean supports(Object handler);  
    public void handle(Object handler);  
}  
  • .supports 方法的作用在于表示本 Adapter 支持调用哪个/哪些 Controller 。

  • .handle 方法的作用是 DispatcherServlet 和 Controller 之间的调用的『桥梁』。

Controller 的作者基于 Adapter 接口就可以写出与上面三个 Controller 配套的用于自家 Controller 的 Adapater:

java 复制代码
// 适用于 AController 的 Adapter
public class AHandlerAdapter implements HandlerAdapter {  
    public void handle(Object handler) {  
        ((AController)handler).xxx();  
    }  
  
    public boolean supports(Object handler) {  
        return (handler instanceof AController);  
    }  
}  
  
// 适用于 BController 的 Adapter
public class BHandlerAdapter implements HandlerAdapter {  
    public void handle(Object handler) {  
        ((BController)handler).yyy();  
    }  
  
    public boolean supports(Object handler) {  
        return (handler instanceof BController);  
    }  
}  
  
// 适用于 CController 的 Adapter
public class CHandlerAdapter implements HandlerAdapter {  
    public void handle(Object handler) {  
        ((CController)handler).zzz();  
    }  

    public boolean supports(Object handler) {  
        return (handler instanceof CController);  
    }  
  
}  

这样依赖,虽然 AController、BController 和 CController 的请求处理方法『长得不一样』,但是它们各自的 Adapter『长得一样』。

这样一来,DispacherServlet 向 HandlerMapping 要到的是 Adapter ,那么它就可以用一种统一的方式调用 Adapter,而各个 Adapter 再去调用各自适配的 Controller 。

DispatcherServlet 对 Adpater 的使用

模拟 DispatcherServlet

java 复制代码
public class DispatchServlet {  
      
    public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();   
      
    public DispatchServlet(){  
        handlerAdapters.add(new AHandlerAdapter());  
        handlerAdapters.add(new BHandlerAdapter());  
        handlerAdapters.add(new CHandlerAdapter());  
    }

    public void doDispatch() {  
          
        AController controller = new AController();  
//      BController controller = new BController();  
//      CController controller = new CController();  

        //得到对应适配器  
        HandlerAdapter adapter = getHandler(controller);  
        //通过适配器执行对应的controller对应方法  
        adapter.handle(controller);  
    }  

    public HandlerAdapter getHandler(Controller controller){  
        for (HandlerAdapter adapter: handlerAdapters) {  
            if (adapter.supports(controller)) {  
                return adapter;  
            }  
        }

        return null;  
    }  

    public static void main(String[] args) {
        new DispatchServlet().doDispatch();  
    }  
      
}  
相关推荐
吃汉堡吃到饱1 小时前
【Android】浅析MVC与MVP
android·mvc
一颗知足的心2 小时前
SpringCloud Alibaba五大组件之——Sentinel
spring·spring cloud·sentinel
wang_book5 小时前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
一个诺诺前行的后端程序员7 小时前
springcloud微服务实战<1>
spring·spring cloud·微服务
Tatakai2510 小时前
Mybatis Plus分页查询返回total为0问题
java·spring·bug·mybatis
掐指一算乀缺钱12 小时前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
bug菌¹13 小时前
滚雪球学SpringCloud[4.2讲]: Zuul:Netflix API Gateway详解
spring·spring cloud·gateway
小哇66613 小时前
spring-TransactionTemplate 编程式事务
数据库·spring
ChinaRainbowSea14 小时前
十三,Spring Boot 中注入 Servlet,Filter,Listener
java·spring boot·spring·servlet·web
小游鱼KF14 小时前
Spring学习前置知识
java·学习·spring