代理模式,就是因为某些原因,无法操作一个具体的对象concrete,那么就提供一个代理对象proxy,通过这个代理对象来操作具体的对象concrete。
代理模式有很多种,包括静态代理,保护代理,远程代理,动态代理等等,每一种都有特定的使用场景。本文主要介绍Java中的动态代理。
一、静态代理和动态代理的区别
首先给出静态代理和动态代理的定义:
静态代理:由程序员创建,或由工具自动生成源代码,然后进行编译。运行期间,代理的class文件已经存在。
动态代理:程序运行时,通过反射机制动态创建而成。
换句话说, 静态代理,是给每一个具体的类写一个代理类,需要用到具体类的时候,就给这个具体的类写一个代理类,然后调用代理类就行了,这些代码都是程序员手动编写,或者由一些工具生成的,在编译前必须完成。
但是这样,会有一个很大的缺点,如果有很多具体类,就需要些很多个代理类,这样无疑产生了很多重复代码。所以如果需要用一个代理类,完成所有的接口,就需要使用动态代理来完成。
二、动态代理示例
动态代理,使用了一个接口InvocationHandler,和一个代理类Proxy, 二者配合完成动态代理的功能。InvocationHandler, Proxy.,都在java.lang.reflet包里。
InvocationHandler 只有一个方法:
Object invoke(Object proxy, Method method, Object[] args);
Proxy主要有两个静态方法:
Class<?> getProxyClass(Classloader loader, Class<?> ... interfaces); //返回一个动态创建的代理类 (Class) Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h); //返回代理类的实例
所以, 通过Java中的反射,在运行时,动态的通过指定类加载器,接口数组,调用处理程序这3个参数,来动态生成代理类。这个就是动态代理的功能。
示例代码如下:
//委托类接口 Inteface IDemo { void sayHello(); } //具体委托类1,处理真正的业务逻辑 class Demo1 implements IDemo { @Override public void sayHello() { System.out.println("Demo1 say Hello"); } } //具体委托类2,处理真正的业务逻辑 class Demo2 implements IDemo { @Override public void sayHello { System.out.println("Demo2 say Hello"); } } //处理类 class DynamicInvocationHandler implements InvocationHandler { Object obj; @Override Object invoke (Object proxy, Method method, Object[] args) { return method.invoke(proxy,args); } Object bind(Object obj) { this.obj = obj; return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),this); } } //客户端类,或者是测试类 class Client { public static void main(String[] args) { IDeomo demo = (IDemo) new DynamicInvocationHandler().bind(new Demo1()); demo.sayHello(); } }
上面的代码中,有一个公用的委托类接口IDemo,两个具体的类Demo1, Demo2 实现了IDemo,用于处理具体的业务逻辑。
然后,DynamicInvocationHandler实现了InvocationHandler接口,也实现了invoke()方法。但是,invoke()方法中,是标准的实现,并没有加入任何代码。真正有用的方法,是bind()方法。
bind() 方法类似于一个工厂方法,返回了处理处理具体委托类的代理类。
我们再来分析客户端的main()方法的流程。
首先, new DynamicInvocationHandler(), 创建了一个DynamicInvocationHandler的对象。然后调用bind()方法,其中的参数是new Demo1(),也就是说,会创建一个处理Demo1的代理类的实例。如果我们想操作Demo2,那么参数中传new Demo2()就可以了。
最后,查看newProxyInstance()方法,可以看到程序进行了验证,优化,缓存,生成字节码,显示加载等操作,最后调用了sun.misc.ProxyGenerator.generateProxyClass()来完成生成字节码的功能.也就是说,jvm是动态生成字节码,也就是class文件,来完成代理类的生成的。在jvm中,class文件不一定非要通过编译来实现,可以通过zip包获取(就是import xxx.jar之类的操作),可以通过网络获取,也可以动态生成。操作字节码的类库,有Cglib,ASM等等。