适配器模式
定义
适配器模式是将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
通俗解释
用生活中的例子就是充电器的转接头或者数据线转接头,也就是两个类不兼容的情况下,通过适配器类来做到兼容。
举个例子
我看了网上很多人的博客,关于适配器模式的一些例子,主要有两种,一种叫类适配器,一种叫对象适配器。写完这两个例子后,我有种恍然大悟的感觉!
类适配器
首先有一个接口是目标接口PayService,目标方法pay()。
1 2 3 4
| public interface PayService { String pay(String channel, String amount) throws Exception; }
|
然后有一个被适配的类CheckHelper,适配方法checkedPay()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class CheckHelper { public boolean checkedPay(String channel, String amount) { try { int mount = Integer.parseInt(amount); List<String> channelList = Arrays.stream(PayEnum.values()) .map(PayEnum::getChannel) .collect(Collectors.toList()); return channelList.contains(channel) && mount > 0; } catch (Exception e) { return false; } } }
|
需求是要使得在接口PayService调用CheckHelper的checkedPay()方法,现在使用类适配器的方式演示:
1 2 3 4 5 6 7 8 9 10 11
| public class PayAdapter extends CheckHelper implements PayService { @Override public String pay(String channel, String amount) throws Exception { boolean checked = super.checkedPay(channel, amount); if (!checked) { return "支付失败,支付参数有误"; } return "支付成功,渠道为:" + channel + ",金额:" + amount; } }
|
其实就是使用继承的方式来完成,适配器类继承CheckHelper类,然后使用super来调用被适配类
CheckHelper的checkedPay()方法,一目了然了。
对象适配器
明显使用类适配器的方式不太灵活,因为java是单继承,所以我们可以改成成员变量的方式,也就是对象适配器。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class PayAdapter implements PayService { private CheckHelper checkHelper = new CheckHelper();
@Override public String pay(String channel, String amount) throws Exception { boolean checked = checkHelper.checkedPay(channel, amount); if (!checked) { return "支付失败,支付参数有误"; } return "支付成功,渠道为:" + channel + ",金额:" + amount; } }
|
那么肯定有人会说,你这样直接new一个对象不好,可以使用SpringIOC注入,于是又可以写成这样:
1 2 3 4 5
| @Component("checkHelper") public class CheckHelper { }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class PayAdapter implements PayService {
@Resource(name = "checkHelper") private CheckHelper checkHelper;
@Override public String pay(String channel, String amount) throws Exception { boolean checked = checkHelper.checkedPay(channel, amount); if (!checked) { return "支付失败,支付参数有误"; } return "支付成功,渠道为:" + channel + ",金额:" + amount; } }
|
然后有人可能已经开始察觉了,这不就是平时我们使用的依赖注入吗?没错!所以我开始就说了,写完这两个例子后,我恍然大悟了。原来适配器模式我们一直都在用,只是没认出来罢了。
总结一下
那么我们用适配器模式有什么优点呢?为什么要这样写:
1.解耦,降低了对象与对象之间的耦合性。
2.增加了类的复用,这点是比较重要的。
3.灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。这点我待会在下面SpringMVC的应用中详细说明。
在SpringMVC中的应用
我们都知道SpringMVC定义一个映射的方式很简单,使用@RequestMapping注解,如下所示:
1 2 3 4 5 6 7
| @RestController public class PayController { @RequestMapping("/pay") public String pay(String channel,String amount)throws Exception{ return ""; } }
|
实际上除了上面这种常用的方式外,还有其他的方式定义:
实现Controller接口
1 2 3 4 5 6 7
| @org.springframework.stereotype.Controller("/path") public class TestController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { return null; } }
|
实现HttpRequestHandler 接口
1 2 3 4 5 6 7 8 9 10
| @Controller("/httpPath") public class HttpController implements HttpRequestHandler {
@Override public void handleRequest(HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { } }
|
实现Servlet接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Controller("/servletPath") public class ServletController implements Servlet {
@Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { }
}
|
还要配置一个SimpleServletHandlerAdapter适配器的bean,因为默认只加载前面三种适配器,所以这种适配器需要自己手动添加。从这里也可以看出SpringMVC已经不推荐这种创建方式。
1 2 3 4 5 6 7 8
| @Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { @Bean public SimpleServletHandlerAdapter simpleServletHandlerAdapter() { return new SimpleServletHandlerAdapter(); } }
|
HandlerFunction接口,关于响应式接口的开发
最后一种是使用HandlerFunction函数式接口,这是Spring5.0后引入的方式,主要用于做响应式接口的开发,这里就不举例子了。后面我会写一篇文章再详述。
问题:以上就有五种方式定义Mapping映射,那么SpringMVC是如何去适配的呢?并且具有良好的扩展性和维护性呢?
源码分析
首先我们把目光放在DispatcherServlet类的doDispatch()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try { ModelAndView mv = null; Exception dispatchException = null;
try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request);
mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } catch (Exception ex) { } catch (Throwable err) { } } }
|
先不要慌张,其实学过策略模式你一眼就可以看出来,实际上这里就是运用了类似于策略模式的方式,根据不同的对象获取到对应的适配器,然后执行HandlerAdapter接口的handle()方法得到结果。
关键是这个getHandlerAdapter()方法,是怎么获取到对应的HandlerAdapter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
|
那么你看到上面这个this.handlerAdapters肯定会有疑问,handlerAdapters集合里面的适配器是什么时候初始化的?哪里初始化?继续看。
在DispatcherServlet的initStrategies()方法中有一堆初始化方法。
1 2 3 4 5 6 7 8 9 10 11 12
| protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
|
接着我们看initHandlerAdapters()方法
1 2 3 4 5 6 7 8 9 10 11 12
| private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.handlerAdapters == null) { this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default"); } } }
|
然后我们又去getDefaultStrategies()方法中看你会发现:
1 2 3 4 5 6
| protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { String key = strategyInterface.getName(); String value = defaultStrategies.getProperty(key); }
|
然后重点就在于这个defaultStrategies对象。我们继续看,很快看到了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;
static { try { 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()); } }
|
所以明显可以看到所有的适配器类都是写在DispatcherServlet.properties文件里了!默认加载这三种适配器。
1 2 3
| 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
|
得到结论:
适配器实现类是从DispatcherServlet.properties文件加载到内存中的。
HandlerAdapter接口
所以关键在于HandlerAdapter接口,接口信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface HandlerAdapter { boolean supports(Object handler); @Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; long getLastModified(HttpServletRequest request, Object handler);
}
|
学过策略模式的应该很清楚了,上面讲过有5种方式定义Mapping。
所以应该可以猜测HandlerAdapter接口有五个子类。打开类图:

果然是有五个实现的子类分别对应五种方式!
那么我们找其中一个实现类,比如最简单的SimpleControllerHandlerAdapter,来分析一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override public boolean supports(Object handler) { return (handler instanceof Controller); }
@Override @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); } @Override public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { return ((LastModified) handler).getLastModified(request); } return -1L; }
}
|
下面画一张图来总结一下以上的分析过程:

这不就像策略模式吗…只能解释为设计模式有很多都比较类似。假设SpringMVC要增加一种定义Mapping的方式,那就很容易了,增加对应的适配器实现类,对原有的代码没有任何的侵入,这就非常符合开闭原则。接下来我们就对适配器进行扩展,自定义一个适配器。
自定义SpringMVC适配器
首先要定义一个适配器MyHandlerAdapter,实现HandlerAdapter接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class MyHandlerAdapter implements HandlerAdapter {
@Override public boolean supports(Object handler) { return handler instanceof MyController; }
@Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((MyController) handler).handleRequest(request, response); }
@Override public long getLastModified(HttpServletRequest request, Object handler) { return -1; } }
|
接着定义一个MyController接口。
1 2 3 4 5 6 7
| public interface MyController {
@Nullable ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; }
|
注册适配器到Spring容器中。
1 2 3 4 5 6 7 8 9
| @Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { @Bean public MyHandlerAdapter myHandlerAdapter() { return new MyHandlerAdapter(); } }
|
最后创建一个MyTestController实现MyController进行测试。
1 2 3 4 5 6 7 8 9
| @Controller("/myTest") public class MyTestController implements MyController {
@Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { response.getWriter().println("MyTestController Test success!!!"); return null; } }
|
启动项目,然后在浏览器输入访问地址,即可看到。

当你理解透彻之后,你就可以这样自定义一个适配器,来加深一下理解,验证之前的分析的正确性。
沉下心学习,才能跑得更快!
以上就是适配器模式的学习,更多的java技术分享,就关注java技术爱好者吧!
能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!