Java:一文带你了解JAVA代理模式(王境泽版)

646 阅读5分钟

写在前面

你可能听过JAVA中的代理模式,它是二十三种设计模式中的一种,一般分为静态代理动态代理。代理模式的好处是可以在不修改目标对象的源码的情况下,对目标对象的某个方法进行扩展。下面我们就通过实际的例子来带大家了解下代理模式

静态代理

静态代理实际上就是程序员手动写代理类代理类中持有需要代理的目标对象,并且实现了目标对象共同的接口。Talk is cheap!我们就拿最近爆红的王境泽表情包举个例子:

我们先写个吃饭的接口,里面有一个吃饭的方法:

//一个简单的吃饭接口
public interface IEatMeal {
    void eatMeal();
}

然后我们写一个实现类,在这里充当目标对象.

//这个是普通人吃饭,在这里充当目标对象,也就是需要被代理的对象
public class NormalPeople implements IEatMeal {
    @Override
    public void eatMeal() {
        System.out.println("吃饭~~~"); //简单的吃饭
    }
}

ok,下面我们来写代理类,也就是对原有的类的方法进行修饰,注意,代理类需要实现与目标对象相同的接口,并且内部持有目标对象的引用。

//我们请来了王境泽代理,代理普通人吃饭~
public class WJZProxy implements IEatMeal {

    private NormalPeople normalPeople; //需要代理的目标对象
    
    public WJZProxy(NormalPeople normalPeople) {
        this.normalPeople = normalPeople;
    }
    
    @Override
    public void eatMeal() {
        /*目标对象执行eatMeal()方法前的修饰*/
        System.out.println("我王境泽就是死,死外边,从这跳下去,不会吃你们一点东西!");
        normalPeople.eatMeal();//目标对象执行eatMeal()方法
        /*目标对象执行eatMeal()后的修饰*/
        System.out.println("哎,真香!");
    }
}

好了,我们看一哈在客户端的使用~

public class Main {
    public static void main(String[] args) {
        NormalPeople normalPeople = new NormalPeople();//建立目标对象
        WJZProxy wjzProxy = new WJZProxy(normalPeople);//建立代理对象并和目标对象关联
        wjzProxy.eatMeal();//调用代理对象的eatMeal()方法
    }
}

输出结果:

我王境泽就是死,死外边,从这跳下去,不会吃你们一点东西!
吃饭~~~
哎,真香!

以上的例子就是静态代理了,我总结了几个要点:

  • 目标对象代理类需要实现需要被代理方法的所属接口
  • 代理类持有目标对象的引用
  • 使用时调用代理类的接口方法

动态代理

静态代理很好理解吧,就是代理类目标对象实现相同接口然后在代理类中对目标类原有的方法进行加工修饰就行了。从上面的例子我们可以知道静态代理是需要开发者自己写代理类的,而动态代理则是在运行时动态的生成代理类。还是相同的例子,用动态代理实现:

//同样的需要代理的方法 以接口形式列出~
public interface IEatMeal {
    void eatMeal();
}

一样的目标对象类,只是简单的吃饭。

public class NormalPeople implements IEatMeal {
    @Override
    public void eatMeal() {
        System.out.println("吃饭~~~");
    }
}

那怎么写代理类呢?不是动态生成吗?虽然是动态生成,但是虚拟机并不知道你需要对要对目标对象的方法进行怎么样的修饰和加工,所以我们还是得写接口。

//写一个类实现InvocationHandler接口 重写invoke方法 并在这里写修饰加工的逻辑
public class WJZDynamicProxy implements InvocationHandler {

    Object target; //需要被代理的目标对象

    public WJZDynamicProxy(Object target) {
        this.target = target; //这里传入的是目标对象
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /*执行前的修饰*/
        System.out.println("我王境泽就是死,死外边,从这跳下去,不会吃你们一点东西!");

        /*这里不再像静态代理那样调用.
        method是一个方法对象.
        method.invoke(target, args) 相当于执行target的method对象对应的方法
        比如method对应的是eat方法,就执行eat方法,对应的是drink方法,就执行drink方法*/
        method.invoke(target, args);

        /*执行后的修饰*/
        System.out.println("哎,真香!");
        return null; //这个return 可以瞎写 不知道干啥的
    }
}

客户端测试下。

public class Main {
    public static void main(String[] args) {
        NormalPeople normalPeople = new NormalPeople();//建立目标对象
        /*动态生成代理对象*/
        IEatMeal iEatMeal = (IEatMeal) Proxy.newProxyInstance(normalPeople.getClass().getClassLoader(), //得到一个类加载器
                normalPeople.getClass().getInterfaces(), //得到目标对象的所有接口方法,也就表明只有接口方法可以被代理
                new WJZDynamicProxy(normalPeople)); //传入一个InvocationHandler对象,这里传入我们的实现类
        iEatMeal.eatMeal();
    }
}

输出结果:

我王境泽就是死,死外边,从这跳下去,不会吃你们一点东西!
吃饭~~~         
哎,真香!

可以看到,实现的效果是一毛一样的。可能到这里有的人有些疑问。

  1. 生成的类是咋样的,怎么可以被强转IEatMeal类型?

观察我们的参数,第二个参数传入的是目标对象的所有接口类对象。实际上,动态生成的类也实现了目标对象实现的所有接口,但是Proxy.newProxyInstance返回的是Object类型,所以我们想要调用IEatMeal的方法,必须把它转成IEatMeal类型。

2.动态代理看着比静态代理复杂,为啥还要用动态代理?

动态代理主要被应用于一些大型框架中,比如Android中的RetrofitJavaEE中的Spring等等,这些框架需要根据用户的注解或者xml配置文件动态的生成不同的类,如果使用静态代理,对于不同的配置,需要不同的代理类,代码量是巨大的。

最后

好啦,以上就是我对JAVA中代理模式的一点小小心得,准备去啃啃Retrofit源码,估计之后会对动态代理有更深的理解。