##1、静态代理
代理模式是客户不直接访问到委托类,实现与委托类之间的解耦,并且代理类可以给委托类添加一些额外的功能例如日志、权限等。静态代理是指代理类是在编译阶段就生成class文件的代理模式,工程结构如下:
第一个包中是动态代理用到的日志类,第二个包中委托类和静态代理类,第三个包中分别测试静态代理和动态代理。代理类和委托类必须实现相同的接口,先看委托类代码:
public class BigTank implements Tank {
@Override
public void shoot() {
// TODO Auto-generated method stub
System.out.println("shoot.....");
}
@Override
public void move() {
// TODO Auto-generated method stub
System.out.println("move....");
}
}
接着看代理类代码:
public class LogBigTank implements Tank {
Tank tank;
public LogBigTank(Tank t) {
// TODO Auto-generated constructor stub
this.tank = t;
}
@Override
public void shoot() {
// TODO Auto-generated method stub
System.out.println("before....");
tank.shoot();
System.out.println("later....");
}
@Override
public void move() {
// TODO Auto-generated method stub
System.out.println("before....");
tank.move();
System.out.println("later....");
}
}
最后看测试代码:
public class TestStaticProxy {
public static void main(String[] args) {
// TODO Auto-generated method stub
BigTank bigTank = new BigTank();
LogBigTank log = new LogBigTank(bigTank);
log.shoot();
}
}
可以看到代理实现了,但是静态代理有个缺点,就是如果我们委托类的方法增加,那么代理类必定要进行修改,这样代码维护比较复杂,然后静态代理一个代理类只能代理同一个接口下的代理对象,如果同样的代理功能(例如日志功能),那么需要实现多个委托类的日志代理类,这样做首先麻烦,其次会有很多重复代码,开发起来麻烦,所以后面介绍动态代理。
##2、动态代理
在动态代理中,所有的代理对象的class文件都是在运行时生成的,jdk是通过调用编译器生成class文件。日志处理类代码:
public class LogHandle implements InvocationHandler{
Object taget;
public Object getNewInstance(Object object){
this.taget = object;
return Proxy.newProxyInstance(taget.getClass().getClassLoader(),
taget.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("befoer....");
method.invoke(taget, args);
System.out.println("later....");
return null;
}
}
这里是通过getNewInstance方法将日志类和委托类绑定起来,并且生成对应的日志代理类,invoke方法就是对委托类对应的方法实现日志代理。测试代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
LogHandle logHandle = new LogHandle();
Tank tankMag = (Tank) logHandle.getNewInstance(new BigTank());
tankMag.shoot();
}
可以看到通过动态代理实现的代理只需要实现一个通用的处理类和委托类就可以生成一个代理类,这个处理类可以和任何委托类绑定在一起生成代理对象,所以使用起来方便了很多。
##3、动态代理原理
上面分别介绍了静态代理和动态代理,在这里模拟一下jdk动态代理的实现。首先看工程结构:
第一个文件是模仿JDK中的InvocationHandler接口,第二个就是日志处理类,需要实现第一个接口,第三个是生成动态代理类的类,最后一个是测试。首先看接口代码:
public interface InvocationHandler {
public void invoke(Object object,Method method);
}
这里和jdk一样都有invoke方法,但这里简便来用,就不加方法的参数了。
再看实现类代码:
public class LogHHH implements InvocationHandler{
Object target;
public Object getNewInstance(Object object) throws Exception{
this.target = object;
return com.ly.proxyrealize.Proxy.newProxyInstance(target.getClass().getInterfaces(), this);
}
@Override
public void invoke(Object object, Method method) {
// TODO Auto-generated method stub
System.out.println("before....");
try {
method.invoke(target);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("later....");
}
}
这个实现类和jdk中的实现类是基本一样的。所以最后看我们真正生成代理对象的类Proxy:
public class Proxy {
public static Object newProxyInstance(Class<?>[] infces, InvocationHandler h) throws Exception{
String methodStr = "";
String rt = "\r\n";
Class infce = infces[0];
Method[] methods = infce.getMethods();
//对方法进行遍历,然后调用h的invoke方法,使得每个方法都没代理了。
for(Method m : methods) {
methodStr += "@Override" + rt +
"public void " + m.getName() + "() {" + rt +
" try {" + rt +
" Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
" h.invoke(this, md);" + rt +
" }catch(Exception e) {e.printStackTrace();}" + rt +
"}";
}
//代理对象的完整字符串,这里主要创建构造方法传入handle,
String src =
"package com.ly.proxyrealize;" + rt +
"import java.lang.reflect.Method;" + rt +
"public class $Proxy1 implements " + infce.getName() + "{" + rt +
" public $Proxy1(InvocationHandler h) {" + rt +
" this.h = h;" + rt +
" }" + rt +
" com.ly.proxyrealize.InvocationHandler h;" + rt +
methodStr +
"}";
//通过字符串写java文件
String fileName = System.getProperty("user.dir")
+ "/src/com/ly/proxyrealize/$Proxy1.java";
File file = new File(fileName);
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(src);
fileWriter.flush();
fileWriter.close();
//对java文件进行编译
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();
//对这个类进行加载
URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir") +"/src/")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("com.ly.proxyrealize.$Proxy1");
System.out.println(c);
//生成代理对象
Constructor ctr = c.getConstructor(InvocationHandler.class);//通过反射获得构造器
System.out.println(ctr);
Object object = (Object) ctr.newInstance(h); //通过构造器生成实例对象
return object;
}
}
最后看我们测试代码中的调用:
BigTank b = new BigTank();
LogHHH logHandle = new LogHHH();
Tank lTank = (Tank) logHandle.getNewInstance(new BigTank());
lTank.move();
可以看到,这里也和我们JDK中的使用是完全一样的,所以这里完成了模拟,最后来说一写模拟的整个实现步骤。首先在我们的proxy类中,将委托类是实现的接口集合和处理类传入,首先编写一个代理类模版并转化为字符串,这个模版主要需要完成两件事,第一件是编写构造函数引用处理类,第二个就是通过接口反射得到方法列表,对这个方法列表进行遍历,让这些方法调用处理类的invoke方法,并将方法名作为参数传递,之后处理类的invoke方法就会添加代理事件并且再调用委托类的对应的方法,形成代理模式。模版编写完成后调用编译器进行编译,在通过类加载器进行加载,最后获得这个类的构造方法,通过构造方法反射生成实例对象,最后将这个对象返回即可。所以在测试程序中,只需要将委托类和处理类绑定,在通过处理类的方法获取到代理类即可。
整个工程的代码连接:
https://download.csdn.net/download/liuyuanq123/10401568