代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
类图:
模拟情景:
1.有一个Tank类,实现Moveable接口中的move(),方法,现在想要对Tank的move()方法前后做些日志记录的操作,这时就可以使用代理。
public interface Moveable {
void move();
}
2.Tank类
import java.util.Random;
/**
* 被代理对象
* @author dell
*
*/
public class Tank implements Moveable{
@Override
public void move() {
System.out.println("Tank moving...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.代理类(TankTimerProxy)
public class TankTimerProxy implements Moveable{
private Moveable t;//被代理对象
public TankTimerProxy(Moveable t){
this.t = t;
}
/**
* 代理的move方法,前后加自定义逻辑
* @param o 代理对象
* @param method 方法处理逻辑
*/
@Override
public void move() {
long start = System.currentTimeMillis();
t.move();//被代理对象的move方法。
long end = System.currentTimeMillis();
System.out.println("UsedTimes: " + (end - start));
}
}
从上面的示例中可以看到,我们本来想使用Tank的move方法,现在通过TankTimerProxy的move()方法,间接的使用了Tank的方法,此时TankTimerProxy就是Tank的一个代理。
Java动态代理模拟:
如上:TankTimerProxy是我们手动编写的,也可称为静态代理,倘若能动态生成所需的代理类,如TankTimerProxy;则更加方便我们对被代理对象添加自定义处理逻辑。即,动态代理。
为模拟Java,声明Proxy类,使用Proxy类产生所需的各种具体代理对象,TankTimerProxy,TankLogProxy等。
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
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.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
* 代理的好处,可以对任意的对象,任意接口,实现任意的代理。
* @author dell
*
*/
public class Proxy{
/**
* 代理类名
*/
private static final String PROXYNAME = "$proxy1";
/**
* 模拟JDK代理
* @param infce 接口
* @param handler 代理处理逻辑
* @return
* @throws Exception
*/
public static Object newProxyInstance(Class infce,InvocationHandler handler) throws Exception{
/**
* Step1.1:
* " @Override"+ rt +
" public void move() {"+ rt +
" long start = System.currentTimeMillis();"+ rt +
" t.move();"+ rt +
" long end = System.currentTimeMillis();"+ rt +
" System.out.println(\"UsedTimes: \" + (end - start));"+ rt +
" }"+ rt +
*/
String methodStr = "";
String rt = "\r\n";
Method[] methods = infce.getMethods();
/**
* step:2
* 对方法的时间代理,且扩展麻烦。为增加不同的处理需重新创建newProxyInstance,为解决此问题,
* 创建单独的处理逻辑InvocationHandler类
for (Method method : methods) {
methodStr += " @Override"+ rt +
" public void " + method.getName() + "() {"+ rt +
" long start = System.currentTimeMillis();"+ rt +
" t." + method.getName() + "();"+ rt +
" long end = System.currentTimeMillis();"+ rt +
" System.out.println(\"UsedTimes: \" + (end - start));"+ rt +
" }";
}
*/
/**
* Step3:
* 为接口的所有方法,动态添加处理逻辑
*/
for (Method method : methods) {
methodStr += " @Override"+ rt +
" public void " + method.getName() + "() {"+ rt +
" try{" + rt +
" Method md = " + infce.getName()+".class.getMethod(\""+method.getName()+"\");" +rt +
" h.invoke(this,md);"+ rt +
" }catch(Exception e){ e.printStackTrace();}"+ rt +
" }";
}
/**
* Step:1
* 上面的TankTimerProxy为手动编写,现在我们用程序自动生成。
* 首先、生成TankTimerProxy类的字符串src,写入到磁盘文件
* 然后、编译生成的文件,生成class文件
* 最后、把生成的class文件load内存并创建TankTimerProxy类的对象
*/
String src = "package com.lxf.proxy;" + rt +
"import java.lang.reflect.Method;"+ rt +
"public class " + PROXYNAME + " implements "+infce.getName()+"{"+ rt +
" private InvocationHandler h;"+ rt +
" public " + PROXYNAME + "(InvocationHandler h) {"+ rt +
" this.h = h;"+ rt +
" }" + rt +
methodStr + rt +
"}";
String fileName = "D:/src/com/lxf/proxy/" + PROXYNAME + ".java";
// String fileName = System.getProperties().getProperty("user.dir")+"/src/com/lxf/proxy/TankTimerProxy.java";
System.out.println(fileName);
File file = new File(fileName);
try {
FileWriter fw = new FileWriter(file);
fw.write(src);
fw.flush();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
//compile
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(fileName);
CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
//load into memory and create an instance
URL[] urls = new URL[]{new URL("file:/"+"D:/src/")};
URLClassLoader ul = new URLClassLoader(urls);//使用classLoader时,必须保证.class文件在classpath路径中,所有为保证class可在任何位置,
//使用URLClassLoader
Class c = ul.loadClass("com.lxf.proxy." + PROXYNAME);//c:类的二进制码对象
System.out.println(c);
Constructor cstr = c.getConstructor(InvocationHandler.class);//c.newInstance(),调用的是无参构造方法。
// Moveable m = (Moveable) cstr.newInstance(new Tank());
// m.move();
Object m = cstr.newInstance(handler);
return m;
}
}
自动生成的代理类存储在硬盘中,当然此类对用户是透明的,用户不用了解此列就可以实现对Tank的代理。
d:/src/com/lxf/proxy/$proxy1.java
package com.lxf.proxy;
import java.lang.reflect.Method;
public class $proxy1 implements com.lxf.proxy.Moveable{
private InvocationHandler h;
public $proxy1(InvocationHandler h) {
this.h = h;
}
@Override
public void move() {
try{
Method md = com.lxf.proxy.Moveable.class.getMethod("move");
h.invoke(this,md);
}catch(Exception e){ e.printStackTrace();}
}
}
为了方便的在方法前后添加处理逻辑(指定处理方式)如:添加日志,时间。即动态指定对方法的处理,所以定义InovactionHandler接口,定义代理对被代理对象的处理方式。
public interface InvocationHandler {
/**
* 代理处理方式
* @param o 代理对象
* @param method 被代理对象的方法
*/
public void invoke(Object o, Method method);
}
各种具体的代理对象,通过实现此接口来实现。
import java.lang.reflect.Method;
/**
* 自定义代理处理逻辑
* @author dell
*
*/
public class TimerHandler implements InvocationHandler {
/**
* 被代理对象
*/
private Object target;
public TimerHandler(Moveable obj) {
this.target = obj;
}
/**
* 前后加自定义逻辑
* @param o 代理对象
* @param method 方法处理逻辑
*/
@Override
public void invoke(Object o, Method method) {
long start = System.currentTimeMillis();
System.out.println("o Name: "+o.getClass().getName());
try {
/**
* 对带有指定参数的指定对象(target)调用由此 Method 对象表示的底层方法
* 然后,调用被代理对象的方法
*/
method.invoke(target);
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("UsedTimes: " + (end - start));
}
}
客户端调用:
public class Client {
public static void main(String[] args) throws Exception {
Moveable tank = new Tank();
InvocationHandler ih = new TimerHandler(tank);
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class,ih);
m.move();
}
}
当然,上面使用了策略模式,方便对添加不同的处理逻辑。
至此,简单的Java 代理就模拟出来了。以上是自己听课(尚学堂:马士兵)《设计模式》所做记录,仅供学习。