最近在看一个大神自己写的模拟Spring框架,里面又不可避免的出现了动态代理,突然发现自己之前还是了解的不够,所以就再在网上找了些资料。
首先为什么要用动态代理
动态代理用于方法的增强,他可以在不影响原程序代码的情况下改变指定方法的运行情况,增加运行前后的处理
常见用于struts的拦截器,spring AOP
然后我们说一下上面是动态代理
其实往简单的说,动态代理技术就是用来产生一个对象的代理对象的。
在这里需要明确关于代理对象的概念:
代理对象的存在价值主要是用于拦截对真实业务对象的访问
代理对象应该具有和目标对象相同的方法
这也是一种设计模式
代理模式
有静态代理和动态代理
实现jdk动态代理
我们需要了解一个类Proxy
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
我们主要关注他的一个静态方法
newProxyInstance
我们来看一下JDK API Documentation
首先我们可以看到文档中对Proxy代理类的介绍
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
创建某一接口 Foo 的代理:
InvocationHandler handler = new MyInvocationHandler(...);
Class proxyClass = Proxy.getProxyClass(
Foo.class.getClassLoader(), new Class[] { Foo.class });
Foo f = (Foo) proxyClass.
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
或使用以下更简单的方法:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[] { Foo.class },
handler);
动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下面描述的行为。 代理接口 是代理类实现的一个接口。 代理实例 是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。通过其中一个代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的 Invoke 方法,并传递代理实例、识别调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。调用处理程序以适当的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果返回。
对于JDK API DOCUMENTATION中关于Proxy类的介绍就摘录到这里,对于更详细的信息,请查阅官方文档。
在看看newProxyInstance
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 });
参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
接下来,我们按照Proxy类的相关介绍来编写程序
先写一个接口,再写一个实现这个接口的类
创建一个实现InvocationHandler接口的类(或者直接使用匿名类创建)
使用Proxy.newPrxoyInstance()得到一个带有代理类的指定调用处理程序的代理实例
package wangcc.proxy;
public interface Subject {
public String getInfo(String info);
}
package wangcc.proxy;
public class RealSubject implements Subject {
@Override
public String getInfo(String info) {
// TODO Auto-generated method stub
String retInfo = info + "KOBE";
return retInfo;
}
}
package wangcc.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler {
// private Subject subject;
private Object obj;
public ProxyHandler(Subject subject) {
this.obj = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("Proxy Invoke");
if (method.getName().equals("getInfo")) {
args[0] = "leborn";
}
// args[0].
return method.invoke(obj, args);
}
}
接下来看一下Test类
package wangcc.proxy;
import java.io.FileOutputStream;
import java.lang.reflect.Proxy;
import sun.misc.ProxyGenerator;
public class Test {
public static void main(String[] args) throws Exception {
// InvocationHandler myHandler = new ProxyHandler(new RealSubject());
// Class proxyClass =
// Proxy.getProxyClass(Subject.class.getClassLoader(),
// new Class[] { Subject.class });
// Subject proxy = (Subject) proxyClass.getConstructor(
// new Class[] { InvocationHandler.class }).newInstance(
// new Object[] { myHandler });
// String s = proxy.getInfo("dddd");
// System.out.println(s);
Subject proxySubject = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(), new Class[] { Subject.class },
new ProxyHandler(new RealSubject()));
String s = proxySubject.getInfo("dddd");
System.out.println(s);
createProxyClassFile();
}
public static void createProxyClassFile() {
String name = "ProxySubject";
byte[] data = ProxyGenerator.generateProxyClass(name,
new Class[] { Subject.class });
try {
FileOutputStream out = new FileOutputStream(name + ".class");
out.write(data);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们看被注释的部分代码
就是创建Subject接口代理实例的第一种方法
而第二种方法就是通过newProxyInstance方法创建一个带有代理类的指定调用处理程序的代理实例的第二种方法,
我们一般都直接用第二种方法。
其实第二种方法就是把第一种方法的实现细节给隐藏起来了而已。
在学习编程以来,我一直都觉得一切的框架方法设计模式都好,都是封装抽象。
那第一种方式又是怎样实现的呢?
我们来看一下动态代理内部实现
首先来看看类Proxy的代码实现 Proxy的主要静态变量
// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap();
// 标记:用于标记一个动态代理类正在被创建中
private static Object pendingGenerationMarker = new Object();
// 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
// 关联的调用处理器引用
protected InvocationHandler h;
Proxy的构造方法
// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
private Proxy() {}
// 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
protected Proxy(InvocationHandler h) {this.h = h;}
由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
Proxy静态方法newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException {
// 检查 h 不为空,否则抛异常
if (h == null) {
throw new NullPointerException();
}
// 获得与指定类装载器和一组接口相关的代理类类型对象
Class cl = getProxyClass(loader, interfaces);
// 通过反射获取构造函数对象并生成代理类实例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
类Proxy的getProxyClass方法调用ProxyGenerator的 generateProxyClass方法产生ProxySubject.class的二进制数据:
public static byte[] generateProxyClass(final String name, Class[] interfaces)
我们可以import sun.misc.ProxyGenerator,调用 generateProxyClass方法产生binary data,然后写入文件,最后通过反编译工具来查看内部实现原理。(这里用的是jd-gui)
如何import sun.misc.ProxyGenerator
即如何导入特殊jar中的类
设置JRE System Library的access rules属性
1、打开项目属性》java Build Path》选择JRE System Library》点击左边的三角形 打开下拉菜单》双击access rules》打开其属性;
2、点击右边的Add》打开access rules设置》选择resolution为accessible》按照提示在下面的rule pattern输入相应的pattern;
pattern 可以输入* 代表所有的包均可导入 ,也可以输入相应包路径下面的类可以进入访问,例如sun/misc/*等等;
3、所有的选择完成之后一步步OK即可,回到工作空间,红色错误即消失。
但是这样操作会存在一定的风险,对于以后代码的可以扩展性、通用性、安全性等,因为这些包不是J2SE规范里面声明的类。
也就是说这些类可能在Windows下可用,但到了Linux或者其他平台下不可用;又或者这些类在jdk的一下个版本可能会被删除;又或者这些类本身就存在安全隐患等等问题,请慎重!
我们观看反编译后的ProxySubject.java Proxy静态方法newProxyInstance
public final class ProxySubject extends Proxy
implements Subject
{
private static Method m3;
private static Method m1;
private static Method m0;
private static Method m2;
public ProxySubject(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final String getInfo(String paramString)
throws
{
try
{
return (String)this.h.invoke(this, m3, new Object[] { paramString });
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m3 = Class.forName("wangcc.proxy.Subject").getMethod("getInfo", new Class[] { Class.forName("java.lang.String") });
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
通过反编译后的代码,我们应该就能看懂Test类中被注释的代码了。
由此也可以得到
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
美中不足
诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。
参考文档:http://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html
再附上我很喜欢的大神的一篇博客,讲了动态代理的具体应用
http://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html