JDK的动态代理涉及到了JAVA的动态编程,说起来也惭愧,我写了四年的代码,从没用过AOP,我们这破公司的架构太单调,技术太简单,所以一旦走进这样的公司就赶紧走人,要不对个人成长一点帮助都没有,等你工作四年五年的时候就知道做过的项目和用过的技术比个人的智商和学习能力更重要,不说了扯远了。
首先写了一个简单的动态代理:
import java.lang.reflect.*;
import java.io.*;
public class TestDynamicProxy{
public static void main(String[] args)throws Exception{
Hello biz = new HelloImpl();
InvocationHandler dpm = new MyInvocationHandler(biz);
Hello hello = (Hello)Proxy.newProxyInstance(biz
.getClass().getClassLoader(), biz.getClass().getInterfaces(),
dpm);
hello.sayHello();
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String msg = br.readLine();
System.out.println(msg);
br.close();
isr.close();
}
}
interface Hello{
public void sayHello();
}
class HelloImpl implements Hello{
public void sayHello(){
System.out.println("Hello World!");
}
}
class MyInvocationHandler implements InvocationHandler{
private Hello biz ;
public MyInvocationHandler(Hello biz){
this.biz = biz;
}
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{
System.out.println("before sayHello.......");
Object o = method.invoke(biz,args);
System.out.println("after sayHello..........");
return o;
}
}
写完这个动态代理我就开始看Proxy类的源代码,以下的方法也就成了我重点关注的,重点的代码进行了红色加粗,但这还没完:
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
if (interfaces.length > 65535) { //一个java类最多可实现65535个接口
throw new IllegalArgumentException("interface limit exceeded");
}
Class proxyClass = null;
String[] interfaceNames = new String[interfaces.length];
Set interfaceSet = new HashSet(); // for detecting duplicates
for (int i = 0; i < interfaces.length; i++) {
String interfaceName = interfaces[i].getName();
Class interfaceClass = null;
try {
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
Object key = Arrays.asList(interfaceNames);
Map cache;
synchronized (loaderToCache) {
cache = (Map) loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap();
loaderToCache.put(loader, cache);
}
}
synchronized (cache) {
do {
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// proxy class already generated: return it
return proxyClass;
} else if (value == pendingGenerationMarker) {
// proxy class being generated: wait for it
try {
cache.wait();
} catch (InterruptedException e) {
}
continue;
} else {
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
try {
String proxyPkg = null; // package to define proxy class in
for (int i = 0; i < interfaces.length; i++) {
int flags = interfaces[i].getModifiers();
if (!Modifier.isPublic(flags)) {
String name = interfaces[i].getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) { // if no non-public proxy interfaces,
proxyPkg = ""; // use the unnamed package
}
{
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
//proxyPkg是null,proxyClassNamePrefix是$Proxy,而num是个数字,所以生成的代理类的名称就是$Proxy0这样子的。
String proxyName = proxyPkg + proxyClassNamePrefix + num;
[color=red][b]byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);[/b][/color]
try {
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
// add to set of all generated proxy classes, for isProxyClass
proxyClasses.put(proxyClass, null);
} finally {
synchronized (cache) {
if (proxyClass != null) {
cache.put(key, new WeakReference(proxyClass));
} else {
cache.remove(key);
}
cache.notifyAll();
}
}
return proxyClass;
}
看到了这里我似乎明白了,原来动态代理是JDK通过反射机制获取了被代理对象的所有接口(动态代理只能实现接口),然后重新生成一个新的类,当然这个类没有保存成文件,而只是在内存里面,这个新的类implements了被代理对象的所有接口,同时他还持有一个InvocationHandler类型的属性(本来开始以为是在生成的代理类中持有的,后来发现是Proxy中的,且待后面分解),实现了接口中的所有方法,然后在代理类调用某个方法的时候通过InvocationHandler去调用被代理类的方法,而在InvocationHandler类的invoke方法中嵌入了我们自己的代码,这样就实现了代理的效果,由此看来代理类和被代理类是一脉相承的,都实现了同样的接口,所以都有一样的方法,以此达到代理的目的。
我看到这里本来以为一切谜团都已经揭开,但是我突然一想,InvocationHandler里的invoke方法是需要传递一个Method类型参数的,这个是怎么传进去的,于是我继续在思考,请教别人,终于一个大师告诉我让我dump出JVM的进程里的class来,这样就可以看到内存里的$Proxy0的实现代码了,感觉他说的有道理,于是我在Linux下装了一个JDK1.7版本,开始了我的摸索之路,以下部分是引用加自己的总结:
引用
SA(Serviceability Agent)自带了一个能把当前在HotSpot中加载了的类dump成Class文件的工具,称为ClassDump。它的全限定类名是sun.jvm.hotspot.tools.jcore.ClassDump,有main()方法,可以直接从命令行执行;接收一个命令行参数,是目标Java进程的进程ID,可以通过JDK自带的jps工具查找Java进程的ID。要执行该工具需要确保SA的JAR包在classpath上,位于$JAVA_HOME/lib/sa-jdi.jar。
默认条件下执行ClassDump会把当前加载的所有Java类都dump到当前目录下,如果有全限定名相同但内容不同的类同时存在于一个Java进程中,那么dump的时候会有覆盖现象,实际dump出来的是同名的类的最后一个(根据ClassDump工具的遍历顺序)。
如果需要指定被dump的类的范围,可以自己写一个过滤器,在启动ClassDump工具时指定-Dsun.jvm.hotspot.tools.jcore.filter=filterClassName,具体方法见下面例子;如果需要指定dump出来的Class文件的存放路径,可以用-Dsun.jvm.hotspot.tools.jcore.outputDir=path来指定,path替换为实际路径。
import sun.jvm.hotspot.tools.jcore.ClassFilter;
import sun.jvm.hotspot.oops.InstanceKlass;
public class MyFilter implements ClassFilter {
@Override
public boolean canInclude(InstanceKlass kls) {
String klassName = kls.getName().asString();
return klassName.contains("$Proxy");
}
}
InstanceKlass对应于HotSpot中表示Java类的内部对象。Sun JDK为反射调用生成的类的名字形如sun/reflect/GeneratedMethodAccessorN,其中N是一个整数;所以只要看看类名是否以"sun/reflect/GeneratedMethodAccessor"开头就能找出来了。留意到这里包名的分隔符是“/”而不是“.”,这是Java类在JVM中的“内部名称”形式.
现在让我前面的代码动态代码跑起来,为了不让进程很快结束,所以我加入了io阻塞等待用户输入,让进程不用结束。
然后我们通过jps命令查看所有的java进程,得到进程号。
$ jps
20542 Demo
20554 Jps
接下来执行ClassDump,指定上面自定义的过滤器:
$ java -classpath ".:./bin:$JAVA_HOME/lib/sa-jdi.jar" -Dsun.jvm.hotspot.tools.jcore.filter=MyFilter sun.jvm.hotspot.tools.jcore.ClassDump 20542
这样子就在当前的目录下生成了一个$Proxy0.class的文件,这个文件可以通过javap查看,但看起来比较费劲,于是我找了一个Xjad将他反编译过来了,代码如下:
import java.lang.reflect.*;
public final class $Proxy0 extends Proxy //继承了Proxy类
implements Hello
{
private static Method m3;
private static Method m1;
private static Method m0;
private static Method m2;
public final void sayHello() //实现了接口中的sayHello方法,
{
try
{
super.h.invoke(this, m3, null);//调用了InvocationHandler里的invoke方法吧
return;
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public $Proxy0(InvocationHandler invocationhandler) //生成了一个这样的构造函数
{
super(invocationhandler);
}
//除了实现了接口中的方法外,代理类另外还重写了hashCode,equals,toString三个方法。
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
//静态代码块,初始化了几个Method属性,这里m3就是接口中的sayHello方法,如果接口中还有其他的方法他也会实现,可能会是m4,m5,m6......
这里m3 = Class.forName("Hello").getMethod("sayHello", new Class[0]);直接以字符串常量的方式传进去,应该是在生成代理类之前通过Method对象的getName方法获取到了。
static
{
try
{
m3 = Class.forName("Hello").getMethod("sayHello", new Class[0]);
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]);
}
catch (NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch (ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
以上这些对我来说也就谜底揭开了,感觉一天比较充实了。。。。。。