适配器模式(SpringMVC源码分析)

现实生活中的例子

1、不同国家的插座是有区别的,如果我们去国外旅游,需要带上国外的插头转换器,来能兼容国外的插座;
2、手机的耳机孔有圆头和扁头,如果扁头的耳机孔想接圆头的耳机就需要一个耳机的转换器;
在这里插入图片描述

上述所说的转换器,其实就是适配器;它是用来做兼容的;

基本概念

  1. 适配器模式(Adapter Pattern):将某个类的接口转换成客户端期望的另一个接口,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper);
  2. 适配器模式属于结构型模式;
  3. 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式;

适配器的3个角色

  • 目标 (target):最终使用的对象;

  • 适配者 (adaptee):做兼容处理的对象;

  • 被适配者 (adapter):需要被兼容的对象;

工作原理

  1. 适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容;

  2. 从用户的角度看不到被适配者;

  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法;

3种适配器模式

  • 类适配器模式
  • 对象适配器模式
  • 接口适配器模式

1、类适配器模式

以生活中充电器为例,充电器本身相当于适配者 (Adapter),220V 交流电相当于被适配者 (adapter),我们的目标(target) 想把220V交流电转成5V直流电;

代码实现:

  1. Voltage220V,被适配的类
public class Voltage220V {
	//输出220V的电压
	public int output220V() {
		int src = 220;
		System.out.println("电压=" + src + "伏");
		return src;
	}
}
  1. IVoltage5V,适配接口
public interface IVoltage5V {
	public int output5V();
}
  1. VoltageAdapter,适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {

	@Override
	public int output5V() {
		//获取到220V电压
		int srcV = output220V();
		int dstV = srcV / 44 ; //转成 5v
		return dstV;
	}
}
  1. Phone类,需要用到适配器进行兼容
public class Phone {

	//充电
	public void charging(IVoltage5V iVoltage5V) {
		if(iVoltage5V.output5V() == 5) {
			System.out.println("电压为5V, 可以充电~~");
		} else if (iVoltage5V.output5V() > 5) {
			System.out.println("电压大于5V, 不能充电~~");
		}
	}
}
  1. Client类,测试类
public class Client {

	public static void main(String[] args) {
		System.out.println(" === 类适配器模式 ====");
		Phone phone = new Phone();
		phone.charging(new VoltageAdapter());
	}

}

2、对象适配器模式

还是以上面的例子为例,这一次VoltageAdapter适配器类不再继承Voltage220V,而是以聚合的方式来代替继承,符合设计模式中的"合成复用原则";java是单继承机制,这样可以保留对象继承权;

代码实现:

  1. VoltageAdapter,适配器类
public class VoltageAdapter implements IVoltage5V {

	private Voltage220V voltage220V; // 组合
	
	//通过构造器,传入一个 Voltage220V 实例
	public VoltageAdapter(Voltage220V voltage220v) {
		this.voltage220V = voltage220v;
	}

	@Override
	public int output5V() {
		
		int dst = 0;
		if(null != voltage220V) {
			int src = voltage220V.output220V();//获取220V 电压
			System.out.println("使用对象适配器,进行适配~~");
			dst = src / 44;
			System.out.println("适配完成,输出的电压为=" + dst);
		}
		return dst;
	}
}
  1. Client类,测试类
public static void main(String[] args) {
    // TODO Auto-generated method stub
    System.out.println(" === 对象适配器模式 ====");
    Phone phone = new Phone();
    phone.charging(new VoltageAdapter(new Voltage220V()));
}

其他的类不需要修改,省略…

优点:
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承被适配者的局限性问题;

3、接口适配器模式
  1. 接口适配器模式(Default Adapter Pattern),也叫缺省适配器模式;

  2. 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),该抽象类的子类可有选择地覆盖父类的某些方法来实现需求;

  3. 适用于一个接口不想使用其所有的方法的情况;

代码实现:

  1. 定义一个接口
public interface InterfaceTest {
	public void m1();
	public void m2();
	public void m3();
	public void m4();
}
  1. 抽象类 AbsAdapter 将 InterfaceTest 的方法进行默认实现
public abstract class AbsAdapter implements InterfaceTest {

	//默认实现
	public void m1() {}
	public void m2() {}
	public void m3() {}
	public void m4() {}
}
  1. Client 调用接口,重写接口方法
public class Client {
	public static void main(String[] args) {
		
		AbsAdapter absAdapter = new AbsAdapter() {
			//只需要去覆盖我们 需要使用 接口方法
			@Override
			public void m1() {
				System.out.println("使用了m1的方法");
			}
		};
		
		absAdapter.m1();
	}
}

适配器模式在 SpringMVC 中的应用

springMVC处理请求流程图
在这里插入图片描述

  • 第1步 用户发送请求至前端控制器DispatcherServlet;
  • 第2,3步 DispatcherServlet收到请求,根据请求url调用HandlerMapping处理器映射器找到具体的处理器;生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
  • 第4步 DispatcherServlet通过HandlerAdapter处理器适配器调用具体的处理器;(这一步用到适配者模式)
  • 第5,6步 执行处理器(Controller,也叫后端控制器),返回ModelAndView;
  • 第7步 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
  • 第8步 DispatcherServlet将ModelAndView传给ViewReslover视图解析器(但是如果加上@responsebody注解,则返回值不通过viewResolver,而是直接返回object);
  • 第9步 ViewReslover解析后返回具体View;
  • 第10步 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中);
  • 第11步 DispatcherServlet响应用户。

SpringMVM 中的 HandlerAdapter(上图的第4步), 就使用了适配器模式;

SpringMVC源码:

DispatcherServlet类下有一个获得处理器适配器的方法:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
        //遍历所有的处理器适配器,找到支持处理当前handler的处理器适配器(HandlerAdapter是一个接口,其实返回的是一个实现HandlerAdapter的子类)
		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");
}

HandlerAdapter接口:

public interface HandlerAdapter {

	boolean supports(Object handler);

	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

	long getLastModified(HttpServletRequest request, Object handler);

}

实现HandlerAdapter子类有这些,使得每一种controller有对应的适配器实现类,每种controller有不同的实现方式;

在这里插入图片描述
在这里插入图片描述

使用 HandlerAdapter 的原因分析:
如果处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果直接调用 Controller 方法,就得不断使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,就得修改原来的代码,这样违背了 OCP 原则;

说明:

  • Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类;
  • 适配器代替 controller执行相应的方法;
  • 扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展;

适配者模式在线程中的应用【2022-02-17补充】

我们知道开启线程有3种方式;

  • 继承Thread类,重写run方法;

  • 实现Runnable 接口,重写run方法;

  • 实现Callable接口,重写call方法;

最常用的是RunnableCallable方法(因为对象是单继承多实现的,继承Thread意味着不能继承其他类,所以Thread 比较少用);而FutureTask对象的有参构造可以同时接收Runnable和Callable参数并执行线程任务;

FutureTask对象中的Runnable和Callable相互转换就用到了适配者模式;

public class ThreadTest {

    // 线程池,线程任务提交给线程池执行
    public static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {

        // 线程池运行Runnable方式
        FutureTask futureTask01 = new FutureTask(new Runnable () {
            @Override
            @SneakyThrows
            public void run() {
                System.out.println("线程池运行Runnable方式");
                Thread.sleep(3000);
            }
        },String.class);
        executorService.submit(futureTask01);

        // 线程池运行Callable方式
        FutureTask futureTask02 = new FutureTask(new Callable<String> () {
            @Override
            public String call() throws Exception {
                Thread.sleep(3000);
                System.out.println("线程池运行Callable方式");
                // 返回一句话
                return "线程池运行Callable方式返回:" + Thread.currentThread().getName();
            }
        });
        executorService.submit(futureTask02);
        System.out.println(futureTask02.get());
    }
}

接下来我们看FutureTask类的两个构造器;

// 使用 Callable 进行初始化
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

// 使用 Runnable 初始化,并传入 result 作为返回结果。
// Runnable 是没有返回值的,所以 result 一般没有用,置为 null 就好了
public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}
// Executors.callable方法
public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}
//RunnableAdapter类
static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

RunnableAdapter 组合了Runnable接口,并提供一个入参为Runnable的构造器,构造器返回的是RunnableAdapter对象,而RunnableAdapter实现了 Callable接口,就可以直接返回Callable对象;

所以RunnableAdapter类就是把runnable适配成callable的;

看到这里相信你已经理解了~

这是非常典型的适配模型,想要把 Runnable 适配成 Callable,首先要实现 Callable 的接口,接着在 Callable 的 call 方法里面调用被适配对象(Runnable)的方法。

当然也可以将Callable适配成Runnable类,那为什么不呢?因为Callable可以提供更多的方法,例如获取返回值;

所以线程池在执行任务时可以直接接收FutureTask对象,而不用针对 Runnable 和 Callable 两种情况提供不同的实现思路;


总结

  1. 三种命名方式,是根据被适配者是以怎样的形式给到适配者(在 Adapter 里的形式)来命名的;
  2. 3种适配器模式
  • 以类给到适配器,在适配者里,就是将被适配者当做类,他们是继承关系;
  • 对象适配器:以对象给到适配器,在适配者里,将被适配者作为一个对象聚合,他们是关联关系;
  • 接口适配器:以接口给到适配器,在适配者里,将被适配者作为一个接口实现,他们是实现关系;
  1. 适配器模式模式最大的作用是将原本不兼容的接口融合在一起工作;
  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值