设计模式之 代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。
代理模式的分类
虚拟代理
远程代理
保护代理
智能引用代理
智能引用代理
静态代理:代理对象和被代理对象在代理之前都是确定的。他们都实现相同的接口或者继承相同的抽象类。
有两种实现方式。
1. 通过继承实现。
2. 通过聚合实现。
情景案例:
我们有一个车类,车具有行驶的方法。通过代理,增加记录行驶时间的方法。
1.定义接口 Moveable.java/*
* 模拟行驶的接口
*/
public interface Moveable {
void move();
}
2.创建一个车类 Car.java 实现这个接口。
public class Car implements Moveable {
public void move() {
//实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.现在我们为车类增加一个记录行驶时间的处理,创建一个代理类
用继承的方式实现。public class Car2 extends Car {
public void move() {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
super.move();//调用父类的方法,这就实现了Car2 对Car的代理
long endTime = System.currentTimeMillis();
System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
}
}
用聚合的方式实现
/*
* 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象
*/
public class Car3 implements Moveable {
public Car3(Car car){
super();
this.car = car;
}
private Car car;
public void move() {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
car.move();//使用聚合的方式,把参数传进来进行调用
long endTime = System.currentTimeMillis();
System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
}
}
继承和聚合实现静态代理,那个更合适?
如果我们要在记录行驶时间前增加记录日志功能,则需要创建Car4.java 继承Car2 或者继承Car,如果在增加其他的功能,则代理类就会无限的膨胀,越来越多。所以使用聚合的方式更适合用来实现静态代理。
使用聚合的方式实现静态代理
1.创建日志记录代理类/*
* 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象
* 汽车日志的代理类
*/
public class CarLogProxy implements Moveable {
public CarLogProxy(Moveable moveable){
super();
this.moveable = moveable;
}
private Moveable moveable;
public void move() {
System.out.println("日志开始");
moveable.move();//使用聚合的方式,把参数传进来进行调用
System.out.println("日志结束");
}
}
2.创建行驶时间代理类
/*
* 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象
*/
public class CarTimeProxy implements Moveable {
public CarTimeProxy(Moveable moveable){
super();
this.moveable = moveable;
}
private Moveable moveable;
public void move() {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
moveable.move();//使用聚合的方式,把参数传进来进行调用
long endTime = System.currentTimeMillis();
System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
}
}
3.测试类,先记录日志,再记录行驶的时间。
/*
* 测试类
*/
public class CTest {
public static void main(String[] args) {
Car car = new Car();
CarTimeProxy ctp = new CarTimeProxy(car);
CarLogProxy clp = new CarLogProxy(ctp);
clp.move();
}
}
4.输出结果
我们可以自由组合这种方式,如先记录时间再记录日志。
public class CTest {
public static void main(String[] args) {
Car car = new Car();
CarLogProxy clp = new CarLogProxy(car);
CarTimeProxy ctp = new CarTimeProxy(clp);
ctp.move();
}
}
思考:如果还要为火车、自行车实现代理类,那么还要再按照上面的方式再分别实现两个类。有没有办法可以让一个类代理火车、自行车、汽车呢?
动态产生代理,实现对不同类,不同方法的代理。
所谓Dynamic Proxy 是这样一种class:
它是在运行时生成的class
该class需要实现一组interface
使用动态代理类时,必须实现InvocationHandler接口
JDK动态代理
Java 动态代理类位于java.lang.reflect包下,一般主要涉及以下两个类:
① Interface InvocationHandler:该接口中仅定义了一个方法
public object invoke(Objectobj,Method method,Object[] args)
在实际使用时,第一个参数obj表示最终生成的代理类对象,method是被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
② Proxy: 该类即为动态代理类
static ObjectnewProxyInstance(ClassLoader loader,Calss[] interface, InvocationHandler h): 返回代理类的一个实例,返回后的代理类可以当做被代理类使用(可使用被代理类在接口中声明过的方法)
JDK动态代理实现步骤
1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
2. 创建被代理的类以及接口。
3. 调用Proxy的静态方法,创建一个代理类
newProxyInstance(ClassLoaderloader,Class[] interfaces,InvocationHandler h)
4. 通过代理调用方法。
1创建TimeHandler.java 实现InvocationHandler 接口
public class TimeHandler implements InvocationHandler {
public TimeHandler(Object target){
super();
this.target = target;
}
private Object target;
/*
* 参数:
* proxy 代理类的对象
* method 被代理对象的方法
* args 方法的参数
*
* 返回值:
* Object 方法的返回值
*/
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;
}
}
模拟JDK动态代理实现思路
动态代理实现思路
实现功能:通过Proxy 的 newProxyInstance返回代理对象。
1. 声明一段源码(动态产生代理)
2. 编译源码(JDK Compiler API),产生新的类(代理类)
3. 将这个类load到内存当中,产生一个新的对象(代理对象)
4. return 代理对象。
初步实现:首先我们自己创建一个Proxy类,并创建newProxyInstance方法
public class Proxy {
/*
* 创建一个方法用来返回我们的代理对象
*/
public static Object newProxyInstance(Class infce) throws Exception{
String rt = "\r\n";//windows下的换行符
/*
* 第一步:声明一段源码
* 替换interface的名字
*/
//获取我们的方法
String methodStr ="";
for(Method m : infce.getMethods()){
methodStr +="public void "+m.getName()+"() {"+rt+
"long startTime = System.currentTimeMillis();"+rt+
"System.out.println(\"汽车开始行驶\");"+rt+
"moveable."+m.getName()+"();//使用聚合的方式,把参数传进来进行调用"+rt+
"long endTime = System.currentTimeMillis();"+rt+
"System.out.println(\"汽车结束行驶,行驶时间:\"+(endTime - startTime)+\"毫秒\");"+rt+
"}";
}
String str=
"package com.meng.proxy;"+rt+
"public class $Proxy0 implements "+infce.getName()+"{"+rt+
"public $Proxy0("+infce.getName()+" moveable){"+rt+
"super();"+rt+
"this.moveable = moveable;"+rt+
"}"+rt+
"private "+infce.getName()+" moveable;"+rt+
methodStr+rt+
"}";
/*
* 第二步:生成一个文件
*/
String filename = System.getProperty("user.dir")+"/bin/com/meng/proxy/$Proxy0.java";
File file = new File(filename);
//调用commons.io.jar 的方法写文件
FileUtils.write(file, str);
/*
* 第三步:编译生成的java类
*/
/*
* 这里需要把jre 改为jdk
*/
//得到当前系统的编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//文件管理者
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
//根据文件名得到文件的数据
Iterable units = fileManager.getJavaFileObjects(filename);
//编译任务
CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
//进行编译
t.call();
fileManager.close();
/*
* 第四步:把编译好的文件load到内存
*/
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("com.meng.proxy.$Proxy0");
Constructor ctr = c.getConstructor(infce);
//根据构造方法把Car传进去
return ctr.newInstance(new Car());
}
}
分欣上面这段代码,我们传递了一个接口拼接了一段代理类的源码$Proxy0
把这段源码写入$Proxy0.java 文件并编译这个文件,并把编译好的文件加载到内存,获取构造方法并创建代理类的对象,返回这个代理类对象。
bin目录下生成的文件
拼接源码文件的内容:
package com.meng.proxy;
public class $Proxy0 implements com.meng.proxy.Moveable{
public $Proxy0(com.meng.proxy.Moveable moveable){
super();
this.moveable = moveable;
}
private com.meng.proxy.Moveable moveable;
public void move() {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
moveable.move();//使用聚合的方式,把参数传进来进行调用
long endTime = System.currentTimeMillis();
System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
}
}
测试类
public class CTest {
public static void main(String[] args) throws Exception {
Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class);
m.move();
}
}
运行结果
分析上面的实现方法,我们发现增加业务逻辑的实现写死在了代码里,而且构造函数那传入的是new Car() 这样就不具有通用性,下面对这种方法进行扩展改进,进行解耦
改进实现:
创建InvocationHandler 接口public interface InvocationHandler {
//第一个参数表示代理类对象,第二个参数表示方法对象,省略方法参数
public void invoke(Object o,Method m);
}
创建TimeHandler 实现这个接口,在这里增加我们业务处理逻辑
public class TimeHandler implements InvocationHandler {
//被代理的对象
private Object target;
public TimeHandler(Object object){
super();
this.target = object;
}
@Override
public void invoke(Object o, Method m) {
try {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶");
m.invoke(target);
long endTime = System.currentTimeMillis();
System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");
} catch (Exception e) {
e.printStackTrace();
}
}
}
改写我们的 Proxy.java
public class Proxy {
/*
* 创建一个方法用来返回我们的代理对象
*/
public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception{
String rt = "\r\n";//windows下的换行符
/*
* 第一步:声明一段源码
* 替换interface的名字
*/
//获取我们的方法
String methodStr ="";
for(Method m : infce.getMethods()){
methodStr +="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+
"}"+rt;
}
String str=
"package com.meng.proxy;"+rt+
"import com.meng.proxy.InvocationHandler;"+rt+
"import java.lang.reflect.Method;"+rt+
"public class $Proxy0 implements "+infce.getName()+"{"+rt+
"public $Proxy0(InvocationHandler h){"+rt+
"super();"+rt+
"this.h = h;"+rt+
"}"+rt+
"private InvocationHandler h;"+rt+
methodStr+rt+
"}";
/*
* 第二步:生成一个文件
*/
String filename = System.getProperty("user.dir")+"/bin/com/meng/proxy/$Proxy0.java";
File file = new File(filename);
//调用commons.io.jar 的方法写文件
FileUtils.write(file, str);
/*
* 第三步:编译生成的java类
*/
/*
* 这里需要把jre 改为jdk
*/
//得到当前系统的编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//文件管理者
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
//根据文件名得到文件的数据
Iterable units = fileManager.getJavaFileObjects(filename);
//编译任务
CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
//进行编译
t.call();
fileManager.close();
/*
* 第四步:把编译好的文件load到内存
*/
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("com.meng.proxy.$Proxy0");
Constructor ctr = c.getConstructor(InvocationHandler.class);
return ctr.newInstance(h);
}
}
重新生成的代理类 $Proxy0.java 如下:
public class $Proxy0 implements com.meng.proxy.Moveable{
public $Proxy0(InvocationHandler h){
super();
this.h = h;
}
private InvocationHandler h;
public void move() {
try{
Method md = com.meng.proxy.Moveable.class.getMethod("move");
h.invoke(this,md);
}catch(Exception e){e.printStackTrace();}
}
}
测试类:
public class CTest {
public static void main(String[] args) throws Exception {
Car car = new Car();
InvocationHandler h = new TimeHandler(car);
Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class,h);
m.move();
}
}
运行结果:
这样我们的动态代理就具有了扩展性
理解的还不是很清楚,如有疑问请移步
慕课网地址:http://www.imooc.com/learn/214