Java模式-代理模式

175 阅读7分钟

代理模式

代理模式是常用的java设计模式,

特征:

  1. 代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
  2. 代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
  3. 简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

一.静态代理

原理:

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

实现:

这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。

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

/**
 * 创建Person接口
 * 代理类 和 被代理类 公共接口
 */
public interface Person {
    //上交班费
    void giveMoney();
}

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

 /**
  * 被代理类
  */
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 per) {
        // 只代理学生对象
        if(per.getClass() == Student.class) {
            this.stu = (Student)per;
        }
    }
    
    //代理上交班费,调用被代理学生的上交班费行为
    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();
    }
}

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

代理模式最主要的就是有一个公共接口(Person),一个被代理类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。也就是说 : 代理对象 = 增强代码 + 目标对象(原对象)。有了代理对象后,就不用原对象了

好处:

上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性是指不直接调用被代理类对象的方法,在代理类中添加一些语句。

就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到,在代理类中添加:

public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;
    
    public StudentsProxy(Person per) {
        // 只代理学生对象
        if(per.getClass() == Student.class) {
            this.stu = (Student)per;
        }
    }
    
    //代理上交班费,调用被代理学生的上交班费行为
    public void giveMoney() {
        System.out.println("张三最近学习有进步!");
        stu.giveMoney();
    }
}

只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程AOP,我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法(切点)所在类肯定就是被代理了,在代理过程中切入了一些其他操作

缺陷:

程序员要手动为每一个目标类编写对应的代理类。如果当前系统已经有成百上千个类,工作量太大了。


========================


二.动态代理

复习对象的创建:

所谓的Class对象,是Class类的实例,而Class类是描述所有类的,比如Person类,Student类

可以看出,要创建一个实例,最关键的就是得到对应的Class对象。只不过对于初学者来说,new这个关键字配合构造方法,隐藏太多细节。

分析到这里,貌似有了思路:

能否不写代理类,而直接得到代理Class对象,然后根据它创建代理实例(反射)

Class对象包含了一个类的所有信息,比如构造器、方法、字段等。如果我们不写代理类,这些信息从哪获取呢?代理类和被代理类实现同一个接口,目的是为了让代理对象和被代理(目标)对象内部结构的一致性,代理对象只关注增强代码。但接口是不能实例化的,怎么解决?

动态代理原理:

JDK提供了java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类,这两者相互配合,入口是Proxy。

Proxy: 有静态方法getProxyClass(ClassLoader, interfaces),传入类加载器和接口,返回代理Class对象。

接口本身不能实例化对象,而Proxy类中的getProxyClass()可以把接口interfaces的信息"复制"到一个新的Class对象中,该Class对象有构造器(接口的Class对象没有构造器),可以实例化。

静态代理

动态代理

所以,一旦我们明确接口,完全可以通过接口的Class对象,创建一个代理Class,通过代理Class即可创建代理对象。知乎某大神的理解就是: 用Class造Class

定义:代理类在程序运行时创建的代理方式被成为动态代理。

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

比如说,想要在每个代理的方法前都加上一个处理方法:

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

实现:

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

创建一个动态代理对象步骤:

public class Test{
    public void static main(String[]args){
     /**
      * 使用Proxy类的getProxyClass静态方法生成一个动态代理类`stuProxyClass`
      * 参数1:把Person接口加载进内存的类加载器
      * 参数2:可以直接写接口的Class对象 Person.class,即代理对象和目标对象的共同接口
      */
      Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
      Constructor<?> constructor1 = stuProxyClass.getConstructor(InvocationHandler.class);
      //反射创建代理实例
      Person stuProxy = (Person) constructor1.newInstance(new InvocationHandler(){
          @Override
          public Object invoke(Object Proxy, Method method, Object[] args){
              
          }
      });
    }
    
    /*
    当然,上面四个步骤可以通过Proxy类的newProxyInstances方法来简化:
    //创建被代理对象
    Person zs = new Student("张三");
    //创建一个与代理对象关联的InvocationHandler
    StuInvocationHandler<Person> stuHandler = new StuInvocationHandler<>(zs);
    //创建一个代理对象stuProxy来代理zs,代理对象的每个执行方法都会替换执行Invocation(即stuHandler)中的invoke方法
    Person stuProxy = (Person) Proxy.newProxyInstance(
        Person.class.getClassLoader(), 
        new Class<?>[]{Person.class}, 
        stuHandler);
        
    //代理执行上交班费的方法
    stuProxy.giveMoney();
    */
}

动态代理不像静态代理那样一目了然,代理对象并不是显而易见的。动态代理会中转一个InvocationHandler对象,调用里面的invoke()方法

静态和动态代理的区别
转载自知乎:zhuanlan.zhihu.com/p/53126004