设计形式之署理形式-从静态署理到动态署理

署理形式看似离咱们的开发比较远,不像战略形式工厂形式那么的直观,可是其实在咱们日常开发的许多场景都用到了署理形式,比如:Spring AOP使用了JDK动态署理Cglib动态署理Mybatis的插件使用了JDK动态署理

署理形式的优点

见名之意,署理形式的中心便是「署理方针」,这个署理方针相当于一个中介,而咱们被署理的方针叫做「方针方针」,你可以把它想象成委托人。中介起到的效果便是隔离了委托人和客户 (说不定委托人不想和客户碰头-安全性),让他们免去了繁琐的流程 (比如租房吧,租客和房东不需求碰头,他们只需求和房屋中介进行对接就行),这在代码中呢,就叫做解除了「调用方」和「方针方针」的的耦合度

综上,署理形式的优点如下:

  • 经过署理方针将真正的方针方针隔离起来,起到了必定的保护效果
  • 解除了调用方和被调用方的直接联络,必定程度上降低了体系的耦合度
  • 署理方针可以做方针方针供给功用外的一些工作,达到对方针功用的增强效果

在开发中,最常遇见的三种署理形式:静态署理、JDK动态署理、CGLIB动态署理。

静态署理

静态署理的角色:1、功用接口;2、被署理类;3、署理类

还是拿租房的比如来写对应的代码:

功用接口-把房子租出去

public interface RentOutHouse {
    void rentOut();  
}

被署理方-委托方,房东

public class Landlord implements RentOutHouse{
@Override  
public void rentOut() {  
    // 中心功用-租房子出去  
    System.out.println("我是房东,我有一个房子需求租出去");  
    }  
}

署理方针-中介

public class HouseProxy implements RentOutHouse{
// 中介要和房东直接接触  
private Landlord landlord;  
public HouseProxy(Landlord landlord) {  
    this.landlord = landlord;  
}  
@Override  
public void rentOut() {  
    // 中心功用前的功用增强
    System.out.println("我是中介,租房之前和房东签合同");  
    System.out.println("我是中介,租房之前和租房者签合同");  
    // 中心功用
    landlord.rentOut();  
    // 中心功用后的增强
    System.out.println("租出去了之后,我要多收取一些中介费");  
}

客户端-租客

public class Client {
    public static void process() {  
        HouseProxy houseProxy = new HouseProxy(new Landlord());  
        houseProxy.rentOut();  
    }  
    public static void main(String[] args) {  
        process();  
    }  
}

成果

我是中介,租房之前和房东签合同
我是中介,租房之前和租房者签合同
我是房东,我有一个房子需求租出去
租出去了之后,我要多收取一些中介费

静态署理的缺点:

1、每个被署理类都需求一个署理类与之对应,由于署理类中是要包含这个被署理类方针的,所以假如再来一个房东需求将自己的房子租出去,还需求创建一个新的中介去和这个新房东对接,而其实中介对于两个房东要做的增强工作是相同的,所以会形成冗余

2、 再有,假如我房东现在是把房子租出去了,假如我想撤销租借呢,这时候就需求顶层的那个接口再加一个撤销租借的抽象办法,这时候就芭比Q了,由于你一切的房东和房屋中介都需求去重写这个办法,hhh。

JDK动态署理

JDK动态署理运用了反射的技术,很好的处理了上述静态署理的缺乏,先看看代码和履行的成果:

功用接口-把房子租出去

public interface RentOutHouse {
    void rentOut();  
}

被署理方-委托方,房东

public class Landlord implements RentOutHouse{
@Override  
public void rentOut() {  
    // 中心功用-租房子出去  
    System.out.println("我是房东,我有一个房子需求租出去");  
    }  
}

办法阻拦器-InvocationHandler

public class MyInvocationHandler implements InvocationHandler {
    private Object object;  
    public MyInvocationHandler(Object object) {  
        this.object = object;  
    }  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        // 中心功用前的功用增强  
        System.out.println("我是中介,租房之前和房东签合同");  
        System.out.println("我是中介,租房之前和租房者签合同");  
        // 中心功用办法调用  
        Object invoke = method.invoke(object, args);  
        // 中心功用后的增强  
        System.out.println("租出去了之后,我要多收取一些中介费");  
        return invoke;  
    }  
}

客户端-租客

public class Client {
    public static void main(String[] args) {  
        RentOutHouse landlord = new Landlord();  
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(landlord);  
        RentOutHouse proxy = (RentOutHouse)Proxy.newProxyInstance(Landlord.class.getClassLoader(), Landlord.class.getInterfaces(), myInvocationHandler);  
        proxy.rentOut();  
    }  
}

留意,这儿的Proxy.newProxyInstance(Landlord.class.getClassLoader(), Landlord.class.getInterfaces(), myInvocationHandler);是用来生成署理方针的,三个参数分别是:1、类加载器;2、类的悉数接口;3、自定义的办法阻拦器;

InvocationHandler是办法阻拦器,它会将被署理类的办法进行阻拦,并加上自己的一些增强,中心便是它里边的invoke()办法。

我这儿写的InvocationHandler比较简单,其实可以在它里边的invoke()办法中做一些复杂的判别,比如 "rentout".equals(method.getName())来指定只有办法名为rentout时我才去进行增强。

这时你会说,不是说用到了反射么,在哪里?别急,先看看咱们实际生成的署理方针到底是个什么东东:

在租客类中打个断点

设计模式之代理模式-从静态代理到动态代理

看署理类的类型:@Proxy0@513,这个类你是找不到的,咱们来把它反编译出来,你就会知道反射用在哪里了~~~

/**
* 生成字节码  
*/  
private static void saveProxyClass(String path) {  
   byte[] $proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", Landlord.class.getInterfaces());  
   FileOutputStream fileOutputStream = null;  
   try {  
       fileOutputStream = new FileOutputStream(new File(path + "$Proxy0.class"));  
       fileOutputStream.write($proxy0s);  
   } catch (Exception e) {  
       throw new RuntimeException(e);  
   }finally {  
       try {  
           fileOutputStream.flush();  
           fileOutputStream.close();  
       } catch (IOException e) {  
           throw new RuntimeException(e);  
       }  
   }  
}

看看生成的class文件:

public final class $Proxy0 extends Proxy implements RentOutHouse {
   private static Method m1;  
   private static Method m2;  
   private static Method m3;  
   private static Method m0;  
   public $Proxy0(InvocationHandler var1) throws {  
       super(var1);  
   }  
   public final boolean equals(Object var1) throws {  
       try {  
           return (Boolean)super.h.invoke(this, m1, new Object[]{var1});  
       } catch (RuntimeException | Error var3) {  
           throw var3;  
       } catch (Throwable var4) {  
           throw new UndeclaredThrowableException(var4);  
       }  
   }  
   public final String toString() throws {  
       try {  
           return (String)super.h.invoke(this, m2, (Object[])null);  
       } catch (RuntimeException | Error var2) {  
           throw var2;  
       } catch (Throwable var3) {  
           throw new UndeclaredThrowableException(var3);  
       }  
   }  
   public final void rentOut() throws {  
       try {  
           super.h.invoke(this, m3, (Object[])null);  
       } catch (RuntimeException | Error var2) {  
           throw var2;  
       } catch (Throwable var3) {  
           throw new UndeclaredThrowableException(var3);  
       }  
   }  
   public final int hashCode() throws {  
       try {  
           return (Integer)super.h.invoke(this, m0, (Object[])null);  
       } catch (RuntimeException | Error var2) {  
           throw var2;  
       } catch (Throwable var3) {  
           throw new UndeclaredThrowableException(var3);  
       }  
   }  
   static {  
       try {  
           m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));  
           m2 = Class.forName("java.lang.Object").getMethod("toString");  
           m3 = Class.forName("com.hss.spring.jdkdynamic.RentOutHouse").getMethod("rentOut");  
           m0 = Class.forName("java.lang.Object").getMethod("hashCode");  
       } catch (NoSuchMethodException var2) {  
           throw new NoSuchMethodError(var2.getMessage());  
       } catch (ClassNotFoundException var3) {  
           throw new NoClassDefFoundError(var3.getMessage());  
       }  
   }  
}

像equals、hashCode那几个办法不看,咱们就看咱们署理的办法rentOut(),你会看到,当咱们调用署理方针的rentOut()办法的时候,它内部是走了super.h.invoke(this, m3, (Object[])null);,这儿,super是指父类,很简单看到是Proxy类,super.h又是什么?跟进去看看:

设计模式之代理模式-从静态代理到动态代理

可以看到,h其实便是咱们在Client里边经过 Proxy.newProxyInstance()生成署理方针时传入的自定义的 InvocationHandler !!!

知道了h是什么,再来看看这儿的m3是什么:

设计模式之代理模式-从静态代理到动态代理

哈哈,这儿便是经过反射拿到咱们接口的办法rentOut(),这样就把这个署理类方针搞懂了,最后一步,便是走到了InvocationHandlerinvoke()办法中:

设计模式之代理模式-从静态代理到动态代理

这便是JDK动态署理的悉数流程了,需求留意的是:JDK动态署理要求被署理的类必须完成一个接口。

那么问题来了:为什么JDK动态署理要求被署理的类要完成接口?

答案:首先,要生成署理方针,是必须去知道被署理的类有哪些办法的,可以让被署理类完成接口,或许署理类去承继被署理类,这样署理类就可以得到被署理类中的一切办法。而JDK动态署理之所以需求被署理类完成接口,咱们可以从反编译的class文件中看到,署理类方针现已承继了Proxy类,java它又是单承继的,所以说就要求被署理类去完成接口,让咱们可以成功的生成署理类。

CGLIB动态署理

CGLIB动态署理和JDK动态署理的本质差异便是:CGLIB不需求被署理类去完成接口(实不完成接口都可以)。

先引入cglib依赖

<dependency>
    <groupId>cglib</groupId>  
    <artifactId>cglib</artifactId>  
    <version>3.2.7</version>  
</dependency>

被署理方-委托方,房东

public class Landlord {
    @Override  
    public void rentOut() {  
        // 中心功用-租房子出去  
        System.out.println("我是房东,我有一个房子需求租出去");  
    }  
}

回调-Interceptor

public class MyInterceptor implements MethodInterceptor {
    @Override  
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {  
        // 中心功用前的功用增强  
        System.out.println("我是中介,租房之前和房东签合同");  
        System.out.println("我是中介,租房之前和租房者签合同");  
        Object obj = methodProxy.invokeSuper(o, objects);  
        System.out.println("租出去了之后,我要多收取一些中介费");  
        return obj;  
    }  
}

客户端-租客

public class Client {
    public static void main(String[] args) {  
        Landlord landlord = new Landlord();  
        // 署理类class文件存入本地磁盘便利咱们反编译查看源码  
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./p");  
        // 经过CGLIB获取署理方针  
        Enhancer enhancer = new Enhancer();  
        // 设置署理方针的父类  
        enhancer.setSuperclass(Landlord.class);  
        // 设置回调  
        enhancer.setCallback(new MyInterceptor());  
        Landlord landlordProxy = (Landlord) enhancer.create();  
        // 经过署理方针调用方针办法  
        landlordProxy.rentOut();  
    }  
}

可以看到全体的代码和JDK动态署理十分的类似,差异便是被署理类不做完成接口的要求,Interceptor替换了InvocationHandler,然后在Client中有部分的代码不相同。

中心办法和增强办法的逻辑都是写在Interceptor中的。

这时候你会问,不是说根据承继么,怎么体现的?

看看咱们生成的class文件(太多了,只看中心的部分):

设计模式之代理模式-从静态代理到动态代理
设计模式之代理模式-从静态代理到动态代理

内部调用了Interceptor的intercept()办法。

over!!!