您的位置:首页 > 其它

设计模式学习笔记(三)动态代理模式

2016-02-16 15:34 519 查看
上节学习的聚合方式实现代理模式,属于静态代理,有个缺点,当我们要用到很多种代理的时候(A接口代理,B接口代理.....),代理类也会泛滥

jdk中的Proxy类实现了动态代理功能,现在来模拟一下。

生成步骤:

1.动态生成代理类的字符串代码,并写入到临时文件

2.JDK API动态编译

3.载入内存并实例化

package com.skymr.pattern.proxypat.dynamic;

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.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import com.skymr.pattern.proxypat.IStudent;
/**
* 模拟jdk的Proxy类
* @author skymr
*
*/
public class Proxy {

/**
* 通过jdk1.6的JavaCompiler API动态编译
* 还可以用其他方式动态编译,CGLib,ASM等第三方jar包
* @param student
* @return
* @throws Exception
*/
public static Object newInstance(IStudent student) throws Exception{
String javaSrc = "";
javaSrc += "package com.skymr.pattern.proxypat.dynamic;";
javaSrc += "public class StudentD implements com.skymr.pattern.proxypat.IStudent{";
javaSrc += "private com.skymr.pattern.proxypat.IStudent student;";
javaSrc += "public StudentD(com.skymr.pattern.proxypat.IStudent student){";
javaSrc += "this.student = student;";
javaSrc += "}";
javaSrc += "public void examine() {";
javaSrc += "long start = System.currentTimeMillis();";
javaSrc += "student.examine();";
javaSrc += "long end = System.currentTimeMillis();";
javaSrc += "System.out.println(\"耗时:\"+(end-start));";
javaSrc += "}";
javaSrc += "}";

String fileName = System.getProperty("user.home")+"/src/com/skymr/pattern/proxypat/dynamic/StudentD.java";
//格式化url
fileName = DirUtil.formatURL(fileName);
DirUtil.mkdir(DirUtil.getParentPath(fileName));
//写代理类代码写入到临时文件中
FileWriter writer = new FileWriter(new File(fileName));
writer.write(javaSrc);
writer.flush();
writer.close();
//动态编译
//编译需要jdk1.6以上,更低的版本也可以,但没有公开api
//jre运行环境不能使用单独的jre,必须使用jdk的jre
//安装jdk时,有两个jre,一个是独立的,一个是jdk内的
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = complier.getStandardFileManager(null,null,null);
CompilationTask task = complier.getTask(null, fileMgr, null, null, null, fileMgr.getJavaFileObjects(fileName));
task.call();
fileMgr.close();
//载入内存
//使用URLClassLoader,不能使用ClassLoader,
//因为ClassLoader是加载classpath和bin目录下的class文件
URLClassLoader classloadedr = new URLClassLoader(new URL[]{new URL("file:/"+System.getProperty("user.home")+"/src/")});
Class c = classloadedr.loadClass("com.skymr.pattern.proxypat.dynamic.StudentD");
//实例化
//因为c.newInstance调用的是无参构造器,而代理类的构造器是有参的,
//所以不能使用c.newInstance实例化
Constructor constuctor = c.getConstructor(IStudent.class);
return constuctor.newInstance(student);
}
}


public static void main(String[] args) throws Exception {

IStudent stuA = new StudentA();
IStudent stuD = (IStudent)Proxy.newInstance(stuA);
stuD.examine();
}


也可以不用临时文件方式进行动态编译,还可直接生成二进制代码,CGLib,asm就是这么做的。

上面的动态代理很简陋,如果我想生成别的接口类的代理呢?

那就当接口名称作为参数传入嘛,但实现接口方法又比较麻烦了。

public static Object newInstance(Object obj, Class interClass) throws Exception{
String javaSrc = "";
String clazz = "ProxyClazz";
javaSrc += "package com.skymr.pattern.proxypat.dynamic;";
javaSrc += "public class "+clazz+" implements "+interClass.getName()+"{\r\n";
javaSrc += "private "+interClass.getName()+" student;";
javaSrc += "public "+clazz+"("+interClass.getName()+" student){\r\n";
javaSrc += "this.student = student;";
javaSrc += "}\r\n";
//实现所有接口方法
String methodStr = "";
Method[] methods = interClass.getMethods();
//简单实现接口无参无返回值方法,细节就不管了
for(Method method : methods){
methodStr += "public void "+method.getName()+"(){";
methodStr += "long start = System.currentTimeMillis();";
methodStr += "student."+method.getName()+"();";
methodStr += "long end = System.currentTimeMillis();";
methodStr += "System.out.println(\"耗时:\"+(end-start));";
methodStr += "}\r\n";
}
javaSrc += methodStr;
javaSrc += "}";

String fileName = System.getProperty("user.home")+"/src/com/skymr/pattern/proxypat/dynamic/"+clazz+".java";
//格式化url
fileName = DirUtil.formatURL(fileName);
DirUtil.mkdir(DirUtil.getParentPath(fileName));
//写代理类代码写入到临时文件中
FileWriter writer = new FileWriter(new File(fileName));
writer.write(javaSrc);
writer.flush();
writer.close();
//动态编译
//编译需要jdk1.6以上,更低的版本也可以,但没有公开api
//jre运行环境不能使用单独的jre,必须使用jdk的jre
//安装jdk时,有两个jre,一个是独立的,一个是jdk内的
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = complier.getStandardFileManager(null,null,null);
CompilationTask task = complier.getTask(null, fileMgr, null, null, null, fileMgr.getJavaFileObjects(fileName));
task.call();
fileMgr.close();
//载入内存
//使用URLClassLoader,不能使用ClassLoader,
//因为ClassLoader是加载classpath和bin目录下的class文件
URLClassLoader classloadedr = new URLClassLoader(new URL[]{new URL("file:/"+System.getProperty("user.home")+"/src/")});
Class c = classloadedr.loadClass("com.skymr.pattern.proxypat.dynamic."+clazz);
//实例化
//因为c.newInstance调用的是无参构造器,而代理类的构造器是有参的,
//所以不能使用c.newInstance实例化
Constructor constuctor = c.getConstructor(interClass);
return constuctor.newInstance(obj);
}


上面的代理模式也不太好,代理功能只是个计算运行时间而已,且代码被固定死了,如果我想做其他功能的代理,难道还要再写个方法newInstance2不成?

解决办法:

将代理功能的方法当作做成接口,代理实现者必须实现这个接口并传入到newInstance方法作为参数

package com.skymr.pattern.proxypat.dynamic;

import java.lang.reflect.Method;
/**
* 调用处理器,代理使用者必须实现
* @author skymr
*
*/
public interface InvocationHandler {

/**
* 决定怎样调用被代理类的方法
* 被代理类方法前后需要做什么样的事情
* @param instance 被代理类实例
* @param method  被代理类方法
* @throws Exception
*/
public void invoke(Object instance, Method method)throws Exception;

}


package com.skymr.pattern.proxypat.dynamic;

import java.lang.reflect.Method;
/**
* 时间处理器,计算时间
* @author skymr
*
*/
public class TimeHandler implements InvocationHandler {

@Override
public void invoke(Object instance, Method method) throws Exception{
long start = System.currentTimeMillis();
method.invoke(instance, null);
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start));

}

}


@SuppressWarnings({ "unchecked", "rawtypes" })
public static Object newInstance(Object obj, Class interClass, InvocationHandler handler) throws Exception{
String javaSrc = "";
String clazz = "ProxyClazz";
javaSrc += "package com.skymr.pattern.proxypat.dynamic;";
javaSrc += "public class "+clazz+" implements "+interClass.getName()+"{\r\n";
javaSrc += "private "+interClass.getName()+" student;";
javaSrc += "private com.skymr.pattern.proxypat.dynamic.InvocationHandler handler;";
javaSrc += "public "+clazz+"("+interClass.getName()+" student,com.skymr.pattern.proxypat.dynamic.InvocationHandler handler){\r\n";
javaSrc += "this.student = student;";
javaSrc += "this.handler = handler;";
javaSrc += "}\r\n";
//实现所有接口方法
String methodStr = "";
Method[] methods = interClass.getMethods();
//简单实现接口无参无返回值方法,细节就不管了
for(Method method : methods){
methodStr += "public void "+method.getName()+"(){";
//必须捕获异常
methodStr += "try{";
methodStr += "java.lang.reflect.Method method = student.getClass().getMethod(\""+method.getName()+"\");\r\n";
methodStr += "handler.invoke(student,method);}";
methodStr += "catch(Exception e){}";
methodStr += "}\r\n";
}
javaSrc += methodStr;
javaSrc += "}";

String fileName = System.getProperty("user.home")+"/src/com/skymr/pattern/proxypat/dynamic/"+clazz+".java";
//格式化url
fileName = DirUtil.formatURL(fileName);
DirUtil.mkdir(DirUtil.getParentPath(fileName));
//写代理类代码写入到临时文件中
FileWriter writer = new FileWriter(new File(fileName));
writer.write(javaSrc);
writer.flush();
writer.close();
//动态编译
//编译需要jdk1.6以上,更低的版本也可以,但没有公开api
//jre运行环境不能使用单独的jre,必须使用jdk的jre
//安装jdk时,有两个jre,一个是独立的,一个是jdk内的
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = complier.getStandardFileManager(null,null,null);
CompilationTask task = complier.getTask(null, fileMgr, null, null, null, fileMgr.getJavaFileObjects(fileName));
task.call();
fileMgr.close();
//载入内存
//使用URLClassLoader,不能使用ClassLoader,
//因为ClassLoader是加载classpath和bin目录下的class文件
URLClassLoader classloadedr = new URLClassLoader(new URL[]{new URL("file:/"+System.getProperty("user.home")+"/src/")});
Class c = classloadedr.loadClass("com.skymr.pattern.proxypat.dynamic."+clazz);
//实例化
//因为c.newInstance调用的是无参构造器,而代理类的构造器是有参的,
//所以不能使用c.newInstance实例化
Constructor constuctor = c.getConstructor(interClass,InvocationHandler.class);
return constuctor.newInstance(obj,handler);
}


取得代理实例时要传入InvocationHandler实例

public static void main(String[] args) throws Exception {

IStudent stuA = new StudentA();
IStudent stuD = (IStudent)Proxy2.newInstance(stuA,IStudent.class,new TimeHandler());
stuD.examine();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: