JAVA的动态代理
代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
首先看一下静态代理:
1、Count.java:
package net.battier.dao; /** * 定义一个账户接口 * * @author Administrator * */ public interface Count { // 查看账户方法 public void queryCount(); // 修改账户方法 public void updateCount(); }2.CountImpl.java
package net.battier.dao.impl; import net.battier.dao.Count; /** * 委托类(包含业务逻辑) * * @author Administrator * */ public class CountImpl implements Count { @Override public void queryCount() { System.out.println("查看账户方法..."); } @Override public void updateCount() { System.out.println("修改账户方法..."); } }3.CountProxy.java
package net.battier.dao.impl; import net.battier.dao.Count; /** * 这是一个代理类(增强CountImpl实现类) * * @author Administrator * */ public class CountProxy implements Count { private CountImpl countImpl; /** * 覆盖默认构造器 * * @param countImpl */ public CountProxy(CountImpl countImpl) { this.countImpl = countImpl; } @Override public void queryCount() { System.out.println("事务处理之前"); // 调用委托类的方法; countImpl.queryCount(); System.out.println("事务处理之后"); } @Override public void updateCount() { System.out.println("事务处理之前"); // 调用委托类的方法; countImpl.updateCount(); System.out.println("事务处理之后"); } }
4.TestCount.java
package net.battier.test; import net.battier.dao.impl.CountImpl; import net.battier.dao.impl.CountProxy; /** *测试Count类 * * @author Administrator * */ public class TestCount { public static void main(String[] args) { CountImpl countImpl = new CountImpl(); CountProxy countProxy = new CountProxy(countImpl); countProxy.updateCount(); countProxy.queryCount(); } }观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。
再来看一下动态代理:
与静态代理类的创建不同的是生成代理的方式,这里使用了java.lang.reflect.Proxy类的newProxyInstance()方法生成一个代理,并且在生成过程中需要传入一个代理处理器,java.lang.reflect.InvocationHandler的一个实现类。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
其中生成的代理类是由Java内部执行的。
public interface InvocationHandler
InvocationHandler 是代理实例的调用处理程序 实现的接口。
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
invoke
Object invoke(Object proxy,
Method method,
Object[] args)
throws Throwable在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
参数:
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
返回:
从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 ClassCastException。
抛出:
Throwable - 从代理实例上的方法调用抛出的异常。该异常的类型必须可以分配到在接口方法的 throws 子句中声明的任一异常类型或未经检查的异常类型 java.lang.RuntimeException 或 java.lang.Error。如果此方法抛出经过检查的异常,该异常不可分配到在接口方法的 throws 子句中声明的任一异常类型,代理实例的方法调用将抛出包含此方法曾抛出的异常的 UndeclaredThrowableException。
public class Proxy
extends Object
implements Serializable
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
Proxy
protected Proxy(InvocationHandler h)使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例。
参数:
h - 此代理实例的调用处理程序
getProxyClass
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。该代理类将由指定的类加载器定义,并将实现提供的所有接口。如果类加载器已经定义了具有相同排列接口的代理类,那么现有的代理类将被返回;否则,类加载器将动态生成并定义这些接口的代理类。
对可以传递给 Proxy.getProxyClass 的参数有以下几个限制:
interfaces 数组中的所有 Class 对象必须表示接口,而不能表示类或基本类型。
interfaces 数组中的两个元素不能引用同一 Class 对象。
所有接口类型的名称通过特定的类加载器必须可见。换句话说,对于类加载器 cl 和所有接口 i,以下表达式必须为 true:
Class.forName(i.getName(), false, cl) == i
所有非公共接口必须位于同一包中;否则,该代理类将不可能实现所有的接口,无论它在哪一个包中定义。
对于有相同签名的指定接口中任何成员方法集:
如果任何方法的返回类型是基本类型或 void,那么所有的方法必须具有与此相同的返回类型。
否则,该方法之一必须是返回类型,它可以指派给该方法其余的所有返回类型。
得到的代理类必须不超过虚拟机在类上施加的任何限制。例如,虚拟机可以限制某一类实现至多 65535 的接口数;在这种情况下,interfaces 数组的大小必须不超过 65535。
如果违反了这些限制,Proxy.getProxyClass 将抛出 IllegalArgumentException。如果 interfaces 数组参数或其任何元素为 null,则将抛出 NullPointerException。
注意,指定的代理接口的顺序非常重要:对接口组合相同但顺序不同的代理类的两个请求会导致两个不同的代理类。
参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
返回:
用指定的类加载器定义的代理类,它可以实现指定的接口
抛出:
IllegalArgumentException - 如果违反传递到 getProxyClass 的参数上的任何限制
NullPointerException - 如果 interfaces 数组参数或其任何元素为 null
newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于:
Proxy.getProxyClass(loader, interfaces).
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
Proxy.newProxyInstance 抛出 IllegalArgumentException,原因与 Proxy.getProxyClass 相同。
参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
抛出:
IllegalArgumentException - 如果违反传递到 getProxyClass 的参数上的任何限制
NullPointerException - 如果 interfaces 数组参数或其任何元素为 null,或如果调用处理程序 h 为 null
isProxyClass
public static boolean isProxyClass(Class<?> cl)当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。
此方法的可靠性对于使用它做出安全决策而言非常重要,所以此方法的实现不应仅测试相关的类是否可以扩展 Proxy。
参数:
cl - 要测试的类
返回:
如该类为代理类,则为 true,否则为 false
抛出:
NullPointerException - 如果 cl 为 null
getInvocationHandler
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException返回指定代理实例的调用处理程序。
参数:
proxy - 返回调用处理程序的代理实例
返回:
代理实例的调用处理程序
抛出:
IllegalArgumentException - 如果参数不是一个代理实例
动态代理类:在运行时生成的class,在其生成过程中,你必须提供一组接口给它,然后该class就声称实现了这些接口。可以把该class的实例当做这些接口中的任何一个来用。其实,这个Dynamic Proxy就是一个Proxy,他不会替你做任何实质性的工作。在生成它的实例时,必须提供一个Handler,由它接管实际的工作。
在使用动态代理类时,必须实现InvocationHandler接口。
通过DynamicProxy,RealSubject可以在运行时动态改变,需要控制的接口Subject也可以在运行时改变,控制的方式DynamicSubject类也可以动态改变,从而实现了非常灵活的动态代理关系。
动态代理
与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
1.BookFacade.java
package net.battier.dao; public interface BookFacade { public void addBook(); }2.BookFacadeImpl.java
package net.battier.dao.impl; import net.battier.dao.BookFacade; public class BookFacadeImpl implements BookFacade { @Override public void addBook() { System.out.println("增加图书方法。。。"); } }
package net.battier.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * JDK动态代理代理类 * * @author student * */ public class BookFacadeProxy implements InvocationHandler { private Object target; /** * 绑定委托对象并返回一个代理类 * @param target * @return */ public Object bind(Object target) { this.target = target; //取得代理对象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); //要绑定接口 } @Override /** * 调用方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; System.out.println("事物开始"); //执行方法 result=method.invoke(target, args); System.out.println("事物结束"); return result; } }
4.TestProxy.java
package net.battier.test; import net.battier.dao.BookFacade; import net.battier.dao.impl.BookFacadeImpl; import net.battier.proxy.BookFacadeProxy; public class TestProxy { public static void main(String[] args) { BookFacadeProxy proxy = new BookFacadeProxy(); BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl()); bookProxy.addBook(); } }看完上面的代码,大致明白动态代理的含义:
A接口有c方法,类B实现A接口,原本应该是执行B类中的c方法,可现在不这样做;
我声明产生B类的代理类B‘,由它来冒充B类的“兄弟”并“实现”A接口,
对外界来说B‘应该也有c方法,可当真正调用它的时候,
它会去执行与它关联InvocationHandler的invoke()方法,
在这个方法里面你可以做很多事情。这样,这个请求就被“代理”到其它地方去了。