一、代理模式的概念
为其他对象提供一种代理以控制对这个对象的访问,代理对象起到中介的作用,可去掉或增加额外的服务。
举个例子:我要买火车票,我可以在火车站买,但是火车站太远了,此时我们就可以通过第三方去购买,当前第三方也可以响应的收取一定的手续费(当然国内没有一家第三方收费,说这个是为了更好理解接下来代理模式的强化对象功能)。
二、常见几种代理模式
虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候进行创建
远程代理:类似于客户端服务器模式,为不同地理对象,提供局域网代表对象
保护代理:控制对一个对象的访问权限(权限控制)
智能代理:提供对目标对象的额外服务
三、静态代理
概念:代理和被代理对象在代理之前是确定的。他们都是实现相同的接口或者继承相同的抽象类。
接下来我们实现一个过程,就是汽车行驶,同时记录汽车行驶的时间
没有代理我们是这样实现的
public class Car implements Moveable {
@Override
public void move() {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
//通过线程睡眠表示汽车行驶
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中...");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
}
}
这样写虽然没什么问题,但违背了java的单一职责,车只要会动就行了,为什么要让他记录运行时间呢。
那么静态代理如何处理呢,我们可以创建一个记录仪类,记录时间的功能就交给它来完成。
public class Recorder implements Moveable{
private Car car;
public Recorder(Car car) {
this.car = car;
}
@Override
public void move() {
long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶");
car.move();
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
}
}
缺点: 毫无疑问,这样写就是把方法写死了,如果汽车还有其他方法需要加上记录时间的功能呢,或者还有其他实现类如火车,飞机,轮船,那么这些代理类越来越多,增大了代码量。
其他:例子中这种方式是聚合方式,还一种继承方式(就是直接继承car,这种方式会当功能叠加的是类会无限增大,所有不推荐使用)
三、JDK动态代理
实现
public class JDKTimeHandler implements InvocationHandler {
private Object target;
public JDKTimeHandler(Object target){
super();
this.target = target;
}
/**
* agr0:被代理对象
* arg1:被代理对象的方法
* arg2:方法的参数
*
* 返回值:被代理对象的方法返回值
*/
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
arg1.invoke(target);
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
return null;
}
}
测试类:
Car car = new Car();
InvocationHandler ih = new JDKTimeHandler(car);
Moveable m = (Moveable) Proxy.newProxyInstance(car.getClass().getClassLoader(), car.getClass().getInterfaces(),ih);
m.move();
Proxy.newProxyInstance(loader,interface,h)
三个参数的意义:loader 类加载器,interface类的实现接口,h InvocationHandler
四、CGLIB动态代理
区别:
这个代理与jdk代理区别在于jdk只能代理实现了接口的类,而CGLIB则针对类来实现代理,指定目录类产生一个子类,通过方法拦截所有父类方法的调用
实现:
public class Train {
public void move() {
System.out.println("火车行驶中");
}
}
public class CGLIBHandler implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
// 设置创建子类的类
enhancer.setSuperclass(clazz);
// 设置回调
enhancer.setCallback(this);
// 创建子类
return enhancer.create();
}
/**
* arg0 目标类的实例
* arg1 目标方法的反射对象
* arg2 方法参数
* arg3 代理类的实例
*/
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
// 代理类调用父类方法
arg3.invokeSuper(arg0, arg2);
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
return null;
}
}
CGLIBHandler ch = new CGLIBHandler();
Train t = (Train) ch.getProxy(Train.class);
t.move();
五、动态代理实现的原理
思路: 通过Proxy的newProxyInstance实现代理对象
1、声明一段源码(动态产生代理)
2、编译源码(JDK Compiler API),产生新的类(代理类)
3、将这个类load到内存中,产生一个新的对象(代理对象)
4、return 代理对象
实现:
我也创建一个proxy类
public class Proxy {
public static Object newProxyInstance() {
return null;
}
}
把之前写的聚合代码放进去
public class Proxy {
public static Object newProxyInstance() {
public class Recorder implements Moveable{
package month0408;
private Car car;
public Recorder(Car car) {
this.car = car;
}
@Override
public void move() {
long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶");
car.move();
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶...时间"+(stopTime-startTime)+"毫秒 ");
}
}
}
}
把上述变成一个字符串
public class Proxy {
public static Object newProxyInstance() {
// windows下的换行符
String rt = "\r\n";
String str =
"package month0408;"+rt+
"public class $Proxy0 implements Moveable {"+rt+
"private Moveable m;"+rt+
"public $Proxy0(Moveable m) {"+rt+
"super();"+rt+
"this.m = m;"+rt+
"}"+rt+
"@Override"+rt+
"public void move() {"+rt+
"long startTime = System.currentTimeMillis();"+rt+
"System.out.println(\"汽车开始行驶\");"+rt+
"m.move();"+rt+
"long stopTime = System.currentTimeMillis();"+rt+
"System.out.println(\"汽车结束行驶...时间\"+(stopTime-startTime)+\"毫秒 \");"+rt+
"}}";
return null;
}
}
其实字符串就是源码(小提示:jdk官方动态代理类的名字为$Proxy0)
在加入源码地址
// 创建源代码的路径
String fileName = System.getProperty("user.dir")+"/src/main/java/month0408/$Proxy0.java";
File file = new File(fileName);
try {
FileUtils.write(file, str);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
FileUtils是commons-io的方法
运行结果:
在方法上我们添加一个传入接口的参数
public class Proxy {
public static Object newProxyInstance(Class infce) {
// windows下的换行符
String rt = "\r\n";
String str =
"package month0408;"+rt+
// 之前的接口Moveable改成通过参数的getName方法获取
"public class $Proxy0 implements "+infce.getSimpleName() +"{"+rt+
// 属性的类型通过参数的getName方法获取
"private "+infce.getSimpleName()+" m;"+rt+
// 参数类型通过参数的getName方法获取
"public $Proxy0("+infce.getSimpleName()+" m) {"+rt+
"super();"+rt+
"this.m = m;"+rt+
"}"+rt+
"@Override"+rt+
"public void move() {"+rt+
"long startTime = System.currentTimeMillis();"+rt+
"System.out.println(\"汽车开始行驶\");"+rt+
"m.move();"+rt+
"long stopTime = System.currentTimeMillis();"+rt+
"System.out.println(\"汽车结束行驶...时间\"+(stopTime-startTime)+\"毫秒 \");"+rt+
"}}";
// 创建源代码的路径
String fileName = System.getProperty("user.dir")+"/src/main/java/month0408/$Proxy0.java";
File file = new File(fileName);
try {
FileUtils.write(file, str);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
这样就实现了任何接口,同理被代理的方法名字也是这样
public class Proxy {
public static Object newProxyInstance(Class infce) {
// windows下的换行符
String rt = "\r\n";
StringBuffer methodStr = new StringBuffer();
for(Method m:infce.getMethods()) {
methodStr.append("@Override"+rt+
// 方法名字
"public void "+m.getName()+"() {"+rt+
"long startTime = System.currentTimeMillis();"+rt+
"System.out.println(\"汽车开始行驶\");"+rt+
// 方法名字
"m."+m.getName()+"();"+rt+
"long stopTime = System.currentTimeMillis();"+rt+
"System.out.println(\"汽车结束行驶...时间\"+(stopTime-startTime)+\"毫秒 \");"+rt+
"}};");
}
String str =
"package month0408;"+rt+
// 之前的接口Moveable改成通过参数的getName方法获取
"public class $Proxy0 implements "+infce.getSimpleName() +"{"+rt+
// 属性的类型通过参数的getName方法获取
"private "+infce.getSimpleName()+" m;"+rt+
// 参数类型通过参数的getName方法获取
"public $Proxy0("+infce.getSimpleName()+" m) {"+rt+
"super();"+rt+
"this.m = m;"+rt+
"}"+rt+ methodStr.toString();
// 创建源代码的路径
String fileName = System.getProperty("user.dir")+"/src/main/java/month0408/$Proxy0.java";
File file = new File(fileName);
try {
FileUtils.write(file, str);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
现在源代码文件有了,但是如何编译呢?
// 获得当前系统的编译器
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
/*diagnosticListener:监听器
* locale, charset都是国际化的参数
*/
// 文件管理者
StandardJavaFileManager sjfm =
jc.getStandardFileManager(null,null,null);
//获取文件
Iterable units = sjfm.getJavaFileObjects(fileName);
//编译任务
/**
* out:输出的位置
* fileManager:文件管理者
* compilationUnits:需要编译的文件
* 其他的参数:都是国际化参数,这里设为null
*/
CompilationTask t = jc.getTask(null, sjfm, null, null, null, units);
//进行编译
t.call();
try {
sjfm.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
运行一下
已生成对应的class文件,表示编译成功
接下来就是创建代理类
// load到内存中
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("month0408.$Proxy0");
//创建代理类
//获得代理类的构造器,参数就是我们的接口
Constructor ctr = c.getConstructor(infce);
return ctr.newInstance(new Car());
运行
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class); m.move();
结果
接下来需要继续完善的是业务逻辑,如何变成活的呢,此时我们需要一个事物处理器InvocationHandler来专门处理方法
public interface InvocationHandler {
public void inkove(Object o,Method m,Object[] arg2);
}
同时一样创建一个实现类
public class TimeHandler implements InvocationHandler {
private Object target;
public TimeHandler(Object target) {
super();
this.target = target;
}
@Override
public void inkove(Object o, Method m, Object[] arg2) {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
try {
m.invoke(target);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶...时间" + (stopTime - startTime) + "毫秒 ");
}
}
其实这个事物处理器的move就是我们要调用,因为它强化了被代理的对象,所以我们只要把以前写的代理类中的infce有关的内容全改成InvocationHandler就行
package month0408;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.apache.commons.io.FileUtils;
public class Proxy {
public static Object newProxyInstance(Class subject, Class infce, InvocationHandler ih)
throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException,
InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// windows下的换行符
String rt = "\r\n";
StringBuffer methodStr = new StringBuffer();
for (Method m : infce.getMethods()) {
methodStr.append("@Override" + rt +
// 方法名字
"public void " + m.getName() + "() {" + rt +
/*
* // 方法名字 "m." + m.getName() + "();" + rt +
*/
"try{" + rt + "Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt
+ "h.invoke(this,md,null);" + rt + "}catch(Exception e){e.printStackTrace();}" + rt + "}");
}
String str = "package month0408;" + rt +
// 引入自己写的InvocationHandler
"import month0408.InvocationHandler;" + rt + "import java.lang.reflect.Method;" + rt +
// 之前的接口Moveable改成通过参数的getName方法获取
"public class $Proxy0 implements " + infce.getSimpleName() + "{" + rt + "private InvocationHandler h;"
+ rt + "public $Proxy0(InvocationHandler h) {" + rt + "super();" + rt + "this.h = h;" + rt + "}" + rt
+ methodStr.toString() + "};";
// 以前的move用不上所以去掉
/*
* // 属性的类型通过参数的getName方法获取 "private " + infce.getSimpleName() + " m;" + rt + //
* 参数类型通过参数的getName方法获取 "public $Proxy0(" + infce.getSimpleName() + " m) {" +
* rt + "super();" + rt + "this.m = m;" + rt + "}" + rt + methodStr.toString() +
* "};";
*/
// 创建源代码的路径
String fileName = System.getProperty("user.dir") + "/src/main/java/month0408/$Proxy0.java";
File file = new File(fileName);
FileUtils.write(file, str);
// 获得当前系统的编译器
JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
/*
* diagnosticListener:监听器 locale, charset都是国际化的参数
*/
// 文件管理者
StandardJavaFileManager sjfm = jc.getStandardFileManager(null, null, null);
// 获取文件
Iterable units = sjfm.getJavaFileObjects(fileName);
// 编译任务
/**
* out:输出的位置 fileManager:文件管理者 compilationUnits:需要编译的文件 其他的参数:都是国际化参数,这里设为null
*/
CompilationTask t = jc.getTask(null, sjfm, null, null, null, units);
// 进行编译
t.call();
sjfm.close();
// load到内存中
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("month0408.$Proxy0");
// 创建代理类
// 获得代理类的构造器,参数就是我们的接口
Constructor ctr = c.getConstructor(InvocationHandler.class);
return ctr.newInstance(ih);
}
}
编译后我们看看生成的代理类
package month0408;
import month0408.InvocationHandler;
import java.lang.reflect.Method;
public class $Proxy0 implements Moveable {
private InvocationHandler h;
public $Proxy0(InvocationHandler h) {
super();
this.h = h;
}
@Override
public void move() {
try {
Method md = month0408.Moveable.class.getMethod("move");
h.invoke(this, md, null);
} catch (Exception e) {
e.printStackTrace();
}
}
};
当然博主到时没搞懂官方的第一个参数ClassLoader loader,用来干什么,谁知道麻烦给我留言一下。