Posted on 2010-08-16 10:55
java小爬虫 阅读(1624)
评论(0) 编辑 收藏
如上文:代理模式和装饰者模式中的静态代理实例,它具有如下缺陷:
1:代理类不可重用,具有相同代理逻辑的类会大量产生;
2:被代理方法惟一,如果有多个方法都需要相同逻辑的代理,那么代理类中就有大量的相似的方法存在;
3:代理方法不具有参数;
4:只实现了单接口了代理;
所以它并不具有实战意义上的价值!
那么,这些问题该如何解决呢?动态代理又是一步一步如何演变过来的呢?
下面就让我们来一步一步以实例的方式来探究它的演变的细节。
如果我们能动态产生一个代理类的源文件,编译后加载到内存,那么我们就可以获取到动态的代理对象。
package proxy;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
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(Object target) throws Exception { //JDK6 Complier API, CGLib, ASM
String rt = "\r\n";
String t = "\t";
String src ="package proxy;"+ rt + t +
"public class TankTimeProxy implements Movable {"+ rt + t +
"private Movable obj; "+ rt + t +
"public TankTimeProxy(Movable obj) {"+ rt + t +
"super();"+ rt + t +
"this.obj = obj;"+ rt + t +
"}"+ rt + t +
"@Override"+ rt + t +
"public void move() {"+ rt + t +
"long begintime = System.currentTimeMillis();"+ rt + t +
"System.out.println(\" Tank is begining to move !\");"+ rt + t +
"obj.move();"+ rt + t +
"long endtime = System.currentTimeMillis();"+ rt + t +
"System.out.println(\" Tank is stop !\");"+ rt + t +
"System.out.println(\"move time : \"+(endtime-begintime));"+ rt + t +
"}"+ rt + t +
"}";
String fileName =System.getProperty("user.dir")+"/src/proxy/TankTimeProxy.java";
File f = new File(fileName);
FileWriter fw = new FileWriter(f);
if(f.exists()){
f.delete();
fw.flush();
f = new File(fileName);
};
fw.write(src);
fw.flush();
fw.close();
//compile
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(fileName);
CompilationTask compilationTask = compiler.getTask(null, fileMgr, null, null, null, units);
compilationTask.call();
fileMgr.close();
//load into memory and create an instance
URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir")+ "/src/")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("proxy.TankTimeProxy");
Constructor ctr = c.getConstructor(target.getClass().getInterfaces()[0]);
Object m = ctr.newInstance(target);
return m;
}
}
测试类:
package proxy;
public class Client {
public static void main(String[] args) throws Exception{
Movable tank = (Movable)Proxy.newProxyInstance(new Tank());
tank.move();
}
}
上面的类就实现了生成
TankTimeProxy.java文件,编译,加载并被调用的功能。(全部源码见:代理模式和装饰者模式异同点比较
)
那么怎么实现代理任意对象呢?
在产生代理类的时候,只要动态的注入目标对象,就实现了对任意对象的代理。
怎么实现对任意方法的代理呢?通过java反射机制,可以获取一个类的所有方法,即可以获取目标类的所有方法,在组成代理类java源码的时候,循环遍历嵌入处理逻辑就可以任意对多方法的代理了。
示例代码如下:
package proxy;
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 intface,Object target) throws Exception { //JDK6 Complier API, CGLib, ASM
String rt = "\r\n";
String t = "\t";
String methodStr = "" ;
Method[] methods = intface.getDeclaredMethods();
for (Method method : methods) {
methodStr += "@Override"+ rt + t +
"public void "+method.getName()+"() {"+ rt + t +
"long begintime = System.currentTimeMillis();"+ rt + t +
"System.out.println(\" Tank is begining to move !\");"+ rt + t +
"obj."+method.getName()+"();"+ rt + t +
"long endtime = System.currentTimeMillis();"+ rt + t +
"System.out.println(\" Tank is stop !\");"+ rt + t +
"System.out.println(\"move time : \"+(endtime-begintime));"+ rt + t +
"}"+ rt + t;
}
System.out.println(methodStr);
String src ="package proxy;"+ rt + t +
"import "+intface.getName()+";"+ rt + t +
"public class $Proxy1 implements "+intface.getSimpleName()+" {"+ rt + t +
"private "+intface.getSimpleName()+" obj; "+ rt + t +
"public $Proxy1("+intface.getSimpleName()+" obj) {"+ rt + t +
"super();"+ rt + t +
"this.obj = obj;"+ rt + t +
"}" + rt + t +
methodStr +rt +
"}";
String fileName =System.getProperty("user.dir")+"/src/proxy/$Proxy1.java";
File f = new File(fileName);
FileWriter fw = new FileWriter(f);
if(f.exists()){
f.delete();
fw.flush();
f = new File(fileName);
};
fw.write(src);
fw.flush();
fw.close();
//compile
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(fileName);
CompilationTask compilationTask = compiler.getTask(null, fileMgr, null, null, null, units);
compilationTask.call();
fileMgr.close();
//load into memory and create an instance
URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir")+ "/src/")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("proxy.$Proxy1");
Constructor ctr = c.getConstructor(intface);
Object m = ctr.newInstance(target);
return m;
}
}
但是新的问题又出现了:
在代理类中,被代理方法前后的处理逻辑已经被“写死了”,很难改变增加的功能,这又该如何处理呢?
我们可以这样考虑,增加一个调用处理器InvocationHandler,把对方法的处理逻辑进行进一步的封闭,并把InvocationHandler分离出来,如果可以的话,就实现了对代理逻辑的可修改性。
那么InvocationHandler里面应该封闭些什么东西呢?
for (Method method : methods) {
methodStr += "@Override"+ rt + t +
"public void "+method.getName()+"() {"+ rt + t +
"long begintime = System.currentTimeMillis();"+ rt + t +
"System.out.println(\" Tank is begining to move !\");"+ rt + t +
"obj."+method.getName()+"();"+ rt + t +
"long endtime = System.currentTimeMillis();"+ rt + t +
"System.out.println(\" Tank is stop !\");"+ rt + t +
"System.out.println(\"move time : \"+(endtime-begintime));"+ rt + t +
"}"+ rt + t;
}
从这个片段代理可以看出,我们应该在InvocationHandler中封装obj对象,即被代理类的接口(实现类)。还应该实现:被分离出去的InvocationHandler能被代理类调用,我们应该把InvocationHandler聚合进来。
代码演变示例:
package proxy;
import java.lang.reflect.Method;
public interface InvocationHandler {
public void invoke(Object proxy, Method m);
}
package proxy;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler{
private Object target;
public TimeHandler(Object target) {
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
long start = System.currentTimeMillis();
System.out.println("starttime:" + start);
System.out.println(o.getClass().getName());
try {
m.invoke(target);
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("time:" + (end-start));
System.out.println("endtime:" + end);
}
}
package proxy;
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 intface,InvocationHandler h) throws Exception { //JDK6 Complier API, CGLib, ASM
String rt = "\r\n";
String t = "\t";
String methodStr = "" ;
Method[] methods = intface.getDeclaredMethods();
for(Method method : methods) {
methodStr += "@Override" + rt + t + t +
"public void " + method.getName() + "() {" + rt + t + t + t +
"try {" + rt + t + t + t + t +
"Method md = " + intface.getName() + ".class.getMethod(\"" + method.getName() + "\");" + rt + t + t + t + t +
"h.invoke(this, md);" + rt + t + t + t +
"}catch(Exception e) {" + rt + t + t + t +
"e.printStackTrace();" + rt + t + t +
"}" + rt + t +
"}" + rt + t + t ;
}
System.out.println(methodStr);
String src ="package proxy;"+ rt + t +
"import "+intface.getName()+";"+ rt + t +
"import java.lang.reflect.Method;"+ rt + t +
"public class $Proxy1 implements "+intface.getSimpleName()+" {"+ rt + t +
"private InvocationHandler h ; "+ rt + t +
"public $Proxy1(InvocationHandler h) {"+ rt + t +
"super();"+ rt + t +
"this.h = h;"+ rt + t +
"}" + rt + t +
methodStr +rt +
"}";
String fileName =System.getProperty("user.dir")+"/src/proxy/$Proxy1.java";
File f = new File(fileName);
FileWriter fw = new FileWriter(f);
if(f.exists()){
f.delete();
fw.flush();
f = new File(fileName);
};
fw.write(src);
fw.flush();
fw.close();
//compile
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(fileName);
CompilationTask compilationTask = compiler.getTask(null, fileMgr, null, null, null, units);
compilationTask.call();
fileMgr.close();
//load into memory and create an instance
URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir")+ "/src/")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("proxy.$Proxy1");
Constructor ctr = c.getConstructor(InvocationHandler.class);
Object m = ctr.newInstance(h);
return m;
}
}
这样,就实现了对任意对象,任意方法的代理。
缺点就是:多接口代理没有实现,被代理对象的方法没有支持参数。