1.代理模式简介(Proxy)
1.1代理模式定义
为其他对象提供一种代理以控制对这个对象的访问,代理对象起到中介作用,可去掉功能或者增加额外的服务。
1.2代理模式的分类
远程代理:
为不同的地理对象提供局域网代表对象
虚拟代理:
根据需要将资源消耗很大的对象进行延迟加载,真正需要的时候再进行创建。(如访问网页时图片的加载,可先用别的图片进行代替)
保护代理:
控制对一个对象的访问权限。(如论坛游客不能发帖等)
智能引用代理:
提供对目标对象额外的服务。(如Spring AOP模式)
2.智能引用代理案例
只能引用代理是在实际实践中使用醉倒的一种代理。
智能引用代理可以分为静态代理和动态代理
2.1 静态代理
代理和被代理对象在代理开始之前就是确定的。他们都实现相同的接口或者继承相同的抽象类。
下面我们通过一个具体的案例来看一下。
假设有一辆车,我们要让他行驶,停下来,并记录它的行驶时间。可以用以下模式
2.1.1普通的实现形式
首先新建一个Movable接口
public interface Movable {
void move();
}
建立一个Car类继承Movable接口并实现move()方法
import java.util.Random;
public class Car implements Movable {
@Override
public void move() {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();// System.currentTimeMillis();获取系统当前时间
System.out.println("汽车开始行驶..........");
// 实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中、、、、、、、");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
}
}
在测试方法中进行测试
public class Test {
public static void main(String[] args) {
Car car = new Car();
car.move();
}
}
测试结果为
汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:127毫秒
下面是一个静态代理的两种方式:
2.1.2继承方式实现代理
修改Car中的move()方法,使其只实现行驶功能,开始实行和行驶完成功能在子类中进行实现
import java.util.Random;
public class Car implements Movable {
@Override
public void move() {
// 实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中、、、、、、、");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
新建一个子类Car2继承Car并重写move()方法
/**
* 继承的方式代理Car
*
* @author Administrator
*
*/
public class Car2 extends Car {
@Override
public void move() {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");
super.move();
// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
}
}
在测试方法中测试
public class Test {
public static void main(String[] args) {
Car car2 = new Car2();
car2.move();
}
}
这种方式为继承方式实现代理。
2.1.3 聚合的方式实现代理
新建一个car3继承movable接口,并实现move()方法。
在类中添加一个Car类型的全局变量,并添加带有Car的构造方法。
在move()方法中实现Car中的move方法。
/**
* 聚合的方式实现代理
*
* @author Administrator
*
*/
public class Car3 implements Movable {
private Car car;
public Car3(Car car) {
super();
this.car = car;
}
@Override
public void move() {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");
car.move();
// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
}
}
在测试类中测试
public class Test {
public static void main(String[] args) {
Car car = new Car();
Movable car3 = new Car3(car);
car3.move();
}
}
这种就是通过聚合的方式实现代理。
2.1.4 继承方式和聚合方式的对比
如果希望实现类功能的叠加。先记录日志,再记录时间。
需要新建一个Car4继承Car,先实现日志的处理再计算运行时间。
如果希望先记录时间,再记录日志的处理,就需要新建一个Car5,先对时间处理,再对日志处理。
如果想实现先对权限的判断,再做日志的处理,再做时间的处理,只能新建一个Car6。
如果通过继承的方式对类进行代理,当功能增加或者改变时,只能通过增加类的方式进行。类只会无限膨胀下去。因此一般不推荐继承的方式代理。
以下为一个聚合代理方式,先记录日志再记录时间
新建一个日志代理类,实现movab接口
/**
* 对时间的代理
* @author Administrator
*
*/
public class CarTimeProxy implements Movable {
private Movable movable;//这次不是Car属性,而是他们共同的接口
public CarTimeProxy(Movable movable) {
super();
this.movable = movable;
}
@Override
public void move() {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");
movable.move();
// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
}
}
再编写一个日志的代理类,同样实现movable接口
/**
* 对日志的代理
* @author Administrator
*
*/
public class CarLogProxy implements Movable {
private Movable movable;
public CarLogProxy(Movable movable) {
super();
this.movable = movable;
}
@Override
public void move() {
System.out.println("日志开始..........");
movable.move();
System.out.println("日志结束");
}
}
在测试类中,我们实现先对日志记录,再对时间记录
public class Test2 {
public static void main(String[] args) {
Car car = new Car();
CarTimeProxy carTimeProxy = new CarTimeProxy(car);
CarLogProxy carLogProxy = new CarLogProxy(carTimeProxy);
carLogProxy.move();
}
}
得到的结果如下
日志开始..........
汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:4毫秒
日志结束
如果想先记录汽车行驶的时间,就先创建CarLogProxy类,再创建CarTimeProxy类,并在CarTimeProxy类中创建CarLogProxy属性。
public class Test2 {
public static void main(String[] args) {
Car car = new Car();
CarLogProxy carLogProxy = new CarLogProxy(car);
CarTimeProxy carTimeProxy = new CarTimeProxy(carLogProxy);
carTimeProxy.move();
}
}
此时控制台打印输出的结果为:
汽车开始行驶..........
日志开始..........
汽车行驶中、、、、、、、
日志结束
汽车停止行驶,行驶时间为:919毫秒
2.2 动态代理
此时实现的代理类只是对car的代理,如果再来一辆货车,还要实现TrainLogProxy 和TrainTimeProxy 两个类. 假如有100个类要做事件代理。就需要新建一百个代理类。
因此可以使用动态代理类,实现对不同类不同方法的代理。
动态代理的主要机制就是在类和代理类之间加入一个ProxyHandler(事务处理器)这样一个类
Java动态代理类位于java.lang.reflect包下,一般涉及到以下两个类。
(1)Interface InvocationHandler:该接口仅定义了一个方法:
public Object invoke(Object obj,Method method, Object args[])
在实际使用中,第一个参数obj一般指代理类,method是指被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
(2) Proxy:该类极为动态代理类
static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
返回代理类的一个实例,返回后的代理类可以被当做代理类使用(可使用被代理类的在接口中声明过的方法)
我们可以通过一个实例来看一下
2.2.1 动态代理实例
首先新建一个TimeHandler(事件代理器)类,实现InvocationHandler接口。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
//构造器
private Object target;
public TimeHandler(Object target) {
super();
this.target = target;
}
/**
* proxy:被代理的对象
* method:被代理对象的方法
* args 方法的参数
*
* 返回值
* Object方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");
method.invoke(target);
// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
return null;//这里没有返回值
}
}
然后就可以在测试类里面测试
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
Car car = new Car();
InvocationHandler h = new TimeHandler(car);
Class<?> cls = car.getClass();
/**
* loader 类加载器
* interfaces 实现接口
* h InvocationHandler
*/
Movable m = (Movable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);
m.move();
}
}
这样就实现了对Car类的动态代理。
2.2.2 动态代理介绍
所谓Dynamic Proxy是这样一种class:
它是在运行时生成的class
该class需要实现一组interface
使用动态代理类时,必须实现InvocationHandler接口。
2.2.3 动态代理实现的步骤
1、创建一个实现接口InvocationHandler的类,必须实现invoke方法
2、创建被代理的类以及接口
3、调用Proxy的静态方法,创建一个代理类。
Proxy.newProxyInstance(loader, interfaces, h)
4、调用代理类的方法。
2.2.4 CGLib动态代理
JDK动态代理:
1、只能代理实现了接口的类
2、没有实现接口的类不能实现JDK的动态代理
CGLIB动态代理:
1、针对类来实现代理的
2、对制定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。
下面是CGLIB创建动态代理的一个实例:
首先在项目里面引入CGlib的jar包。jar包下载地:
http://download.csdn.net/download/beidiqiuren/9533454
我们先建立一个火车类,不实现任何接口,并添加一个move方法
public class Train {
public void move(){
System.out.println("火车正在行驶");
}
}
其次,添加一个动态代理类。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
//设置创建子类的类(被代理的类)
enhancer.setSuperclass(clazz);
//回调
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 拦截所有目标类方法的调用
* obj 目标类的实例
* method 目标方法的反射对象
* args 方法的参数
* proxy 代理类的实例
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("日志开始。。。。");
//代理类调用父类的方法
proxy.invokeSuper(obj, args);
System.out.println("日志结束。。。。");
return null;
}
}
这里之所以调用父类的代理方法是因为:intercept()方法拦截所有目标类方法的调用,如果调用proxy.invoke(obj, args);
会报java.lang.StackOverflowError异常,原因是将代理类作为目标类,这样会无限循环调用intercept方法,
导致无限死循环;二代理类的父类只有一个,调用父类的方法只会调用一次。
我们在测试类中测试一下
public class Test {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Train t = (Train) proxy.getProxy(Train.class);
t.move();
}
}
测试结果为
日志开始。。。。
火车正在行驶
日志结束。。。。
可见代理已经成功。
3.模拟动态代理实现思路
动态代理实现思路
实现功能:通过Proxy的newProxyInstance返回代理对象
1、声明一段源码(动态产生代理)
2、编译源码(JDK Compiler API),产生新的类(代理类)
3、将这个类load到内存当中,产生一个新的对象(代理对象)
4、return 代理对象
下面我们来实际编写一下
3.1 声明一段源码
在此,我们使用一个外部的jar包简化上传下载的代码
jar包地址:http://download.csdn.net/download/beidiqiuren/9533455
package proxy;
import java.io.File;
import org.apache.commons.io.FileUtils;
public class Proxy {
public static Object newProxyInstance() throws Exception {
String rt = "\r\n";
String str = "package proxy;" + rt +
"public class CarTimeProxy implements Movable {" + rt +
" private Movable movable;" + rt +
"public CarTimeProxy(Movable movable) {" + rt + " super();" + rt + " this.movable = movable;"
+ rt + " }" + rt +
" @Override" + rt + " public void move() {" + rt
+ " long starttime = System.currentTimeMillis();" + rt
+ " System.out.println(\"汽车开始行驶..........\");" + rt +
" movable.move();" + rt +
" long endtime = System.currentTimeMillis();" + rt +
" System.out.println(\"汽车停止行驶,行驶时间为:\" + (endtime - starttime) + \"毫秒\");" + rt + " }" + rt +
"}";
// 编译:先生成一个java文件,对这个文件进行编译
// System.getProperty("user.dir")当前路径
String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy.java";
File file = new File(filename);
//FileUtils这个类可以快速的对文件进行删除和读写
FileUtils.writeStringToFile(file, str);
return null;
}
}
为了方便起见,代理类我们就使用之前编写好的CarTimeProxy
在测试类中运行
package proxy;
public class Test3 {
public static void main(String[] args) throws Exception {
Proxy.newProxyInstance();
}
}
运行之后,生成了一个新的java文件
接下来,我们进一步优化这个代理类
将代理类实现的接口实现动态调整,并通过动态获得应重写的方法。
3.2 优化一段源码
package proxy;
import java.io.File;
import java.lang.reflect.Method;
import org.apache.commons.io.FileUtils;
public class Proxy {
public static Object newProxyInstance(Class Infce) throws Exception {
String rt = "\r\n";
String methodStr = "";
for(Method m:Infce.getMethods()){
methodStr +=" @Override" + rt + " public void "+m.getName()+"() {" + rt
+ " long starttime = System.currentTimeMillis();" + rt
+ " System.out.println(\"汽车开始行驶..........\");" + rt +
" movable."+m.getName()+"();" + rt +
" long endtime = System.currentTimeMillis();" + rt +
" System.out.println(\"汽车停止行驶,行驶时间为:\" + (endtime - starttime) + \"毫秒\");" +
rt + " }" ;
}
String str = "package proxy;" + rt +
"public class $Proxy0 implements "+Infce.getName()+" {" + rt +
" private "+Infce.getName()+" movable;" + rt +
"public $Proxy0("+Infce.getName()+" movable) {" + rt + " super();" + rt + " this.movable = movable;"
+ rt + " }" + rt +
methodStr+rt +
"}";
// 编译:先生成一个java文件,对这个文件进行编译
// System.getProperty("user.dir")当前路径
String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy0.java";
File file = new File(filename);
//FileUtils这个类可以快速的对文件进行删除和读写
FileUtils.writeStringToFile(file, str);
return null;
}
}
在测试类中运行,生成的$Proxy0.java文件并无不妥的地方,我们开始对这个文件进行编译。
3.3 编译源码
在Proxy类的newProxyInstance()中。我们对生成的文件进行编译
//得到当前系统的编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//创建文件的管理者
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
//根据文件名得到管理文件的数组
Iterable units = fileMgr.getJavaFileObjects(filename);
//编译任务
CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);
//进行编译
task.call();
fileMgr.close();
在测试类中运行
编译后,在bin目录下生成了一个$Proxy0.class文件。接下来就是把这个文件载入到内存当中。
3.4 将编译后的文件载入到内存当中
//load到内存当中
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("proxy.$Proxy0");
//根据构造器初始化创建代理类
//构造器传递的参数就是接口的名字
Constructor ctr = c.getConstructor(Infce);
//根据构造器创建对象
return ctr.newInstance(new Car());
在测试类中测试
package proxy;
public class Test3 {
public static void main(String[] args) throws Exception {
Movable movable = (Movable) Proxy.newProxyInstance(Movable.class);
movable.move();
}
}
此时控制台打印输出的结果为
汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:126毫秒
代理成功。
3.5 动态指定业务逻辑
在上面的案例中,业务逻辑需要代理的方法是写死在proxy类中的,而且需要传入Car这个类,如何动态的制定业务逻辑?
下面我们模拟一下真实的JDK Proxy
首先,新建一个InvocationHandler接口
package proxy;
import java.lang.reflect.Method;
public interface InvocationHandler {
public void invoke(Object o,Method m);
}
定义一个TimeHandler类实现InvocationHandler接口。
package proxy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
private Object target;
public TimeHandler(Object target) {
super();
this.target = target;
}
@Override
public void invoke(Object o, Method m) {
// TODO Auto-generated method stub
try {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");
m.invoke(target);
long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
然后我们修改Proxy类,使其更加独立。
package proxy;
import java.io.File;
import java.lang.reflect.Constructor;
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 Infce,InvocationHandler h) throws Exception {
String rt = "\r\n";
String methodStr = "";
for(Method m:Infce.getMethods()){
methodStr +=" @Override" + rt +
" public void "+m.getName()+"() {" + rt+
"try{"+
" Method md = "+Infce.getName()+".class.getMethod(\""+
m.getName()+"\");"+rt +
"h.invoke(this,md);"+rt +
"}catch(Exception e){e.printStackTrace();}"+
" }" ;
}
String str = "package proxy;" + rt +"import proxy.InvocationHandler;"+rt+
"import java.lang.reflect.Method;"+rt+
"public class $Proxy0 implements "+Infce.getName()+" {" + rt +
"private InvocationHandler h;"+rt+
"public $Proxy0(InvocationHandler h) {" + rt
+ " super();" + rt
+ " this.h = h;"
+ rt + " }" + rt +
methodStr+rt +
"}";
// 编译:先生成一个java文件,对这个文件进行编译
// System.getProperty("user.dir")当前路径
String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy0.java";
File file = new File(filename);
//FileUtils这个类可以快速的对文件进行删除和读写
FileUtils.writeStringToFile(file, str);
//得到当前系统的编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//创建文件的管理者
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
//根据文件名得到管理文件的数组
Iterable units = fileMgr.getJavaFileObjects(filename);
//编译任务
CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);
//进行编译
task.call();
fileMgr.close();
//load到内存当中
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("proxy.$Proxy0");
//根据构造器初始化创建代理类
//构造器传递的参数就是接口的名字
Constructor ctr = c.getConstructor(InvocationHandler.class);
//根据构造器创建对象
return ctr.newInstance(h);
}
}
最后在测试类中测试:
package proxy;
public class Test3 {
public static void main(String[] args) throws Exception {
Car car = new Car();
InvocationHandler h = new TimeHandler(car);
Movable movable = (Movable) Proxy.newProxyInstance(Movable.class,h);
movable.move();
}
}
成功。