【Java必会】一个保姆与两只宠物的“代理”故事(动态代理)

文章导航:


文章目的:
1、介绍什么是代理
2、如何理解“代理”
3、代理有哪些实现方式

什么是代理?

在讲解Java代理之前我们先要搞明白什么是“代理”?

代理:法律上指以他人的名义,在授权范围内进行对被代理人直接发生法律效力的法律行为。
1.短时间代人担任职务
2.受委托代表当事人进行某种活动。如:诉讼、签订合同等
3.对为别人进行诉讼的人的认可
4.代理人的职务

如果对博主上面查询到的结果有异议,一律驳回!除非你给我搬出《新华词典》!

在《head first 设计模式》中也对Java的代理模式做出了解释:

代理模式: 为另一个对象提供一个替身或占位符以控制对这个对象的访问。

所以总的来说: 代理就是控制一个对象的访问,并替被代理者进行相应的行为。

代理模式在我们生活中也是非常常见的,博主忘记在哪个论坛上看过这样一个例子,非常形象,印象很深,说的是三国时期一个非常有名的代理事件—— 曹操挟天子以令诸侯!这个故事想必大家都知道,在这个事件中,汉献帝就是被代理者,曹操就是他的代理。曹操要想代替天子做某些事情,首先就必须把天子控制起来,天子想发一道诏书,必须经过曹丞相,下面文武百官想向天子提交奏折也必须经过曹丞相,这样一来曹操就控制了天子的访问,并代替他进行了一些行为。

当然,上面那个例子只是形容了代理模式中控制对象的访问,并不是说所有的代理都是像上面那个例子一样被迫进行的。即使不是非常准确的比喻,但是我们还是可以从上面那个例子中可以提取出两个要点:
1)代理拥有被代理的控制权
2)被代理对象需要进行的操作都要经过代理对象

下面我会通过两个案例进行相关讲解:

扫描二维码关注公众号,回复: 3459521 查看本文章

Java代理分为静态代理和动态代理,首先我们先说一下静态代理。

静态代理

博主呢养了两只宠物,一个小猫(Mark),一只小狗(Lucky),现在我们就从他们入手,我先创建一个动物接口,描述两只宠物的一些行为

package com.promonkey.biz;

/**
 * Created by promonkey on 2017/2/21.
 */
public interface AnimalBase {

    String eat();//吃东西

    String exercise();//运动
}

接下来就开始写两只小家伙的实现类了
小猫: 

package com.promonkey.biz.impl;

import com.promonkey.biz.PetBase;

/**
 * Created by promonkey on 2017/2/21.
 */
public class Cat implements PetBase{

    private String name;
    private int age;
    private int health;

    private static Cat cat;

    //因为我只有一只小猫,所以私有化构造器,写成了单例模式
    private Cat(String name, int age, int health) {
        this.name = name;
        this.age = age;
        this.health = health;
    }

    //Mark吃鱼
    public String eat() {
        return this.name + " eat fish!";
    }

    //Mark喜欢玩球类
    public String exercise() {
        return this.name + " paly ball!";
    }

    //获得Mark对象
    public static Cat getCat() {
        if (cat == null) {
            cat = new Cat("Mark", 2, 90);
        }
        return cat;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getHealthValue() {
        return health;
    }

    /**
     * 为了方便显示出动物的一些属性值,重写了toString方法
     */
    @Override
    public String toString() {
        return "-------\n" +
                "name:" + this.getName() +
                "\nage:" + this.getAge() +
                "\nhealth:" + this.getHealthValue() +
                "\n-------";
    }
}

---------------------

本文来自 AProMonkey 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/jdjdndhj/article/details/56291629?utm_source=copy 

小狗(写法和小猫的类似):

package com.promonkey.biz.impl;

import com.promonkey.biz.PetBase;

/**
 * Created by promonkey on 2017/2/21.
 */
public class Dog implements PetBase {

    private String name;
    private int age;
    private int health;

    private static Dog lucky;

    public Dog(String name, int age, int health) {
        this.name = name;
        this.age = age;
        this.health = health;
    }

    //Lucky 吃骨头
    public String eat() {
        return getName() + " eat bone!";
    }

    //Lucky 奔跑
    public String exercise() {
        return getName() + " run!";
    }

    public static Dog getDog() {
        if (lucky == null) {
            lucky = new Dog("Lucky", 5, 95);
        }
        return lucky;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getHealthValue() {
        return health;
    }

    @Override
    public String toString() {
        return "-------\n" +
                "name:" + this.getName() +
                "\nage:" + this.getAge() +
                "\nhealth:" + this.getHealthValue() +
                "\n-------";
    }
}

现在就可以让两个小家伙做一些动作了:
测试类:

 @Test
 public void testCat() {
    AnimalAbstract cat = Cat.getCat();//获得Mark对象
    System.out.println(cat);//输出Mark的一些属性值
    System.out.println(cat.eat());//Mark吃鱼
    System.out.println(cat.exercise());//Mark玩球

}

 @Test
public void testDog() {
    AnimalAbstract dog = Dog.getDog();//获得Lucky对象
    System.out.println(dog);//输出Lucky的一些属性值
    System.out.println(dog.eat());//Lucky吃骨头
    System.out.println(dog.exercise());//Lucky奔跑
}

添加需求:现在我需要记录两个小家伙的一些日常行为,比如什么时候吃东西了,什么时候运动了……就相当于一个日志记录。

有一个很直接的方法——让两个小家伙自己记录。然而,你如果有这样的宠物,请送我一打!即便是它们可以后天学会,但是我还得花大量时间去教会它们(重新编写内部程序)!况且我可能只是这两天心血来潮想知道他们的的行踪而已,过两天我可能就不想知道他们几时几分在干嘛,而是想知道其他的内容,难道我又去教?显然不会!像哥这么机智的银,肯定会去给它请一个管家,帮它记录自己的一些行为以及时间!

假如管家要管理我的小宠物,首先要知道小宠物所有的行为(重写eat(),exercise()),以及小宠物的属性值,还需要拥有小宠物的控制权(this.petProxy = pet)

管家——petProxy :

package com.promonkey.proxy;


import com.promonkey.biz.PetBase;

/**
 * Created by promonkey on 2017/2/21.
 */
public class PetProxy implements PetBase {

    //小家伙的管家——petProxy
    private PetBase petProxy;

    /**
     * 管家构造器
     *
     * @param pet 需要管家管理的宠物
     */
    public PetProxy(PetBase pet) {
        //让管家获得小家伙的所有权
        this.petProxy = pet;
    }

    //小家伙吃的行为;管家控制宠物吃东西需要的操作(控制访问)
    public String eat() {
        //this就是管家对象,由他负责记录(记录功能是他自己的)
        this.logger();
        //这里实际是小动物(Mark或lucky)的行为,前面已经把小家伙的控制权给了管家petProxy
        return this.petProxy.eat();
    }

    //小家伙运动的行为
    public String exercise() {
        logger();
        return this.petProxy.exercise();
    }

    public PetBase getAnimalProxy() {
        return petProxy;
    }

    public void logger() {
        System.out.println("当前时间:" + System.currentTimeMillis());
    }
}

现在两个小家伙的管理已经请过来了,现在测试一下它好不好用:

   @Test
    public void testCatProxy() {
        System.out.println("=====未请管理之前=====");
        //获得Mark对象
        PetBase mark = Cat.getCat();
        //输出Mark的一些属性值
        System.out.println(mark);
        //Mark吃鱼
        System.out.println(mark.eat());
        //Mark玩球
        System.out.println(mark.exercise());

        System.out.println("=====请管理后=====");
        //请了管理,现在把Mark交给管理员markPry
        PetBase markProxy = new PetProxy(mark);
        //输出管理员获得的小猫信息
        System.out.println(markProxy);
        //管理员控制小猫吃并记录
        System.out.println(markProxy.eat());
        //管理员控制小猫运动并记录
        System.out.println(markProxy.exercise());
    }

测试结果:
这里写图片描述

看来这个管理员还是不错的!小狗lucky的请自行测试,都一样。

这就是静态代理,之所以叫为“静态”,是因为使用这种模式时我们需要为任何一个需要被代理的接口创建代理类。

动态代理

此前我只需要记录我的小宠物的日常行为,所以只请一个管家。但是我现在还要记录我家保姆的工作情况,如果做得好需要适当的加价薪对吧,哈哈!但是现在就有一个问题了,我之前请的宠物管家只知道我的小宠物的行为
这里写图片描述

并且他只能接管我的小宠物
这里写图片描述

除此之外他对其他情况一概不知,更加不知道我家保姆需要做哪些事情。虽然保姆的行为我可以教他(给他加上保姆的行为方法),但是重点是他没有接管保姆的这一项功能(继承的是宠物的抽象类)。这样一来肯定就不能让他去记录了,我要想记录保姆的工作情况就必须再去请一个管家了,也就是说需要为我的保姆类写一个新的代理类。如果我还要记录我家花花草草的成长记录呢?也是需要重新请一个管家?于现实而言,我没有那么多money去请那么多管家,钱烧得慌!于开发而言,我没有那么多精力和时间去为每一个需要被代理的接口创建新的代理类。

那么有没有什么方法途径可以把这些事情全部交给一个管家去做呢?即节省了money,又提高了效率,岂不快哉!

答案肯定是有的!那就是“动态代理”!下面我们来完成上面所说的,除了记录小宠物,还要记录保姆李婶的工作情况。

保姆接口:

package com.promonkey.biz;

/**
 * Created by promonkey on 2017/2/22.
 */
public interface BabySitter {

    //照看小孩
    String childmind();

    //打扫卫生
    String sweep();

    //下厨
    String cook();

}
  • 保姆接口实现类 —— 李婶
package com.promonkey.biz.impl;

import com.promonkey.biz.BabySitter;

/**
 * Created by promonkey on 2017/2/22.
 */
public class BabySitterImpl implements BabySitter {

    private String name;//保姆姓名

    //家里只请一个保姆,没打算请其他保姆
    private BabySitterImpl(String name) {
        this.name = name;
    }

    public String childmind() {
        return this.name + " 照看小孩!";
    }

    public String sweep() {
        return this.name + " 打扫卫生!";
    }

    public String cook() {
        return this.name + " 下厨做饭!";
    }

    //取得"李婶"对象
    public static BabySitter getBabySitter(){
        return new BabySitterImpl("李婶");
    }
}

先看下李婶的正常工作情况:
这里写图片描述

接下来就要请一个”全能管家”了,既要记录小宠物的日常行为,还要记录李婶的工作情况。现在我们有两种途径可以请到这样的”全能管家”,一种是通过jdk,一种是通过cglib。

通过jdk请管家(jdk实现动态代理)

管家:

package com.promonkey.proxy;

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

/**
 * Created by promonkey on 2017/2/22.
 */
public class StewardInvocationHandler implements InvocationHandler {

    // 目标对象
    private Object object;

    /**
     * 构造方法
     *
     * @param object 需要被代理的对象(目标对象)
     */
    public StewardInvocationHandler(Object object) {
        super();
        this.object = object;
    }

    /**
     * 执行目标对象(被代理对象)的方法
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 管家在被管理对象(宠物或保姆)有行为操作时记录时间
        logger();
        // 执行被管理对象的操作
        Object result = method.invoke(object, args);
        return result;
    }

    /**
     * 获得目标对象的代理对象(获得管家对象)
     *
     * @return 返回代理对象(管家)
     */
    public Object getProxyObject() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), object.getClass().getInterfaces(), this);
    }

    //时间记录
    public void logger() {
        System.out.println(">>>>>Time: " + System.currentTimeMillis());
    }
}

使用jdk(实现InvocationHandler 接口)请到了我们需要的管家,现在进行测试,看这个管家是否合格:

package com.promonkey.test;

import com.promonkey.biz.BabySitter;
import com.promonkey.biz.PetBase;
import com.promonkey.biz.impl.BabySitterImpl;
import com.promonkey.biz.impl.Cat;
import com.promonkey.biz.impl.Dog;
import com.promonkey.proxy.StewardInvocationHandler;

/**
 * Created by promonkey on 2017/2/22.
 */
public class TestSteward {

    public static void main(String[] args) {

        // 需要被管家管理的对象
        PetBase cat = Cat.getCat();
        PetBase dog = Dog.getDog();
        BabySitter babySitter = BabySitterImpl.getBabySitter();

        // 管家对象(全能管家可以根据不同的管理对象进行“变身”)
        StewardInvocationHandler steward ;

        // 管理猫的时候
        System.out.println("=====管理Mark=====");
        steward = new StewardInvocationHandler(cat);
        PetBase catProxy = (PetBase) steward.getProxyObject();
        System.out.println(catProxy.eat());
        System.out.println(catProxy.exercise());

        // 管理狗的时候
        System.out.println("=====管理Lucky=====");
        steward = new StewardInvocationHandler(dog);
        PetBase dogProxy = (PetBase) steward.getProxyObject();
        System.out.println(dogProxy.eat());
        System.out.println(dogProxy.exercise());

        // 管理保姆的时候
        System.out.println("=====管理李婶=====");
        steward = new StewardInvocationHandler(babySitter);
        BabySitter sitterProxy = (BabySitter) steward.getProxyObject();
        System.out.println(sitterProxy.childmind());
        System.out.println(sitterProxy.sweep());
        System.out.println(sitterProxy.cook());
    }
}

测试结果:
这里写图片描述

nice!这不就是我们需要的“全能管家”吗!管家在手,天下我有!哈哈!

现在已经证实在jdk平台请我们需要的管家没问题,但是本着“货比三家”的原则,我们再看看其他途径请的管家是不是会优惠一点,或者“质量”好点呢!

通过cglib请管家(cglib实现动态代理)

通过cglib请管家的话我们还需要下载cglib这个“APP”才能请。
maven:

  <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>

jar下载链接:http://download.csdn.net/download/jdjdndhj/9760709

导好jar包后,我们就开始“请”管家了。

管家:

package com.promonkey.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Created by promonkey on 2017/2/22.
 */
public class CglibStewardProxy implements MethodInterceptor {

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 管家在被管理对象(宠物或保姆)有行为操作时记录时间
        logger();
        // 执行被管理对象的操作
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }

    /**
     * 获得目标对象的代理对象(获得管家对象)
     *
     * @return 返回代理对象(管家)
     */
    public Object getProxyObject(Class superClass,Class[] dataType,Object[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(this);
        enhancer.setSuperclass(superClass);
        return enhancer.create(dataType,args);
    }

    //时间记录
    public void logger(){
        System.out.println(">>>>>Time: " + System.currentTimeMillis());
    }
}

同样的,通过cglib请到了管家后,我们来测试一下这个管家是否合格!
测试:

package com.promonkey.test;

import com.promonkey.biz.PetBase;
import com.promonkey.biz.impl.Cat;
import com.promonkey.proxy.CglibStewardProxy;
import net.sf.cglib.proxy.Enhancer;

/**
 * Created by promonkey on 2017/2/22.
 */
public class TestCglibProxy {

    public static void main(String[] args) {
        CglibStewardProxy stewardProxy = new CglibStewardProxy();

        Enhancer enhancer = new Enhancer();

        // 管理猫的时候
        System.out.println("=====管理Mark=====");
        // Cat类中构造器参数类型
        Class[] cat_type = {String.class, int.class, int.class};
        // Cat类构造器参数
        Object[] cat_obj = {"Mark", 2, 90};
        // 获得代理对象
        PetBase catProxy = (PetBase) stewardProxy.getProxyObject(Cat.class, cat_type, cat_obj);
        System.out.println(catProxy.eat());
        System.out.println(catProxy.exercise());
    }
}

运行结果:
运行结果—报错

咦?这个cglib平台“不靠谱”呀,管家都没请到!看看报错信息, No visible constructors in class com.promonkey.biz.impl.Cat;在Cat类中没有可见的构造器!!!

是的,如果你还记得我们最开始是把Cat构造器私有化了,因为cglib需要使用构造器来获得被代理的对象(cglib的内部实现原理以后有时间我们再来详细研究一下),现在我们对之前的Cat,Dog,BabySitterImpl三个类进行稍微的改动,以便此次的测试。把三个类现有的构造器设置为public即可!

修改完后我们再次进行测试:

package com.promonkey.test;

import com.promonkey.biz.BabySitter;
import com.promonkey.biz.PetBase;
import com.promonkey.biz.impl.BabySitterImpl;
import com.promonkey.biz.impl.Cat;
import com.promonkey.biz.impl.Dog;
import com.promonkey.proxy.CglibStewardProxy;

/**
 * Created by promonkey on 2017/2/22.
 */
public class TestCglibProxy {

    public static void main(String[] args) {

        CglibStewardProxy stewardProxy = new CglibStewardProxy();

        // 管理猫的时候
        System.out.println("=====管理Mark=====");
        // Cat类中构造器参数类型
        Class[] cat_type = {String.class, int.class, int.class};
        // Cat类构造器参数
        Object[] cat_obj = {"Mark", 2, 90};
        // 获得代理对象
        PetBase catProxy = (PetBase) stewardProxy.getProxyObject(Cat.class, cat_type, cat_obj);
        System.out.println(catProxy.eat());
        System.out.println(catProxy.exercise());

        // 管理狗的时候
        System.out.println("=====管理Lucky=====");
        Class[] dog_type = {String.class, int.class, int.class};
        Object[] dog_obj = {"Lucky", 5, 95};
        // 获得代理对象
        PetBase dogProxy = (PetBase) stewardProxy.getProxyObject(Dog.class, dog_type, dog_obj);
        System.out.println(dogProxy.eat());
        System.out.println(dogProxy.exercise());

        // 管理保姆的时候
        System.out.println("=====管理李婶=====");
        Class[] type = {String.class};
        Object[] obj = {"李婶"};
        // 获得代理对象
        BabySitter sitterProxy = (BabySitter) stewardProxy.getProxyObject(BabySitterImpl.class, type, obj);
        System.out.println(sitterProxy.childmind());
        System.out.println(sitterProxy.sweep());
        System.out.println(sitterProxy.cook());
    }
}

运行结果:
这里写图片描述

好了,这次终于通过cglib请来了管家!比较两个不同平台(jdk、cglib)“请”管家的方法,你感觉哪一个更顺手一点呢?

其中一个比较明显的比较:
这里写图片描述

这里写图片描述

两段代码可以看出,jdk实现动态代理,其被代理的对象必须要实现了某个接口,而cglib只是获取被代理对象的超类,没有要求被代理对象实现接口。

思考与总结

文章中我们介绍了静态代理和动态代理两种模式,你觉得他们的区别是什么?

静态代理和动态代理的区别: 静态代理需要手动为每一个被代理对象接口创建代理类;而动态代理不需要手动创建代理类,在程序运行时可以动态生成。

看到这里,现在你有没有一点自己的理解,为什么使用代理,以及使用代理的好处和不足呢?

什么是代理:代理说白了就是控制一个对象的访问。这也是代理模式和修饰模式的最大区别,修饰模式是对一个对象添加行为,而代理是控制一个对象的访问。

为什么使用代理:在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

代理模式的不足:
1、代理对象通过持有被代理的引用,控制记录被代理方法的调用,但是,代理对象并不知道被代理对象内部实现的逻辑;
2、不清楚被代理对象在方法体中做了什么,所以注定代理对象在其方法体中只能做一些与被代理对象中的逻辑无关的一些操作。例如,日志记录,权限拦截,访问控制,异常拦截等通用性功能;


这篇博文主要是想介绍一下什么是代理,如何理解代理。至于其中一些具体的实现原理(比如cglib内部实现原理)我们之后再进行学习。文章中如若有不妥之处,欢迎大家指正交流!

--------------------- 本文来自 AProMonkey 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/jdjdndhj/article/details/56291629?utm_source=copy    ---------------------

猜你喜欢

转载自blog.csdn.net/weixin_39531549/article/details/82916776