模拟JDK动态代理
一、需求
代理Tank类,计算Tank类中方法的执行时间。
因此我们需要在方法执行的前后加上计算逻辑方法
二、方案
接口
package com.proxy.dynamic;
public interface Moveable {
void move();
}
代理类
package com.proxy.dynamic;
import java.util.Random;
public class Tank implements Moveable{
@Override
public void move() {
System.out.println("tank move...");
try {
Thread.sleep(new Random().nextInt(3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
请求处理类(继承自定义InvocationHandler接口)
package com.proxy.dynamic;
import java.lang.reflect.Method;
/**
* 进行时间处理的Handler处理者
* @author lenovo
*
*/
public class TimeHandler implements InvocationHandler{
private Object target;//被代理的对象,并不局限于Tank
public TimeHandler(Object target) {
super();
this.target = target;
}
@Override
public void invoke(Object o,Method m) {
long start=System.currentTimeMillis();
try {
m.invoke(target); //Method类的invoke方法 (对象,方法参数)
} catch (Exception e) {
e.printStackTrace();
}
long end=System.currentTimeMillis();
System.out.println("time:"+(end-start));
}
}
模拟Proxy类
需求:通过Proxy类动态生成Tank的代理类TmeProxy。
说明:在实际的操作中,动态代理类TmeProxy是看不到源码的。为了说明Proxy类做了些什么。我把需要的TmeProxy罗列如下。
package com.proxy.dynamic;
import java.lang.reflect.Method;
public class TimeProxy implements com.proxy.dynamic.Moveable{
com.proxy.dynamic.InvocationHandler h;
public TimeProxy(InvocationHandler h) {
super();
this.h = h;
}
@Override
public void move(){
try{
Method md=com.proxy.dynamic.Moveable.class.getMethod("move");
h.invoke(this ,md);
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
}
目标:
1. 动态拼接上面代码字符串。通过反射reflect。
2. 生成java文件,通过I/O流将动态字符串写入文件。
3. 编译java文件,生成class文件
4. 将class文件load到内存,得到class对象
5. 通过class对象生成代理对象
package com.proxy.dynamic;
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.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
//如何把这段代码编译成class文件,JDK Complier API,CGLib(直接生成2进制文件,没有java文件),ASM
public class Proxy {
//用来产生代理类对象
public static Object newProxyInstance(Class interf,InvocationHandler h) throws Exception{
String methodStr="";
String rt="\r\n"; //回车加换行
Method[] methods=interf.getMethods();
/*for(Method m:methods){
methodStr+="@Override"+rt+
"public void "+m.getName()+"(){"+rt+
" long start=System.currentTimeMillis();"+rt+
" k."+m.getName()+"();"+rt+
" long end=System.currentTimeMillis();"+rt+
" System.out.println(\"time:\"+(end-start));"+rt+
"}" ;
}*/
for(Method m:methods){
methodStr+="@Override"+rt+
" public void "+m.getName()+"(){"+rt+
" try{" +rt+
" Method md="+interf.getName()+".class.getMethod(\""+m.getName()+"\"); "+rt+
" h.invoke(this ,md);"+rt+
" } catch (NoSuchMethodException | SecurityException e) {"+rt+
" e.printStackTrace();"+rt+
" }"+rt+
" }" ;
}
String src=
"package com.proxy.dynamic;"+rt+
"import java.lang.reflect.Method; "+rt+
"public class TimeProxy implements "+interf.getName()+"{"+rt+
" com.proxy.dynamic.InvocationHandler h; "+rt+
// " Moveable k;"+rt+
" public TimeProxy(InvocationHandler h) {"+rt+
" super();"+rt+
" this.h = h;"+rt+
" }"+rt+
methodStr+rt+
"}";
/**
* 生成一个java文件
*/
//System.getProperty("user.dir")
//获得项目全路径 D:\Projects\javaSE\java_00_design_patterns\Proxy_01000
String fileName=System.getProperty("user.dir")
+"/src/com/proxy/dynamic/TimeProxy.java"; //一个路径
File f=new File(fileName); //生成一个文件对象,创建一个实体的文件
FileWriter fw=new FileWriter(f); //给这个文件创建一个流,用于向文件中写入
fw.write(src); //写入字符串
fw.flush();
fw.close();
/**
* 编译这个java文件
*/
//JDK6开始新提供的类 javax.tools.*
JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();//拿到系统默认的java编译器,javac
//System.out.println(compiler.getClass().getName()); //com.sun.tools.javac.api.JavacTool
StandardJavaFileManager fileMgr=compiler.getStandardFileManager(null, null, null); //先生成一个管家
Iterable<? extends JavaFileObject> units=fileMgr.getJavaFileObjects(fileName); //让这个管家获得这个文件对象
CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units);//获得任务
t.call();//执行编译
fileMgr.close(); //关闭管家
/**
* load 到内存memory
*/
// 无法用loadClass()方法装载到内存,因为必须保证你需要装载的class文件在classpath下,在eclipse中即/bin下,所以需要指定我的classpath
URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src/")}; // 指定classpath
URLClassLoader ul=new URLClassLoader(urls);//
Class c=ul.loadClass("com.proxy.dynamic.TimeProxy");//把class文件load到内存
System.out.println(c);
/**
* create Instance 生成对象
*/
//无法用newInstance(),只限于带无参数构造方法的类
Constructor ctr= c.getConstructor(InvocationHandler.class); //参数 :有参构造方法的参数的类型
Object o=ctr.newInstance(h);
//Moveable tp=(Moveable)ctr.newInstance(new Tank());
//tp.move();
return o;
}
}
通过这个proxy的模拟可以学习到:
1. 代码方式编译java文件
2. 将任意位置的class装载到内存
因为使用类装载器对象的loadClass方法必须保证,class类在classpath下
3. 通过获得jvm中class对象来创建类对象
测试
package com.proxy.dynamic;
public class Client {
public static void main(String[] args) throws Exception{
Tank t=new Tank();
InvocationHandler h=new TimeHandler(t);
Moveable m=(Moveable)Proxy.newProxyInstance(Moveable.class,h);//自动生成代理类对象,这个代理类代理了Moeable接口中的所有方法,代理内容为InvocationHandler
m.move();
/* Class c=com.proxy.dynamic.Tank.class;
Tank t=(Tank)c.newInstance();
System.out.println( t instanceof Tank);*/
/* Object o=Class.forName("com.proxy.dynamic.Tank").newInstance();
Tank t=(Tank)o;
System.out.println(t.getClass());*/
}
}