适配器模式与SpringMVC

定义

适配器模式是将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

通俗解释

用生活中的例子就是充电器的转接头或者数据线转接头,也就是两个类不兼容的情况下,通过适配器类来做到兼容。

举个例子

我看了网上很多人的博客,关于适配器模式的一些例子,主要有两种,一种叫类适配器,一种叫对象适配器。写完这两个例子后,我有种恍然大悟的感觉!

类适配器

首先有一个接口是目标接口PayService,目标方法pay()

public interface PayService {
	
    String pay(String channel, String amount) throws Exception;
}

然后有一个被适配的类CheckHelper,适配方法checkedPay()

public class CheckHelper {
    //检查支付渠道和支付金额
    public boolean checkedPay(String channel, String amount) {
        try {
            //字符串转成数字,如果出现转换异常返回fasle
            int mount = Integer.parseInt(amount);
            //PayEnum定义了一些支付渠道,比如支付宝、微信、银联等等
            List<String> channelList = Arrays.stream(PayEnum.values())
                .map(PayEnum::getChannel)
                .collect(Collectors.toList());
            //包含在支付渠道中,并且金额大于0,返回true,否则返回false
            return channelList.contains(channel) && mount > 0;
        } catch (Exception e) {
            return false;
        }
    }
}

需求是要使得在接口PayService调用CheckHelpercheckedPay()方法,现在使用类适配器的方式演示:

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来调用被适配类

CheckHelpercheckedPay()方法,一目了然了。

对象适配器

明显使用类适配器的方式不太灵活,因为java是单继承,所以我们可以改成成员变量的方式,也就是对象适配器。代码如下:

public class PayAdapter implements PayService {
    //使用成员变量
    private CheckHelper checkHelper = new CheckHelper();

    @Override
    public String pay(String channel, String amount) throws Exception {
        //调用CheckHelper的checkedPay()方法
        boolean checked = checkHelper.checkedPay(channel, amount);
        if (!checked) {
            return "支付失败,支付参数有误";
        }
        return "支付成功,渠道为:" + channel + ",金额:" + amount;
    }
}

那么肯定有人会说,你这样直接new一个对象不好,可以使用SpringIOC注入,于是又可以写成这样:

//注册到Spring容器中
@Component("checkHelper")
public class CheckHelper {
    
}
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注解,如下所示:

@RestController
public class PayController {
    @RequestMapping("/pay")
    public String pay(String channel,String amount)throws Exception{
        return "";
    }
}

实际上除了上面这种常用的方式外,还有其他的方式定义:

实现Controller接口

@org.springframework.stereotype.Controller("/path")
public class TestController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return null;
    }
}

实现HttpRequestHandler接口

@Controller("/httpPath")
public class HttpController implements HttpRequestHandler {

    @Override
    public void handleRequest(HttpServletRequest request,
                              HttpServletResponse response
    ) throws ServletException, IOException {
        //业务处理,页面跳转,返回响应结果等等
    }
}

实现Servlet接口

@Controller("/servletPath")
public class ServletController implements Servlet {
    //Servlet生命周期函数
    //重写init()方法
    //重写getServletConfig()方法

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
	//业务处理
    }

    //重写getServletInfo()方法
    //重写destroy()方法
}

还要配置一个SimpleServletHandlerAdapter适配器的bean,因为默认只加载前面三种适配器,所以这种适配器需要自己手动添加。从这里也可以看出SpringMVC已经不推荐这种创建方式。

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Bean
    public SimpleServletHandlerAdapter simpleServletHandlerAdapter() {
        return new SimpleServletHandlerAdapter();
    }
}

HandlerFunction接口,关于响应式接口的开发

最后一种是使用HandlerFunction函数式接口,这是Spring5.0后引入的方式,主要用于做响应式接口的开发,这里就不举例子了。后面我会写一篇文章再详述。

问题:以上就有五种方式定义Mapping映射,那么SpringMVC是如何去适配的呢?并且具有良好的扩展性和维护性呢?

源码分析

首先我们把目光放在DispatcherServlet类的doDispatch()方法

	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);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
				
                                //重点: 获取到对应的适配器
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                		//省略...
                		//重点: 调用HandlerAdapter接口的handle()方法,得到ModelAndView结果
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				//省略...
			}
			catch (Exception ex) {
				//省略...
			}
			catch (Throwable err) {
				//省略..
			}
		}
	}

先不要慌张,其实学过策略模式你一眼就可以看出来,实际上这里就是运用了类似于策略模式的方式,根据不同的对象获取到对应的适配器,然后执行HandlerAdapter接口的handle()方法得到结果。

关键是这个getHandlerAdapter()方法,是怎么获取到对应的HandlerAdapter

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
            		//这个handlerAdapters有全部的适配器,遍历handlerAdapters集合
			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集合里面的适配器是什么时候初始化的?哪里初始化?继续看。

DispatcherServletinitStrategies()方法中有一堆初始化方法。

	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
    		//这个就是初始化适配器的方法,handlerAdapters就是在这里初始化的
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

接着我们看initHandlerAdapters()方法

	private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;
		//省略...
		//如果为null,刚开始当然为null,所以加载handlerAdapters集合
		if (this.handlerAdapters == null) {
            		//关键又在于getDefaultStrategies方法
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
			}
		}
	}

然后我们去getDefaultStrategies()方法中,你会发现:

    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		String key = strategyInterface.getName();
        	//defaultStrategies中获取值,key就是HandlerAdapter.class对象
		String value = defaultStrategies.getProperty(key);
    		//省略...
	}

然后重点就在于这个defaultStrategies对象。我们继续看,很快看到了。

	//DispatcherServlet.properties文件名
	private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
	//Properties对象,全局变量
	private static final Properties defaultStrategies;

	static {
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            		//加载DispatcherServlet.properties文件
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
		}
	}

所以明显可以看到需要加载的适配器类都是写在DispatcherServlet.properties文件里了!默认加载这三种适配器。

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接口,接口信息如下:

public interface HandlerAdapter {
	
    	//子类去实现,用于判断上级接口
	boolean supports(Object handler);
	
    	//子类实现这个方法,返回响应的结果
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	
    	//判断是否使用浏览器缓存,返回-1表示不使用浏览器缓存
	long getLastModified(HttpServletRequest request, Object handler);

}

学过策略模式的应该很清楚了,上面讲过有5种方式定义Mapping

所以应该可以猜测HandlerAdapter接口有五个子类。打开类图:
在这里插入图片描述
果然是有五个实现的子类分别对应五种方式!

那么我们找其中一个实现类,比如最简单的SimpleControllerHandlerAdapter,来分析一下:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
    	//getHandlerAdapter()方法就会调用这个方法判断,然后返回对应的适配器实现类
    	//这里返回的就是SimpleControllerHandlerAdapter适配器
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		//执行Controller接口的handleRequest,也就是mapping映射的方法
		return ((Controller) handler).handleRequest(request, response);
	}
	
    	//判断是否使用浏览器缓存,返回-1表示不使用浏览器缓存
	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}

}

下面画一张图来总结一下以上的分析过程:
在这里插入图片描述
这不就像策略模式吗…只能解释为设计模式有很多都比较类似。假设SpringMVC要增加一种定义Mapping的方式,那就很容易了,增加对应的适配器实现类,对原有的代码没有任何的侵入,这就非常符合开闭原则。接下来我们就对适配器进行扩展,自定义一个适配器。

自定义SpringMVC适配器

首先要定义一个适配器MyHandlerAdapter,实现HandlerAdapter接口。

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) {
        //不使用浏览器缓存,返回-1
        return -1;
    }
}

接着定义一个MyController接口。

public interface MyController {
    /**
     * 处理请求
     */
    @Nullable
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

注册适配器到Spring容器中。

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {	
    //注册自定义的适配器
    @Bean
    public MyHandlerAdapter myHandlerAdapter() {
        return new MyHandlerAdapter();
    }
}

最后创建一个MyTestController实现MyController进行测试。

@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技术爱好者」,拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!!
在这里插入图片描述

能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,Spring MVC框架中使用了适配器模式适配器模式的作用是将不同类型的处理器适配到统一的接口上,使得框架能够统一处理不同类型的处理器。在Spring MVC中,HandlerAdapter就是一个适配器,它负责将不同类型的处理器适配到统一的Controller接口上,使得框架能够统一调用Controller的方法。 使用适配器模式的原因是为了解决不同类型的处理器调用方式的不确定性。如果直接调用Controller方法,就需要使用if-else语句来判断处理器的类型,然后执行相应的方法。这样的实现方式不仅不灵活,而且当需要扩展Controller时,就需要修改原来的代码,违背了开闭原则。 Spring MVC通过适配器模式来获取对应的Controller。它定义了一个适配接口,使得每一种Controller都有一种对应的适配器实现类。适配器代替Controller执行相应的方法。当需要扩展Controller时,只需要增加一个适配器类就可以完成Spring MVC的扩展。 以下是模拟代码实现适配器模式的示例: ```java // 定义Controller接口 public interface Controller { void handleRequest(); } // 定义具体的Controller实现类 public class UserController implements Controller { @Override public void handleRequest() { System.out.println("Handling user request"); } } // 定义适配器接口 public interface HandlerAdapter { boolean supports(Object handler); void handle(Object handler); } // 定义UserController的适配器实现类 public class UserControllerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return handler instanceof UserController; } @Override public void handle(Object handler) { UserController userController = (UserController) handler; userController.handleRequest(); } } // 定义DispatcherServlet类,用于调度请求 public class DispatcherServlet { private List<HandlerAdapter> handlerAdapters; public DispatcherServlet() { handlerAdapters = new ArrayList<>(); handlerAdapters.add(new UserControllerAdapter()); } public void doDispatch(Object handler) { for (HandlerAdapter adapter : handlerAdapters) { if (adapter.supports(handler)) { adapter.handle(handler); break; } } } } // 测试代码 public class Main { public static void main(String[] args) { UserController userController = new UserController(); DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.doDispatch(userController); } } ``` 以上代码演示了Spring MVC中适配器模式的实现。DispatcherServlet负责调度请求,根据不同的处理器类型选择对应的适配器来处理请求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值