samwellwang

samwellwang

coder
twitter

详解代理模式

写代码的过程中看到许多 Proxy 的类,知道是代理模式,也知道代码如何走下去。

写代码的过程中看到许多 Proxy 的类,知道是代理模式,也知道代码如何走下去。但是对于代理模式的细节和原理却不太懂。
通过网上的学习得知
代理模式是 Java 中常见的一种设计模式,根据创建代理类的时间点分为静态代理和动态代理。

静态代理#

静态代理就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的. class 文件就已经生成。
静态代理模式最主要的就是有一个公共接口,一个具体的类,一个代理类,代理类持有具体类的实例,代为执行具体类实例方法。

静态代理的例子#

根据我的想法编了一个小例子,座位明星肯定要出席活动,Jisoo 要出席香奈儿的活动,但是由于身体原因。现在有 Jennie 代为出席。假如就和 Jisoo 出席的效果一样。
首先创建一个 Person 接口

public interface Person{
    //出席活动
    public void attendance();
}


然后是 Idol 的实现类:

public class Idol implements Person{
    String name;
    int age;
    Idol(String n,int a){
        this.name=n;
        this.age=a;
    }
    //实现接口
    @Override
    public void attendance() {
        System.out.print(this.name+"出席了Channl活动");
    }

}


然后是 IdolProxy 代理类,持有 Idol 的对象。也和 Idol 对象一样实现了 Person Interface

public class IdolProxy implements Person{
    Idol idol;
    public    IdolProxy(Person stu){
        if(stu.getClass()==Idol.class) {
            this.idol=(Idol)stu;
        }
    }
    @Override
    public void attendance() {
        idol.attendance();

    }

}   

接下来就是测试类

public static void main(String[] args) {

    Person Jisoo=new Idol("Jisoo",18);
    Person Jennie=new IdolProxy(Jisoo);
    Jennie.attendance();

}

输出结果为:

Jisoo出席了Channl活动

没有通过 Jisoo 去参加活动,而是通过 Jennie 的代理去的,这就是代理模式;
有一个公共的接口(Person),一个实现类(Idol)和一个代理类(IdolProxy)都实现了接口,
代理类还包含一个实现类的实例对象。并且代理这个对象执行具体的方法。
代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。比如 AOP 的的一些功能

动态代理#

静态代理的时候代理类是已经实现的,就是我们写好的,而动态代理就是在程序运行时动态创建的代理类。事先没写代理类的实现。
主要的点就是在 Proxy.newProxyInstance (loader, interfaces, h); 这个方法,这个方法是 Proxy 下的静态方法,而 Proxy 是 Java.lang.reflect 下的类
这个方法就相当于对上面静态代理的代理类的一个抽象,他可以有我们代码指示去如何代理一个对象。下面通过上面的那个例子来简单实现以下:

动态代理的例子#

我们要实现的仍然是 Jennie 去代理身体不输入的 Jisoo 出席 Channal 的活动。我看了网上的大部分教程都是从上往下讲,从建立接口到实现 Invocation 接口的类
从我自己的理解来看,是从下网上理解较容易理解一些。那就直接从测试的类开始:

//创建一个实例对象,这个对象是被代理的对象
Person Jisoo = new Idol("Jisoo",18);

//创建一个与代理对象相关联的InvocationHandler,这个就相当于静态代理的代理类idolProxy了 只不过比他牛逼,他含有所有的类的对象是一个Object
InvocationHandler idolHandler = new IdolInvocationHandler<Person>(Jisoo);
//创建一个代理对象Jennie来代理Jisoo,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);
//代理执行方法
Jennie.attendance();

第一步 Person Jisoo = new Idol (“Jisoo”,18); 不需多说了创建一个实现 Person 接口的实例化对象。
关键就是第二步 InvocationHandler idolHandler = new IdolInvocationHandler (Jisoo);
这里面的 InvocationHandler 是 java.lang.reflect.InvocationHandler; 是 Java 提供的实现动态代理类的接口,我们需要实现这个接口:

public class IdolInvocationHandler<T> implements InvocationHandler{

       //invocationHandler持有的被代理对象
    T target;

    public IdolInvocationHandler(T target) {
       this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("11111");
        method.invoke(target, args);
        System.out.println("2222");
        return null;
    }

}

这个接口定义了 invoke 方法,invoke 就是援引,调用的意思。就是说所有被代理类的方法都通过这个方法调用。
如何获取的被代理类的方法呢?就是通过 Java 的反射机制。invoke 方法有三个参数,第一个就是被代理的对象,第二个是方法对象,第三个就是参数
接下来就是 :

Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);

这就生成了一个代理的对象 Jennie, Proxy.newProxyInstance (loader, interfaces, h) 包含三个参数,第一个是接口的加载器 ,第二个是代理接口的列表
第三个就是上面实现 Invocation 接口的类 IdolInvocationHandler 的对象 idolHandler,这样在执行 Jennie 的 attendance 方法的时候就会输出:

11111
Jisoo出席了Channl活动
2222

这样就实现了动态代理的功能。。注意:动态代理只能对接口进行代理,不能对类进行代理。究其原因就是 Java 只支持单继承,只有通过接口实现多继承的关系。
原理:
其实大概就是把接口复制出来,通过这些接口和类加载器,拿到这个代理类 cl。然后通过反射的技术复制拿到代理类的构造函数(这部分代码在 Class 类中的 getConstructor0 方法),最后通过这个构造函数 new 个一对象出来,同时用 InvocationHandler 绑定这个对象。

PS:

Person idolProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, (proxy,method,agrs)->{
    if(method.getName()=="attendance") {
        System.out.println("1111");
         method.invoke(Jisoo, args);
         System.out.println("2222");
         return null;
    }
    return null;
});

在创建动态代理对象的那步还可以这么写,用 java8?的新特性 Lmbada 表达式来写,就不用写 Invocation 的实现类了。

参考文章

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。