我们平常买火车票的时候是不是就只有三种方式:火车站售票厅,火车票代售处,网上购票,但是在很多年前的话就是前两种方式。其实火车票代售处就是火车站售票厅的代理,并且火车票代售处可以提供额外的服务,比如电话预约,并且我们知道火车票代售处是不负责票的退换的,所以这就要说到代理模式的几种模式了(请看概念)。
概念
代理模式:为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。
常见代理模式 | 定义 |
---|---|
远程代理 | 为不同地理的对象提供局域网代表对象 |
虚拟代理 | 根据需要将资源消耗很大的对象进行延迟,当真正需要的时候才进行创建 |
保护代理 | 权限控制 |
智能引用代理 | 根据需要可去掉功能服务或增加额外的服务 |
接下来我们就以智能引用代理来进行实现,智能引用代理模式主要分为静态代理和动态代理,接下来我们就分别用这两种方式实现功能。
静态代理代码实现
静态代理:代理和被代理对象在代理之前是确定的。他们都实现相同接口或者继承相同的抽象类。
静态代理有两种实现方法,一种是继承的方法,另一种是组合的方法,但是不管怎么样首先定义一个接口作为基类。
Moveable.java
package com.xjh.proxy;
public interface Moveable {
void move();
}
之后定义一个类实现Moveable接口,这个类作为被代理的类。
Car.java
package com.xjh.proxy;
import java.util.Random;
public class Car implements Moveable {
@Override
public void move() {
// 实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中...");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
之后就是实现代理类,这里就有两种方法了:一种是继承,另一种是组合。
CarTimeProxy1.java(继承)
package com.xjh.proxy;
public class CarTimeProxy1 extends Car {
//实现了Car2对Car的代理,继承方法
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
super.move();
long endtime = System.currentTimeMillis();
System.out.println("汽车结束行驶... 继承方法实现汽车行驶时间:" + ( endtime - starttime) + "ms");
}
}
CarTimeProxy2.java(组合)
package com.xjh.proxy;
import java.util.Random;
public class CarTimeProxy2 implements Moveable {
//使用组合的方法实现代理
private Moveable m;
public CarLogProxy(Moveable m) {
super();
this.m = m;
}
@Override
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
m.move();
long endtime = System.currentTimeMillis();
System.out.println("汽车结束行驶... 组合实现方法汽车行驶时间:" + ( endtime - starttime) + "ms");
}
}
接下来我们就测试一下。
Client.java
package com.xjh.proxy;
public class Client {
public static void main(String[] args) {
CarTimeProxy1 car1 = new CarTimeProxy1();
car1.move();
Moveable car2 = new CarTimeProxy2(new Car());
car2.move();
}
}
当然我们要对比一下这两种方法实现静态代理有什么区别,那个更加适合代理模式,其实我们可以想象一种情况,假如我们需要添加功能两种方法都该这么实现呢?
- 继承:我们就要无限的新建一个类,继承于之前的类,这样的话如果功能增加过多,并且有很多重复组合,代理类会无限的膨胀下去,并且重复的代码会很多,无法达到代码复用的效果。(不推荐使用)
- 组合:我们只需要实现Moveable接口,然后不断地嵌套就可以。
因为继承的方式实现很简单,就是无限的重复CarTimeProxy1的编写方式,我们就不来编写相关代码了。下面我们就来看看组合是怎么处理这种方式的。
假如我们要加上打印日志的功能,我们只需要再定义一个类实现Moveable接口。
CarLogProxy.java
package com.xjh.proxy;
import java.util.Random;
public class CarLogProxy implements Moveable {
//使用组合的方法实现代理
private Moveable m;
public CarLogProxy(Moveable m) {
super();
this.m = m;
}
@Override
public void move() {
long starttime = System.currentTimeMillis();
System.out.println("日志开始...");
m.move();
long endtime = System.currentTimeMillis();
System.out.println("日志结束...");
}
}
之后我们在main方法中new对象的时候传入对象。
Client.java
package com.xjh.proxy;
public class Client {
public static void main(String[] args) {
Car car = new Car();
CarTimeProxy2 ctp = new CarTimeProxy2(car);
CarLogProxy clp = new CarLogProxy(ctp);
clp.move();
}
}
这样我们就实现了功能的拓展,如果我们想要先移动再记录日志就没有必要重新编写一个类了,直接修改就好了。
Client.java
package com.xjh.proxy;
public class Client {
public static void main(String[] args) {
Car car = new Car();
CarLogProxy clp = new CarLogProxy(car);
CarTimeProxy2 ctp = new CarTimeProxy2(clp);
ctp.move();
}
}
这样我们就解决了日志和时间交换顺序时不需要再新建类,但是每种功能在不同的类,比如汽车和火车,我们还是要新建代理类,假如我们要加入很多的功能,这样我们的代理类还是很多,有什么办法解决嘛?答案是动态代理。
动态代理实现代码
动态代理模式类图:
我们可以通过下面的类图发现,动态代理的实现就是在代理类和被代理类之间加入了一个InvocationHandler,而InvocationHandler接口需要我们实现invoke方法,他有三个参数,我们来分别理解一下三个参数以及返回对象对应的内容:
名称 | 内容 |
---|---|
proxy | 生成的代理对象 |
method | 被代理对象的方法 |
args | 方法的参数 |
Object | 方法的返回值 |
接下来我们就来实现这个类,我们只需要在里面调用method.invoke(target)方法,之后在前后实现这个功能的相关代码。
TimeHandler.java
package com.xjh.jdkproxy;
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;
}
@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) + "ms");
return null;
}
}
之后我们编写main函数,我们主要就是调用Proxy.newProxyInstance(loader, interfaces, h)方法自动生成代理类,里面参数的含义如下:
名称 | 含义 |
---|---|
loader | 类加载器 |
interfaces | 实现接口 |
h | InvocationHandler |
所以我们相应的就要new出这些参数,然后传入Proxy.newProxyInstance方法中,生成一个代理类,然后调用Moveable的move方法完成该功能。
Client.java
package com.xjh.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import com.xjh.jdkproxy.TimeHandler;
public class Client {
public static void main(String[] args) {
Car car = new Car();
InvocationHandler h = new TimeHandler(car);
Class<?> cls = car.getClass();
Moveable m = (Moveable)Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);
m.move();
}
}
执行结果如下:
这样我们就不需要编写多个不同类的代理类,只需要在TimeHandler构造函数中传入相应的类就可以了,系统的JDK会自动生成对应的Class。
所谓的Dynamic Proxy是这样一种Class:它是在运行时生成的Class,该Class需要实现一组interface,并且使用动态代理类时,必须要实现InvocationHandler接口。
当然我们需要搞懂动态代理是如何实现的,主要的就是通过Proxy的newProxyInstance方法返回代理对象,但是具体步骤是什么呢?
- 声明一段源码(动态产生代理)
- 编译源码(JDK Compiler API),产生新的代理类
- 将这个类加载到内存中,产生一个新的代理对象
- 返回代理对象
接下来我们就根据这个步骤来模仿JDK中动态代理的实现:
首先新建一个Proxy类,里面新建一个newProxyInstance的静态方法,然后根据我们之前的步骤来完成这个动态代理。
Proxy.java
package com.xjh.proxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
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;
public class Proxy {
public static Object newProxyInstance(Class infce) throws Exception{
//声明一段源码(动态产生代理)
String methodStr = "";
for(Method m : infce.getMethods()) {
methodStr += " @Override\r\n" +
" public void " + m.getName() + "() {\r\n" +
" long starttime = System.currentTimeMillis();\r\n" +
" System.out.println(\"汽车开始行驶...\");\r\n" +
" m." + m.getName() + "();\r\n" +
" long endtime = System.currentTimeMillis();\r\n" +
" System.out.println(\"汽车结束行驶... 组合实现方法汽车行驶时间:\" + ( endtime - starttime) + \"ms\");\r\n" +
" }";
}
String str = "package com.xjh.proxy;\r\n" +
"import java.util.Random;\r\n" +
"public class $Proxy0 implements " + infce.getName() + " {\r\n" +
" //使用组合的方法实现代理\r\n" +
" private " + infce.getName() + " m;\r\n" +
" public $Proxy0(" + infce.getName() + " m) {\r\n" +
" super();\r\n" +
" this.m=m;\r\n" +
" }\r\n" +
methodStr + "\r\n" +
"}";
//产生代理类的Java文件
String filename = System.getProperty("user.dir")+"/bin/com/xjh/proxy/$Proxy0.java";
File file = new File(filename);
FileWriter writer;
try {
writer = new FileWriter(filename);
writer.write(str);
writer.flush();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//编译源码(JDK Compiler API),产生新的代理类
//拿到编译器
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();
//将这个类加载到内存中,产生一个新的代理对象
ClassLoader cl = ClassLoader.getSystemClassLoader();//得到类加载器
Class c = cl.loadClass("com.xjh.proxy.$Proxy0");
Constructor ctr = c.getConstructor(infce);
//返回代理对象
return ctr.newInstance(new Car());
}
}
其实在这里面我们遇到了一个问题:找不到jdk的lib目录下tools.jar文件,没法编译。因为它编译文件时,会找到JAVA_HOME的jre\lib\tools.jar,但是tools.jar并不在jre/lib中。所以我们需要在Project Explorer中点击右键,然后依次选择Properties -> Java Build Path -> Libraries,选中JRE System Library,然后点击Edit -> Installed JREs,选中你的jre,然后点击Edit -> Add External JARs,在本地jdk/lib目录下选择tools.jar添加,最后Finish -> Apply -> OK即可。
Client.java
package com.xjh.proxy;
public class Client {
public static void main(String[] args) {
try {
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class);
m.move();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
之后我们在Window–>Show View–>Other–>Navigator–>Open,打开Navigator视图我们就能看到我们自动生成的代理类。
Main函数的执行结果:
虽然我们首先了动态加载代理类,但是我们的源代码都是固定好的,业务逻辑是写死的,那怎么才能真正的实现动态呢?
当然就是模仿着JDK中的样式,同样定义一个接口,里面定义invoke方法。
InvocationHandler.java
package com.xjh.proxy;
import java.lang.reflect.Method;
public interface InvocationHandler {
public void invoke(Object o,Method m);
}
然后我们就和在JDK中一样,new一个类实现这个接口。
TimeHandler.java
package com.xjh.proxy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
Object target;
public TimeHandler(Object target) {
super();
this.target = target;
}
@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) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
}
之后我们就要在Proxy.java中进行修改他的传入参数,以及一些代码的变动。我们就要根据传入的InvocationHandler参数来动态的添加方法。
Proxy.java
package com.xjh.proxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
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;
public class Proxy {
public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception{
//声明一段源码(动态产生代理)
String methodStr = "";
for(Method m:infce.getMethods()) {
methodStr += " @Override\r\n" +
"public void " + m.getName() + "(){\r\n" +
" try{\r\n" +
" Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");\r\n" +
" h.invoke(this, md);\r\n" +
" } catch (Exception e) {\r\n" +
" e.printStackTrace();\r\n" +
" }" +
"}";
}
String str = "package com.xjh.proxy;\r\n" +
"import java.lang.reflect.Method;\r\n" +
"import java.util.Random;\r\n" +
"import com.xjh.proxy.InvocationHandler;\r\n" +
"public class $Proxy0 implements " + infce.getName() + " {\r\n" +
" //使用组合的方法实现代理\r\n" +
" private InvocationHandler h;\r\n" +
" public $Proxy0(InvocationHandler h) {\r\n" +
" this.h = h;\r\n" +
" }\r\n" +
methodStr + "\r\n" +
"}";
//产生代理类的Java文件
String filename = System.getProperty("user.dir")+"/bin/com/xjh/proxy/$Proxy0.java";
File file = new File(filename);
FileWriter writer;
try {
writer = new FileWriter(filename);
writer.write(str);
writer.flush();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//编译源码(JDK Compiler API),产生新的代理类
//拿到编译器
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();
//将这个类加载到内存中,产生一个新的代理对象
ClassLoader cl = ClassLoader.getSystemClassLoader();//得到类加载器
Class c = cl.loadClass("com.xjh.proxy.$Proxy0");
Constructor ctr = c.getConstructor(InvocationHandler.class);
//返回代理对象
return ctr.newInstance(h);
}
}
然后我们在Main函数中new出Handler,传入对象就可以获得动态产生的代理类了。
Client.java
package com.xjh.proxy;
public class Client {
public static void main(String[] args) {
try {
Car car = new Car();
InvocationHandler h = new TimeHandler(car);
Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class, h);
m.move();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
总结
我们平常使用代理模式的主要愿意是,我们经常会使用某些类库的某些方法,但是对于源码我们是不能进行修改的,我们就可以使用代理模式来在方法前后增加一些业务逻辑,已达到我们想要的效果。这就是AOP(面向切面编程)。