众所周知在设计模式中有一个非常经典的模式叫做代理模式,而在JAVA语言中对代理模式又做了一个重要的补充,即动态代理。

由于有了动态代理,甚至出现了一种新的编程模式:面向切面编程,即所谓的AOP。

所以本文想要讨论的就是我们如何使用JAVA语言的动态代理特性来进行编码。

普通代理

先来温习一下什么是普通的代理模式,普通的代理模式的UML视图如下所示:

 undefined

图中3个角色分别为:

  • ISubject : 抽象主题角色,例如我们定义的业务抽象接口
  • RealSubject:具体主题角色,是被代理的对象,例如我们定义的业务具体的实现类
  • Proxy:代理主题角色,在代理模式中,我们都会使用代理角色来完成调用,而非直接使用具体主题角色。

说定义总是抽象的,我们还是结合实际场景和代码来描述。

假设有一个叫做张三,他去参加聚会想要结交新朋友,但是他这个人不会说话,于是他就找了自己的好哥们李四在他自我介绍的时候帮下忙做补充。

这下我们就有了3个角色+1个场景,分别是:

  • 人:具体点说是要去参加聚会的人,这是一个抽象主题角色,即ISubject。
  • 张三:张三是要去参加聚会的人,这是一个具体主题角色,即RealSubject。
  • 李四:是张三拉过来帮自己忙做介绍的,我们可以理解为李四代理张三做介绍,即Proxy。
  • 场景:一个聚会,参加聚会的人会做自我介绍。

用代码来描述的话,如下所示:

抽象主题:人

public interface IPerson {
    public void introduce();
}

具体主题:张三

public class Person implements IPerson {
    public Person(IPerson proxy) throws Exception {
        if (proxy == null) {
            throw new Exception("我需要有个好哥们当代理");
        }
    }
    public void introduce() {
        System.out.println("我的名字叫张三。");
    }
}

代理主题:李四

public class ProxyPerson implements IPerson {
    IPerson realSubject;
    public ProxyPerson() {
        try {
            // 好哥们张三由我李四代理
            this.realSubject = new Person(this);
        } catch (Exception e) {}
    }
    public void introduce() {
        System.out.println("初次见面");
        realSubject.introduce();
        System.out.println("可以请你喝杯东西吗?");
    }
}

场景:聚会

public class Party {
    public static void main(String[] args) {
        IPerson clientProxy = new ProxyPerson();
        clientProxy.introduce();
    }
}

运行Party的main函数,结果如下:

初次见面
我的名字叫张三。
可以请你喝杯东西吗?

可以看到,在李四的帮助下,张三的自我介绍变得丰富了起来,不再是只有干巴巴的一句“我的名字叫张三”。

这也是代理模式希望解决的问题,在不修改具体实现类的情况下实现一些增强操作。

聊完了普通代理模式以后,我们能发现普通代理模式有一个比较明显的特点,就是代理类的编写比较复杂,目前我们的IPerson接口只有1个方法,如果IPerson中有10个方法的话,由于我们的Proxy类实现了IPerson,因此也要实现10个方法,这样就为Proxy类的编写带来了困难。

动态代理

动态代理特性则很好地解决了上述问题,那么上面这个例子用动态代理来编写的话,是如下这样的:

首先自定义一个InvocationHandler

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

public class PersonInvocationHandler implements InvocationHandler {
    Object realSubject;

    public PersonInvocationHandler(IPerson realSubject) {
        this.realSubject = realSubject;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + "方法执行前...");
        Object result = method.invoke(this.realSubject, args);
        System.out.println(method.getName() + "方法执行后...");
        return result;
    }
}

为了展示跟前面例子的区别,我们稍稍修改下IPerson接口和Person类,这次张三不仅自我介绍,还要喝上一杯了。

IPerson

public interface IPerson {
    public void introduce();

    public void drink();
}

Person

public class Person implements IPerson {
    public Person() {}

    public void introduce() {
        System.out.println("我的名字叫张三。");
    }

    public void drink() {
        System.out.println("我先干为敬!");
    }
}

最后是聚会场景Party类

import java.lang.reflect.Proxy;

public class Party {
    public static void main(String[] args) {
        PersonInvocationHandler handler = new PersonInvocationHandler(new Person());
        IPerson proxy = (IPerson) Proxy.newProxyInstance(Person.class.getClassLoader(), Person.class.getInterfaces(), handler);
        proxy.introduce();
        proxy.drink();
    }
}

最后的运行结果如下所示

introduce方法执行前...
我的名字叫张三。
introduce方法执行后...
drink方法执行前...
我先干为敬!
drink方法执行后...

可以看到我们这次没有自己去手动编写代理类了,自然也无需手动实现introduce方法和drink方法,通过java反射包中提供的InvocationHandler接口以及Proxy类,我们动态地创建出了一个代理对象,并且在执行Person类的方法前后都做了一些处理,看到这里,是否就觉得有AOP那味儿了?

那么关于JAVA的动态代理初步介绍就先到这里为止,最后给自己留点思考题到下一篇文章中解答,也算是挖个坑。

如果将代码

IPerson proxy = (IPerson) Proxy.newProxyInstance(Person.class.getClassLoader(), Person.class.getInterfaces(), handler);

改成

Person proxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), Person.class.getInterfaces(), handler);

就会出现ClassCastException,这底下的原理是什么呢?

InvocationHandler是什么?

Proxy.newProxyInstance做了些什么?