title: 设计模式-代理模式
author: Xoni
tags:
Spring 框架中都用到了哪些设计模式?
Spring 框架中使用到了大量的设计模式,下面列举了比较有代表性的:
单例设计模式在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。使用单例模式的好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
代理设计模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring
事件驱动模型就是观察者模式很经典的一个应用。Spring
事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。适配器模式 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,就可以使用装饰者模式

在软件开发中,也有一种设计模式可以提供与代购网站类似的功能。由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。
代理模式是一种应用很广泛的结构型设计模式,而且变化形式非常多,常见的代理形式包括远程代理、保护代理、虚拟代理、缓冲代理、智能引用代理等,后面将学习这些不同的代理形式。
代理模式是一种对象结构型模式,在代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。
其实代理的一般模式就是静态代理的实现模式:首先创建一个接口(JDK代理都是面向接口的),然后创建具体实现类来实现这个接口,再创建一个代理类同样实现这个接口,不同之处在于,具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。

代理类和真实类的公用接口
public interface Subject {
void request();
}
实现公有的接口
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("真实请求");
}
}
代理请求,引入了真实类对象,对方法进行了增强。
public class Proxy implements Subject{
//真实请求接口
private Subject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
System.out.println("前置增强");
//转发真实请求
realSubject.request();
System.out.println("后置增强");
}
}
public class ProxyDemo {
public static void main(String[] args){
Proxy proxy = new Proxy();
proxy.request();
}
}
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。
动态代理需要实现InvocationHandler接口
/**
* 动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。
* 该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
*/
public class DynamicProxyHandler implements InvocationHandler {
// 目标对象
private Object targetObject;
/**
* 绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
* @param targetObject
* @return
*/
public Object newProxyInstance(Object targetObject){
this.targetObject=targetObject;
//该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
//第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
//第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
//第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
//根据传入的目标返回一个代理对象
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),this);
}
/**
* 关联的这个实现类的方法被调用时将被执行
* InvocationHandler接口的方法
* @param proxy 代理对象
* @param method 原对象被调用的方法
* @param args 表示方法的参数*
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强");
//调用目标方法
Object result = method.invoke(targetObject, args);
System.out.println("后置增强");
return result;
}
}
public class DynamicProxyDemo {
public static void main(String[] args){
DynamicProxyHandler dynamicProxyHandler=new DynamicProxyHandler();
UserManager userManager=(UserManager)dynamicProxyHandler.newProxyInstance(new UserManagerImpl());
UserManager userManager=new UserManagerImpl();
userManager.addUser("1111", "张三");
}
}
虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLIB了。CGLIB采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLIB动态代理均是实现Spring AOP的基础。
public class CglibProxy implements MethodInterceptor {
// 目标对象
private Object targetObject;
public Object getInstance(final Object target) {
this.target = target;
//CGLIB enhancer增强类对象
Enhancer enhancer = new Enhancer();
//设置代理类
enhancer.setSuperclass(this.targetObject.getClass());
//定义代理逻辑对象为当前对象 ,对象要继承MethodInterceptor。并实现逻辑方法intercept
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 代理逻辑方法
* @param proxy 代理对象
* @param method 方法
* @param args 方法参数
* @param methodProxy 方法代理
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("前置增强");
//真的是代理对象,不像JDk代理,还是真实对象
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("后置增强");
return result;
}
}
这里用了CGLIB的加强者Enhancer,通过设置超类的方法setSuperclass,然后通过setCallback方法设置哪个类为它的代理类。其中,参数为this就意味着是当前对象,那就要求用this这个对象实现接口Methodlnterceptor的方法intercept,然后返回代理对象。那么此时当前类的intercept方法就是其代理逻辑方法,其参数内容见代码注解,我们在反射真实对象方法前后进行了打印,CGLIB是通过如下代码完成的。
Object result=methodProxy.invokeSuper(proxy,args);
public class CglibProxyDemo {
public static void main(String arg[]) {
CglibProxy cglib = new CglibProxy();
Dog dog = (Dog) cglib.getInstance(Dog.class);
//调用代理对象的isAnimal()方法
dog.isAnimal();
}
}
传言的 CGLIB 比 jdk动态代理性能高出 10 倍
JDK 1.8
win10 i7
我们经过测试并不是这样,或者说以前是这样,大家可以测试下,我把结果贴出来
JDKProxy创建耗时:1
JDKProxy调用耗时:2
CjLibProxy创建耗时:110
CjLibProxy调用耗时:21
创建耗时JDK比CGLIB快了将近100倍
调用也比CGLIB快了将近十倍
所以使用代理的时候最好使用面向接口的JDK的动态代理。