java框架学习日志-7(静态代理和JDK代理)

╄→гoц情女王★ 提交于 2020-03-17 15:12:23

某厂面试归来,发现自己落伍了!>>>

静态代理

我们平时去餐厅吃饭,不是直接告诉厨师做什么菜的,而是先告诉服务员点什么菜,然后由服务员传到给厨师,相当于服务员是厨师的代理,我们通过代理让厨师炒菜,这就是代理模式。代理模式需要三个东西:
真实对象——厨师(chef),用户真正需要去用到的对象。
调用者——客人(Client),需要用到真实对象的对象。
代理对象——服务员(waiter),代理真实对象与调用者沟通的对象。
厨师需要有个炒菜的方法,服务员负责代理厨师,所以也需要有一个炒菜方法,为了保证两个对象的炒菜方法一致,需要一个接口。

public interface Cook {
    public void cook(String name);//传入菜品名字
}

然后chef实现这个接口

public class Chef implements Cook{
    @Override
    public void cook(String name) {
        System.out.println("正在炒"+name);
    }
}

waiter也需要实现这个接口

public class Waiter implements Cook{
    Chef chef;

    public Waiter(Chef chef) { //通过构造函数与获取chef的实例对象
        this.chef = chef;
    }

    @Override
    public void cook(String name) { //自己不实现做菜,而是调用chef的方法
        chef.cook("鱼香肉丝");
    }
}

设置好后,客人就来点餐了

public class Client {

    public static void main(String[] args) {

        Chef chef=new Chef();
        Waiter waiter=new Waiter(chef);
        waiter.cook("鱼香肉丝");
    }
}


这里client没有直接调用chef的cook方法,而是通过waiter调用的cook方法。这样waiter就起到了代理的作用。代理的优点是可以让真实对象处理的业务更加纯粹,不再去关注一些公共的事情,公共的业务由代理来完成。
比如这个点餐的例子,可以让厨师专心做饭,不用去管点餐的事情,如果还要增加收钱功能,就可以让服务员去完成。

public class Waiter implements Cook{
    Chef chef;

    public Waiter(Chef chef) { //通过构造函数与获取chef的实例对象
        this.chef = chef;
    }

    @Override
    public void cook(String name) { //自己不实现做菜,而是调用chef的方法
        chef.cook("鱼香肉丝");
    }
    public void pay(){
        System.out.println("结账");
    }
}

动态代理

** 动态代理是spring的难点和重点。务必好好掌握。**

但是静态代理的缺点也很明显,多了代理类,工作量变大,开发效率降低。为了弥补这些缺点,就可以使用动态代理。动态代理又有几种实现
基于接口动态代理——jdk动态代理
基于类动态代理——cglib动态代理
还有用javasist来生成动态代理

先说jdk动态代理,动态代理只需要一个代理类就可以代理所有真实对象。静态代理是这么怎么做的呢?上面例子中,静态代理类(Waiter)代理了真实对象(Chef)。为了保证真实对象的方法(cook)与代理类的方法(cook)一致,所以需要都实现同一个接口(Cook),那如果还需要代理其他类呢?比如代理老板收钱,又需要实现pay接口,代理会计算账,又需要实现reckon接口。就会很麻烦。

动态代理只需要代理类实现一个InvocationHandler接口。就可以在代理类里面动态设置代理对象。API里InvocationHandLer是这样描述的



InvocationHandler是由代理实例的调用处理程序实现的接口 。 在这个例子中,代理实例就是指服务员(waiter),调用处理程序指调用厨师(chef)的做菜(cook),就是chef.cook("鱼香肉丝")。这里还提到了invoke方法



处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。结合例子,当你想点餐(处理代理实例上的方法)时,就可以调用这个方法。然后结果点餐结果也会告诉你(并返回结果),因为这个方法可以代理很多真实对象,所以返回的Object。
还有一个需要了解的Proxy类,API中的描述



注意这里断句,Proxy提供了创建动态代理类和实例,的静态方法。这里的静态方法就是



第一个参数——类加载器,java去执行某一个类的时候,需要将这个.clss文件加载到java虚拟机里面去,这个加载的过程就需要类加载器去加载。 第二个参数——要实现接口的列表,接口也有class。所以是class<?>[]。
第三个参数——这个InvocationHandler就和之前的联系上了。
大概了解后可以配合代码理解了。我们在上面点餐的例子上更改。动态代理指动态生成的代理类,因此接口(Cook)依然存在,真实对象(Chef)也存在。然后是代理类(waiter),代理类需要去实现InvocationHandler接口,因为可以实现动态代理了,稍微改个名字Waiter2。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Waiter2 implements InvocationHandler{
    private Cook cook;//真实对象,因为chef实现了Cook,所以创建cook就可以了

    public void setCook(Cook cook) {
        this.cook = cook;
    }


    /**
     *代理方法逻辑
     * @param proxy 代理对象(chef)
     * @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
     * @param args 当前方法的参数 (菜品名)
     * @return 返回代理结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj=method.invoke(cook,args);//第一个参数是真实对象
        return obj;
    }

    /**
     * 生成代理类
     * @return 返回代理类
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),cook.getClass().getInterfaces(),this);
    }
}

详细解释

private Cook cook;//真实对象,因为chef实现了Cook,所以创建cook就可以了

    public void setCook(Cook cook) {
        this.cook = cook;
    }

代理对象依然因为需要代理真实对象,所以还是需要先创建真实对象,真实对象厨师Chef继承了接口Cook的烹饪方法cook,所以创建接口Cook就可以了。然后通过set方法来设置真实对象。

/**
     *代理方法逻辑
     * @param proxy 代理对象(chef)
     * @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
     * @param args 当前方法的参数 (菜品名)
     * @return 返回代理结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj=method.invoke(cook,args);//第一个参数是真实对象
        return obj;
    }

之前提到过,invoke是处理代理实例上的方法调用并返回结果,可以理解成让代理对象与真实对象建立逻辑关系。
这里的method.invoke()是通过反射去调用真实对象,第一个参数cook就是真实对象,第二个参数就是传的参数值。 然后返回代理结果就可以了。
之前还提到过Proxy有一个静态方法可以生成代理类。

/**
     * 生成代理类
     * @return 返回代理类
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),cook.getClass().getInterfaces(),this);
    }

newProxyInstance的第一个参数是类加载器,直接传入当前类的类加载器。
第二参数是接口,这里传入cook的接口。 第三个参数是InvocationHandler,本身这个类就实现了InvocationHandler接口,所以传入当前类。
然后是客户端

public class Client {
    public static void main(String[] args) {
        Chef chef=new Chef();  //获取真实对象的实例
        Waiter2 waiter2=new Waiter2();//获取代理的实例
        waiter2.setCook(chef); //设置代理去代理哪个真实实例
        Cook proxy=(Cook) waiter2.getProxy();//创建代理,getProxy返回的是object,所以转换一下。
        proxy.cook("宫保鸡丁");//通过代理去控制真实对象。
    }
}

Cook proxy=(Cook) waiter2.getProxy();这一行是用来创建代理的,proxy就相当于之前的服务员。创建为Cook类型是为了调用cook方法。之前的静态代理中,用的是Waiter waiter去创建代理,因为Waiter也实现了Cook接口。所以可以用Waiter类型。
运行结果

如果要添加公共方法到代理对象中

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("来了,老弟");
        Object obj=method.invoke(cook,args);//第一个参数是真实对象
        return obj;
    }


之前说静态代理有个坏处,有很多类,动态代理可以解决这个问题。现在好像还看不出来。我们稍微改一下,让动态代理类可以代理所有类。
这个例子中代理的是厨师,也可以加入收钱功能,或者打扫卫生功能。真实对象怎么变,我们的目的不变,代理真实对象,那么就可以把Waiter中的真实对象Cook,换成Object。Waiter2代码如下

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Waiter2 implements InvocationHandler{
    private Object target;//真实对象

    public void setTarget(Object target) {
        this.target = target;
    }

    /**
     *代理方法逻辑
     * @param proxy 代理对象(chef)
     * @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
     * @param args 当前方法的参数 (菜品名)
     * @return 返回代理结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("服务员,麻烦你");//公共方法
        Object obj=method.invoke(target,args);//第一个参数是真实对象
        return obj;
    }

    /**
     * 生成代理类
     * @return 返回代理类
     */
    public Object getProxy(){
        return  Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}

新增两个接口,打扫和付钱,和它们的接口实现类

public interface Clean {
    public  void clean();
}

public class CleanImpl implements Clean{
    public void clean(){
        System.out.println("打扫");
    }
}

public interface Pay {
    public void paymoney(int i);
    public void free();
}


public class PayImpl implements Pay{
    public void paymoney(int i){
        System.out.println("付钱诶!一共"+i);
    }
    public void free(){
        System.out.println("能不能免单");
    }
}

然后再客户端测试一下

public class Client {
    public static void main(String[] args) {
        Chef chef=new Chef();  //获取真实对象的实例
        Pay pay=new PayImpl();
        Clean clean=new CleanImpl();
        Waiter2 waiter2=new Waiter2();//获取代理的实例

        waiter2.setTarget(chef); //设置代理去代理哪个真实实例
        Cook cookproxy=(Cook) waiter2.getProxy();//创建代理,getProxy返回的是object,所以转换一下。
        cookproxy.cook("宫保鸡丁");//通过代理去控制真实对象。

        waiter2.setTarget(pay);
        Pay payproxy=(Pay) waiter2.getProxy();
        payproxy.paymoney(50);
        payproxy.free();

        waiter2.setTarget(clean);
        Clean cleanproxy=(Clean) waiter2.getProxy();
        cleanproxy.clean();
    }
}


这样这个代理类就可以代理各种各样的类。注意被代理的类必须实现接口,因为在newProxyInstance方法里面第二个参数就是传入真实对象的接口。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!