Fork me on GitHub

Java编程思想———动态代理

什么是代理

代理是基本的设计模式之一,给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。

通过使用代理,通常有两个优点:

  • 可以隐藏委托类的实现。
  • 可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

静态代理

若代理类在程序运行前就已经存在,那么这种代理方式被称为静态代理,这种情况下的代理类通常都是我们在Java代码中定义的。通常情况下,静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。

1
2
3
4
5
6
7
/**
* 委托类和代理类都实现Sell接口
*/
public interface Sell {
void sell();
void ad();
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 委托类
*/
public class Vendor inplements Sell {
void sell() {
System.out.println("In sell method");
}
void ad() {
System.out.println("ad method");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 代理类
*/
public class Agent inplements Sell {
private Vendor vendor;

public Agent(Vendor vendor) {
this.vendor = vendor;
}

public void sell() {
vendor.sell();
}
public void ad() {
vendor.ad();
}
}

通过以上代码可以看出,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。

静态代理的局限在于运行前必须编写好代理类。

动态代理

代理类在程序运行时创建的代理方式被称为动态代理。这种情况,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指令”动态生成的。

动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

现在假设我们需要,在执行委托类中的方法之前输出“before”,在执行完毕之后输出“after”。

静态代理的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StaticAgent implements Sell {
private Vendor vendor;

public StaticAgent(Vendor vendor) {
this.vendor = vendor;
}

public void sell() {
System.out.println("before");
vendor.sell();
System.out.println("before");
}

public void ad() {
System.out.println("before");
vendor.ad();
System.out.println("before");
}
}

使用动态代理实现

  • InvocationHandler接口

    在使用动态代理是,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口,这个接口的定义如下:

    1
    2
    3
    4
    5
    6
    /**
    * 调用处理程序
    */
    public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args);
    }

    实现了这个接口的中介类叫做“调用处理类”,当我们调用代理类对象的方法时,这个调用会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑。

  • 中介类的定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class DynamicProxy implements InvocationHandler {
    //object为委托类对象
    private Object object;

    public DynamicProxy(Object obj) {
    this.obj = obj;
    }

    @override
    public Object invoke(Object proxy, Method method, Object[] args) {
    System.out.println("before");
    Object result = method.invoke(obj, args);
    System.out.println("after");
    return result;
    }
    }

    通过以上代码可以看出,中介类持有一个委托类对象引用,在invoke方法中调用了委托类对象的相应方法。

  • 动态生成代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Main {
    public static void main(String[] args) {
    //创建中介类实例
    DynamicProxy inter = new DynamicProxy(new Vendor());
    //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
    System.getProperties.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    //获取代理类实例
    Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));
    //通过代理类对象调用代理类方法,实际上会转到invoke方法执行。
    sell.sell();
    sell.ad();
    }
    }

    调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定接口并且会把方法调用分发到指定的调用处理器。这个方法的声明如下:

    1
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

    方法的三个参数含义分别如下:
    * loader:定义类代理类的ClassLoader
    * interfaces:代理类实现的接口列表
    * h:调用处理器,也就是上面实现了InvocationHandler接口的类实例

代理模式

代理模式

代理模式最大的特点就是代理类和实际业务类实现同一个接口(或继承同一父类),代理对象持有一个实际对象的引用,外部调用时操作的是代理对象,而在代理对象的内部实现中又会去调用实际对象的操作。

Java动态代理其实内部是通过反射机制来实现的,即已知的一个对象,然后在运行时动态调用其方法,这样在调用前后做一些相应的处理。

求鼓励,求支持!