代理模式


静态代理

  静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

静态代理简单实现

  :假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长代理学生上交班费,班长就是学生的代理。

  首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。

 //创建Person接口
public interface Person {
    //上交班费
    void giveMoney();
}

  Student类实现Person接口。Student可以具体实施上交班费的动作。

//学生类,实现Person接口
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }

    @Override
    public void giveMoney() {
       System.out.println(name + "上交班费50元");
    }
}

  StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。

 //学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;

    public StudentsProxy(Person stu) {
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }

    //代理上交班费,调用被代理学生的上交班费行为
    public void giveMoney() {
        stu.giveMoney();
    }
}

  下面测试一下,看如何使用代理模式:

public class StaticProxyTest {
    public static void main(String[] args) {
        //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
        Person zhangsan = new Student("张三");

        //生成代理对象,并将张三传给代理对象
        Person monitor = new StudentsProxy(zhangsan);

        //班长代理上交班费
        monitor.giveMoney();
    }
}//output:张三上交班费50元

  这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。

  代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:

public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;

    public StudentsProxy(Person stu) {
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }

    //代理上交班费,调用被代理学生的上交班费行为
    public void giveMoney() {
        System.out.println("张三最近学习有进步!");
        stu.giveMoney();
    }
}//output:
            张三最近学习有进步
            张三上交班费50

动态代理

  代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:

 public void giveMoney() {
        //调用被代理方法前加入处理方法
        beforeMethod();
        stu.giveMoney();
    }

  这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果出了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。那看看下面动态代理如何实现。

动态代理简单实现

  在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

  首先定义一个Person接口

public interface Person {
    //交班费
    void giveMoney();
}

  创建需要被代理的实际类

public class Student implements Person{
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void giveMoney() {
        try {
            //假设数钱花了一秒时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "上交班费50元");
    }

}

  再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。

public class MonitorUtil {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void start(){
        threadLocal.set(System.currentTimeMillis());
    }

    //结束时打印耗时
    public static void finish(String methodName){
        long finish = System.currentTimeMillis();
        System.out.println(methodName+"方法耗时:"+(finish-threadLocal.get())+"ms");
    }
}

  创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

  再在invoke方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。

public class StuInvocationHandler<T> implements InvocationHandler {
    T target;//被代理的对象

    public StuInvocationHandler(T target) {
        this.target = target;
    }

    /**
     * proxy : 动态代理对象
     * method: 正在执行的方法
     * args  : 目标方法传入的实参
     *
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("正在代理执行"+method.getName()+"方法");
        //插入监测方法
        MonitorUtil.start();
        Object result = method.invoke(target,args);
        MonitorUtil.finish(method.getName());
        return result;
    }
}

  做完上面的工作后,我们就可以具体来创建动态代理对象

public class ProxyPattern {
    public static void main(String[] args) {
        //创建一个要被代理的实例对象
        Person zhangsan = new Student("zhangsan");

        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<>(zhangsan);

        //创建一个代理对象StuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy =(Person) Proxy.newProxyInstance(Person.class.getClassLoader(),new Class[]{Person.class},stuHandler);
        stuProxy.giveMoney();
    }
}output:正在代理执行giveMoney方法
        zhangsan上交班费50元
        giveMoney方法耗时:1001ms

  动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。


文章作者: kangshifu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kangshifu !
 上一篇
HashMap源码分析 HashMap源码分析
概述: hashmap差不多是由链表组成的数组,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置。当hash值相同时就放在已存在元素的后面形成链表,当链表长度太大时,链表就转换为红黑树。
2018-07-03
下一篇 
建造者模式 建造者模式
1. 重叠构造器模式在这种模式下,我们提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器含有所有参数。 public class Person { private
2018-06-21
  目录