对于newProxyInstance方法我们先将一下整个实现的思路。
总体思路就是他通过字符串化产生一个新的java类,再动态编译返回对象。注意是动态编译。
简单来说就是他通过一些传递进来的参数,自己动态模拟写了一个java类,编译返回给用户,因为用户只要求得到一个对象。中间就比较自由。
-------------------------------------------------------------------------------------------------------
A.首先通过string构造 这个方法。
第一步利用传递进来的接口类,通过反射机制获得这个接口的所有方法。
Method[] methods = inter.getMethods();
这个inter 就是上篇中的Moveable.class
然后遍历这个方法。
for(Method m: methods){
利用字符串把中间所有的方法都相加。
method +=
"@Override "+ rt +
" public void " + m .getName()
+"(){" + rt +
" try{" + rt +
" Method md ="+inter
.getName()+".class.getMethod(\"" +m .getName()+"\");" + rt +
" h.invoke(this, md);" + rt +
" }catch(Exception e){" + rt +
" e.printStackTrace();" + rt +
" }" + rt +
" }" + rt + " " ;
}
这段字符串后期生成的代码是这样的:
@Override
public void move(){
try{
Method md =Moveable.class.getMethod("move");
h.invoke(this, md);
}catch(Exception e){
e.printStackTrace();
}
}
public void move(){
try{
Method md =Moveable.class.getMethod("move");
h.invoke(this, md);
}catch(Exception e){
e.printStackTrace();
}
}
红色是动态生成的部分,我们可以看到 对move方法的操作:
1.首先生成一个move这个方法的对象,这里注意我们传递给invoke的那些方法是对象而不是一个个方法名,而我们通过newProxyInstance 第一个参数获得的仅仅是方法名字。所以这里我们要通过反射机制 利用得到的move方法名 再去获得方法对象md。
这时,我们可以回想第二篇中重写的invoke方法:md就是Method m这个参数,也就是move方法。
2.调用h的invoke方法。有人会问 h 是什么,h就是newProxyInstnce 第二个参数,TimeHandler 这个h对象才能调用 TimeHandler里面的invoke方法呀。但是h是实体,但是我们现在书写的是在string里面的字符串。所以我们要继续写 一个构造方法。
String src =
"import java.lang.reflect.Method;" + rt +
"public class TankTime implements "+inter .getName()
+"{" + rt +
" private InvocationHandler h;" + rt +
" public TankTime(InvocationHandler h){" + rt +
" this.h = h;" + rt +
" }" + rt +
" " +method + rt +
"}";
加上这段代码后 最后会生成下面的代码:
import java.lang.reflect.Method;
public class TankTime implements Moveable{
private InvocationHandler h;
public TankTime(InvocationHandler h){
this.h = h;
}
@Override
public void move(){
try{
Method md =Moveable.class.getMethod("move");
h.invoke(this, md);
}catch(Exception e){
e.printStackTrace();
}
}
}
public class TankTime implements Moveable{
private InvocationHandler h;
public TankTime(InvocationHandler h){
this.h = h;
}
@Override
public void move(){
try{
Method md =Moveable.class.getMethod("move");
h.invoke(this, md);
}catch(Exception e){
e.printStackTrace();
}
}
}
绿色是一开始生成的method方法。
这段代码 只要我们在编译的时候把h实体传进去,就可以了。注意,现在还是空的,等会我们会动态编译,然后传入h这个参数。
注意:这个类名TankTime其实不重要,叫什么都可以,因为它不起到什么作用,最后只要的是这个类实现了Moverable的对象。这只是一个中间量。
--------------------------------------------------------------------------------------------------------------
B.然后我们把这段代码 write成一个java文件。
String path = "c:/harvey/TankTime.java";
File file = new File( path);
FileWriter fw = new FileWriter( file);
fw.write( src);
fw.flush();
fw.close();
这段代码把我们生成的string 编程了一个java文件叫TankTime.java
--------------------------------------------------------------------------
C.有了这个文件,我们现在调用动态编译器去编译他:
//compiler
JavaCompiler compiler =
ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr =compiler.getStandardFileManager( null, null, null);
Iterable units = fileMgr .getJavaFileObjects(path );
CompilationTask t = compiler.getTask( null, fileMgr, null, null,null, units);
t.call();
fileMgr.close();
这段代码就是用来编译的:ToolProvider提供系统的编译器。
StandardJavaFileManager 文件管理类,通过getStandardFileManager
获得,里面三个参数 第一个监听器,第二 第三个是国际化的处理。暂时不用管。下面就是获得java文件然后编译了。别忘了关闭最后编译器。
编译完成后会在 java文件底下生成class文件。
--------------------------------------------------------------------------
D.既然已经有类class文件,我们需要把他装载到内存中好生成对象。
//load to memory and create an instance
URL[] urls = new URL[]{ new URL("file:/" +"c:/harvey/" )};
URLClassLoader url = new URLClassLoader( urls);
Class c = url .loadClass("TankTime" );
上面代码 URLClassLoader会根据URL所指定的路径寻找所有的class文件,最后一句:其中一个叫TankTime的将会被装载 load。
---------------------------------------------------------------------------------------------------------------
E.这个时候生成对象。
Constructor ctr = c .getConstructor(InvocationHandler.class );
Object m = ctr.newInstance( h);
上面代码 先在这个c中寻找是不是有InvocationHandler类的构造函数或者setter方法。
然后newInstance 生成对象的时候把h 就是我们传进来的h对象放进去,生成一个对象,最后返回
这就是整个newProxyInstance的原理。
主要难的地方就是对在string 构造这个调用方法的时候,一些动态的参数需要考虑。
好了,我讲的不清楚,看源代码就懂了。
源代码:
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
public class Proxy
{
public static Object
newProxyInstance( Class inter,InvocationHandler h) throws Exception{
String rt = "\r\n";
String method = "";
Method[] methods = inter.getMethods();
for(Method m:methods){
method +=
"@Override "+ rt +
" public void "+ m.getName()
+"(){" + rt +
" try{"+ rt +
" Method md ="+inter .getName()+".class.getMethod(\"" +m .getName()+"\");" + rt +
" h.invoke(this, md);" + rt +
" }catch(Exception e){" + rt +
" e.printStackTrace();" + rt +
" }" + rt +
" }" + rt + " ";
}
String src =
"import java.lang.reflect.Method;" + rt +
"public class TankTime implements "+inter .getName()
+"{" + rt +
" private Moveable t;" + rt +
" private InvocationHandler h;" + rt +
" public TankTime(InvocationHandler h){" + rt +
" this.h = h;" + rt +
" }"+ rt +
" "+method + rt +
"}";
//String path = System.getProperty("user.dir")+"/src/TankTime.java";
String path = "c:/harvey/TankTime.java";
//System.out.println(path);
//write file
File file = new File( path);
FileWriter fw = new FileWriter( file);
fw.write( src);
fw.flush();
fw.close();
//compiler
JavaCompiler compiler =
ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager( null, null, null);
Iterable units = fileMgr .getJavaFileObjects(path );
CompilationTask t = compiler.getTask( null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
//load to memory and create an instance
URL[] urls = new URL[]{ new URL("file:/"+"c:/harvey/" )};
URLClassLoader url = new URLClassLoader( urls);
Class c = url .loadClass("TankTime" );
//System.out.println(c);
Constructor ctr = c.getConstructor(InvocationHandler.class );
Object m = ctr.newInstance( h);
//Moveable m
= ( Moveable)ctr.newInstance(new Tank());
//m.move();
return m;
}
}