一.首先使用API中的Proxy来实现动态代理的例子
(1)定义一个接口Moveable, 里面就有一个move方法
package com.feng.proxy;
/**
* 定义的接口,实现对这个接口的代理
* 简单起见,只定义一个方法来演示动态代理的效果
* @author Administrator
*
*/
public interface Moveable {
void move();
}
(2)为Moveable接口定义实现类Car
package com.feng.proxy;
/**
* 为Moveable定义一个实现类
* 实现move方法
* @author Administrator
*
*/
public class Car implements Moveable{
@Override
public void move() {
System.out.println("I am running");
}
}
(3)定义一个TimeInvocationHandler,将要添加的逻辑放在这个类中
package com.feng.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 定义一个InvocationHandler的实现类,定义要添加的操作
* @author Administrator
*
*/
public class TimeInvocationHandler implements InvocationHandler{
//定义一个被代理对象
private Object target;
//定义一个set方法
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//原操作前添加的操作
System.out.println("begin logs...");
//原操作,就是target。method();
Object result = method.invoke(target, args);
//原操作后添加的操作
System.out.println("end logs...");
return result;
}
}
(4)定义一个代理类,负责返回一个代理对象
package com.feng.proxy;
import java.lang.reflect.Proxy;
/**
* 时间代理类
* 获取一个代理类对象
* @author Administrator
*
*/
public class TimeProxy {
public static Object getProxyInstance(Object target)
{
TimeInvocationHandler h = new TimeInvocationHandler();
h.setTarget(target);
//newProxyInstance 是Proxy的静态方法,三个参数为类加载器,被代理的接口数组,InvacationHandler
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), h);
}
}
(5)编写一个测试类
package com.feng.proxy;
public class Client {
/**
* 测试类
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Moveable m = (Moveable) TimeProxy.getProxyInstance(new Car());
m.move();
}
}
运行上面的程序,结果如下:我们已经成功的在move()方法前后加上了我们想添加的操作
二.简单代理,实现代理的两种形式继承与聚合
还是以上面的例子为例,有一个moveable接口,有一个实现类car,我现在要在car的move()方法前加一些操作,在后加一些操作,应该如何实现
(1)通过继承实现代理
定义一个TimerProxyByExtends
package com.feng.proxy;
/**
* 通过继承car,就可以在car的move()方法前后就一些操作了
* @author Administrator
*
*/
public class TimeProxyByExtends extends Car{
@Override
public void move() {
//加在前面的操作
System.out.println("前面的操作");
super.move();
//加在后面的操作
System.out.println("后面的操作");
}
}
编写一个测试类
package com.feng.myproxy;
public class Client {
/**
* 测试类
* @param args
*/
public static void main(String[] args) {
Moveable m = new TimeProxyByExtends();
m.move();
}
}
执行结果为:
(2)通过聚合实现代理
定义一个代理类
package com.feng.myproxy;
public class TimeProxyByOneOf implements Moveable{
private Moveable m;
public TimeProxyByOneOf(Moveable m) {
super();
this.m = m;
}
@Override
public void move() {
// TODO Auto-generated method stub
System.out.println("通过聚合实现代理前操作");
m.move();
System.out.println("通过聚合实现代理后操作");
}
}
定义一个测试类
package com.feng.myproxy;
public class Client {
/**
* 测试类
* @param args
*/
public static void main(String[] args) {
TimeProxyByOneOf m = new TimeProxyByOneOf(new Car());
m.move();
}
}
测试结果为:
现在比较一下这两种方式的区别:
假设现在有一种场景,我不仅要对Car实现时间的代理还要实现记录日志的代理还要实现权限的代理,在这我就用a, b ,c来表示这三种代理
如果使用继承的方式实现,我要有一个car1实现a, car2实现b,car3实现c,不仅如此我还要对代理进行叠加,我想实现ab(先实现时间代理再实现日志代理)接着要定义car4去继承car1,我又想实现ac,又要定义一个car5,总之有多少种组合我就要定义多少了代理类,这样很容易引起类爆炸。因为在实现代理的时候我们通常不适用继承来实现。
如果使用聚合的方式来实现,我们可以定义car1实现a, 定义car2实现b, 定义car3实现c,接着如果实现组合的情况,比如实现ab,我只需要将car1的对象作为car2的被代理对象即可,不需要添加新的类,下面来模拟一个多种代理一起使用的情况
在上面程序的继承上我再定义一个日记代理和权限代理,实现如下:
package com.feng.myproxy;
public class LogsProxy implements Moveable{
private Moveable m;
public LogsProxy(Moveable m) {
super();
this.m = m;
}
@Override
public void move() {
System.out.println("开始记录日志");
m.move();
System.out.println("结束记录日志");
}
}
package com.feng.myproxy;
public class AuthorizedProxy implements Moveable{
private Moveable m;
public AuthorizedProxy(Moveable m) {
super();
this.m = m;
}
@Override
public void move() {
System.out.println("开始检查权限");
m.move();
}
}
测试类:如果想要换组合顺序的话,只要改一下代码中的顺序即可,或者写顺序写到配置文件中,从配置文件中读取
package com.feng.myproxy;
public class Client {
/**
* 测试类
* @param args
*/
public static void main(String[] args) {
TimeProxyByOneOf m = new TimeProxyByOneOf(new Car());
LogsProxy l = new LogsProxy(m);
AuthorizedProxy a = new AuthorizedProxy(l);
a.move();
}
}
执行结果如下:
这只是简单的代理,存在一些不灵活的地方,比如这个时间代理现在只能代理实现了Moveable接口的类,如果有其他接口的话,还要再写一个另一个接口的代理类,如果有一百个接口那么就要写上一百个接口的时间代理类,显然这很不灵活,能不能写一个代理类能适应于所以的类的,这就要用到动态代理了
三. 动态代理
使用上述程序中的Moveable,car类
首先定义一个自己的Proxy,里面有一个静态的newProxyInstance方法,在该方法中实现动态的编译,加载,实例化的过程,并返回类的对象
(1)现在类中定义一个字符串,字符串里面放着我们之前写的代理类代码,对这个字符串操作,先将字符串写到一个java文件中,然后进行编译,加载,实例化
代码如下:
package com.feng.dynamicproxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
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;
public class Proxy {
public static Object newProxyInstance()
{
//1,定义一个类的字符串, 将时间代理类的代码原封不动的写到字符串中
String rt = "\r\n";
String str = "package com.feng.dynamicproxy;"+rt+
"public class TimeProxy implements Moveable{"+rt+
" private Moveable m;"+rt+
" public TimeProxy(Moveable m) {"+rt+
" super();"+rt+
" this.m = m;"+rt+
" }"+rt+
" @Override"+rt+
" public void move() {"+rt+
" System.out.println(\"通过聚合实现代理前操作\");"+rt+
" m.move();"+rt+
" System.out.println(\"通过聚合实现代理后操作\");"+rt+
" }"+rt+
"}";
//2. 将字符串输出到一个java文件中,放到哪里都可以,我放到的是当前目录下的com.feng.dynamicproxy包下
String fileName = System.getProperty("user.dir")+"/src/com/feng/dynamicproxy/TimeProxy.java";
File file = new File(fileName);
FileWriter fw;
try {
fw = new FileWriter(file);
fw.write(str);
fw.flush();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
//3.对声场的TimeProxy。java进行编译,这里使用API自带的JavaCompiler进行编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMsg = compiler.getStandardFileManager(null, null, null);
Iterable untis = fileMsg.getJavaFileObjects(fileName);
CompilationTask task = compiler.getTask(null, fileMsg, null, null, null, untis);
task.call();
//4.使用加载器将编译好的二进制文件加载到内存中
Class c = null;
try {
URLClassLoader ul = new URLClassLoader(new URL[]{ new URL("file:/"+System.getProperty("user.dir")+"/src")});
c = ul.loadClass("com.feng.dynamicproxy.TimeProxy");
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//5.出师话对象
Object m = null;
try {
Constructor construct = c.getConstructor(Moveable.class);
m = construct.newInstance(new Car());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return m;
}
}
编写测试类:
package com.feng.dynamicproxy;
public class Client {
/**
* 测试类
* @param args
*/
public static void main(String[] args) {
Moveable m = (Moveable) Proxy.newProxyInstance();
m.move();
}
}
执行结果如下图:
(2)把接口当做参数传递到Proxy中,这样就可以操作所有的类了,正常来说一个类可能会实现多个接口,但是这里方便起见我就模拟一个接口的情况,而且不做权限和返回值的限制了,只需要了解原理即可。
对上面的Proxy类进行改造:将有接口的地方都替换成参数的类型,获取接口中的方法,动态的构造方法
package com.feng.dynamicproxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
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;
public class Proxy {
public static Object newProxyInstance(Class interf)
{
String rt = "\r\n";
//动态获取接口中的方法
String methodStr = "";
Method[] methods = interf.getMethods();
for(Method m: methods)
{
methodStr+=" @Override"+rt+
" public void "+m.getName()+"() {"+rt+
" System.out.println(\"通过聚合实现代理前操作\");"+rt+
" m.move();"+rt+
" System.out.println(\"通过聚合实现代理后操作\");"+rt+
" }";
}
//1,定义一个类的字符串, 将时间代理类的代码原封不动的写到字符串中
String str = "package com.feng.dynamicproxy;"+rt+
"public class TimeProxy implements "+interf.getName()+"{"+rt+
" private "+interf.getName() +" m;"+rt+
" public TimeProxy("+interf.getName() +" m) {"+rt+
" super();"+rt+
" this.m = m;"+rt+
" }"+rt+
methodStr+rt+
"}";
//2. 将字符串输出到一个java文件中,放到哪里都可以,我放到的是当前目录下的com.feng.dynamicproxy包下
String fileName = System.getProperty("user.dir")+"/src/com/feng/dynamicproxy/TimeProxy.java";
File file = new File(fileName);
FileWriter fw;
try {
fw = new FileWriter(file);
fw.write(str);
fw.flush();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
//3.对声场的TimeProxy。java进行编译,这里使用API自带的JavaCompiler进行编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMsg = compiler.getStandardFileManager(null, null, null);
Iterable untis = fileMsg.getJavaFileObjects(fileName);
CompilationTask task = compiler.getTask(null, fileMsg, null, null, null, untis);
task.call();
//4.使用加载器将编译好的二进制文件加载到内存中
Class c = null;
try {
URLClassLoader ul = new URLClassLoader(new URL[]{ new URL("file:/"+System.getProperty("user.dir")+"/src")});
c = ul.loadClass("com.feng.dynamicproxy.TimeProxy");
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//5.出师话对象
Object m = null;
try {
Constructor construct = c.getConstructor(interf);
m = construct.newInstance(new Car());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return m;
}
}
编写一个测试类:
package com.feng.dynamicproxy;
public class Client {
/**
* 测试类
* @param args
*/
public static void main(String[] args) {
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);
m.move();
}
}
结果如图:
上面的程序还是有问题,我们现在能够动态的生成传递进来的接口的方法了,但是方法体里面的语句还是在代码中写死的,这显然不灵活,如果方法体里面要加什么操作能够让用户来指定那就完美了,我们再次进行改造
(3)再添加一个参数InvocationHandler 里面有一个方法invoke,invoke在具体实现了InvocationHandler接口的类中实现。
首先定义自己的InvocationHandler接口
package com.feng.dynamicproxy;
import java.lang.reflect.Method;
public interface InvocationHandler {
//真正实现中是有返回值的,并且参数中有方法的参数数组,这里为了简单模拟,就简化操作了
public void invoke(Object o, Method m);
}
接着定义TimerInvocationhandler类实现InvocationHandler接口
package com.feng.dynamicproxy;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
private Object target;
public TimerInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
// TODO Auto-generated method stub
System.out.println("实现自定义方法体的操作前");
try {
m.invoke(target, new Class[]{});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("实现自定义方法体的操作前");
}
}
然后修改Proxy,将方法体的操作提供给用户自定义去编写,在动态生成代码的时候让代码调用InvocationHandler里面的invoke方法,同时传递自己,方法,一遍在InvocationHandler的实现类中调用此方法,同时就可以在此方法的前后加一些操作了。这里动态生成的代码中也不再是传一个具体的接口了,而是应该传一个InvocationHandler类型的参数,只要是调用方法,就会回调InvocationHandler方法中的invoke方法,这里的逻辑有点复杂,多看几遍就能看懂了
package com.feng.dynamicproxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import com.feng.dynamicproxy.InvocationHandler;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class Proxy {
public static Object newProxyInstance(Class interf, InvocationHandler h)
{
String rt = "\r\n";
//动态获取接口中的方法
String methodStr = "";
Method[] methods = interf.getMethods();
for(Method m: methods)
{
methodStr+=" @Override"+rt+
" public void "+m.getName()+"() {"+rt+
" try{"+rt+
" Method method = "+ interf.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
" h.invoke(this, method);"+rt+
" }catch(Exception e){}"+rt+
" }";
}
//1,定义一个类的字符串, 将时间代理类的代码原封不动的写到字符串中
String str = "package com.feng.dynamicproxy;"+rt+
"import java.lang.reflect.Method;"+rt+
"public class TimeProxy implements "+interf.getName()+"{"+rt+
" private "+h.getClass().getName() +" h;"+rt+
" public TimeProxy("+h.getClass().getName() +" h) {"+rt+
" super();"+rt+
" this.h = h;"+rt+
" }"+rt+
methodStr+rt+
"}";
//2. 将字符串输出到一个java文件中,放到哪里都可以,我放到的是当前目录下的com.feng.dynamicproxy包下
String fileName = System.getProperty("user.dir")+"/src/com/feng/dynamicproxy/TimeProxy.java";
File file = new File(fileName);
FileWriter fw;
try {
fw = new FileWriter(file);
fw.write(str);
fw.flush();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
//3.对声场的TimeProxy。java进行编译,这里使用API自带的JavaCompiler进行编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMsg = compiler.getStandardFileManager(null, null, null);
Iterable untis = fileMsg.getJavaFileObjects(fileName);
CompilationTask task = compiler.getTask(null, fileMsg, null, null, null, untis);
task.call();
//4.使用加载器将编译好的二进制文件加载到内存中
Class c = null;
try {
URLClassLoader ul = new URLClassLoader(new URL[]{ new URL("file:/"+System.getProperty("user.dir")+"/src")});
c = ul.loadClass("com.feng.dynamicproxy.TimeProxy");
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//5.出师话对象
Object m = null;
try {
Constructor construct = c.getConstructor(h.getClass());
m = construct.newInstance(h);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return m;
}
}
编写测试类:
package com.feng.dynamicproxy;
public class Client {
/**
* 测试类
* @param args
*/
public static void main(String[] args) {
InvocationHandler h = new TimerInvocationHandler(new Car());
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class, h);
m.move();
}
}
结果如下图所示:
到此我们动态生成的代码中已经没有固定死的代码的,我们定义的代理也不再单单是为某一类对象的代理了。在真正的动态代理模式中会考虑更多的细节,但大致思想就是这个样子的。