1.什么是代理模式
为其他对象提供一种代理以控制这个对象的访问,这就是代理模式(百度文库)。我的理解就是当程序不希望用户直接访问目的对象时,在用户对象和目的对象之间插入一个对象,这个对象作为目的对象的代理,代理通过调用目的对象的相应方法达到用户对象的要求。
2.代理模式的角色
①.抽象主题(Subject):抽象主题是一个接口,是对象和它的代理共有的接口。
②.实际主题(RealSubject):实际主题的实例是代理对象所要代理的对象。
③.代理(Proxy):代理含有主题接口声明的变量,存放实际主题的实例,这样就可以控制对实际主题的访问。
3.代理类的作用
代理类不但可以控制对实际主题的访问,而且还主要负责为实际主题预处理消息、过滤消息、把消息转发给实际主题,以及事后处理消息等。
看下面的例子
抽象主题:
public interface Geometry {
//得到三角形面积的方法
public double getArea();
}
实际主题:
public class Triangle implements Geometry {
//三角形的三边
private double sideA,sideB,sideC;
public Triangle(double a,double b,double c){
sideA = a;
sideB = b;
sideC = c;
}
@Override
public double getArea() {
//根据海伦公式求面积
double p = (sideA+sideB+sideC)/2.0;
double area = Math.sqrt(p*(p-sideA)*(p-sideB)*(p-sideC));
return area;
}
}
代理:
public class TriangleProxy implements Geometry {
//三角形的三边
private double sideA,sideB,sideC;
//声明实际三角形对象
private Triangle triangle;
//构造函数,传入三角形三边的值
public TriangleProxy(double a,double b,double c){
sideA = a;
sideB = b;
sideC = c;
}
@Override
public double getArea() {
//根据定理判定这三边能不能组成一个三角形
if(sideA+sideB>sideC&&sideA+sideC>sideB&&sideB+sideC>sideA){
System.out.println("能组成一个三角形");
//创建实际三角形对象
triangle = new Triangle(sideA,sideB,sideC);
double area = triangle.getArea();
return area;
}else{
System.out.println("输入数据不能组成一个三角形");
return -1;
}
}
}
测试类:
public class Application {
public static void main(String [] args){
//创建代理对象
TriangleProxy proxy = new TriangleProxy(20,22,26);
//调用代理类求面积的方法
double area = proxy.getArea();
System.out.println("面积是:"+area);
}
}
结果:
能组成一个三角形
面积是:213.7662274541982
从上面的例子可以看出,用户根本没有直接访问实际对象(Triangle),而是访问的代理对象(TriangleProxy),通过访问代理来达到访问实际对象的目的。当输入数据满足一个三角形的时候,才创建实际对象,如果不满足则不创建,这也达到了预处理消息,过滤消息的作用。
但是这也有个问题,如果有很多类实现Geometry接口,而且实现类都有代理类,当Geometry接口中再定义一个求三角形周长的方法时,那么所有的代理类都要修改。这可不是我们想看到的结果,这时就需要动态代理了。
4.按代理创建的时期可以分为静态代理和动态代理
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了(上例)。
动态代理:在程序运行时,运用反射机制动态创建而成。
5.动态代理的实现
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到一个集中的方法中处理(invoke()方法),这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
动态代理只能代理接口,主要依赖于java.lang.reflect包下面的InvocationHandler接口和Proxy类,其中InvocationHandler接口只提供了一个方法:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {},这里proxy指被代理的对象,即实际对象,method指要调用的方法,args指方法调用时所需要的参数。Proxy类主要方法是public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException{},这里loader是指类加载器(后面分析JVM的时候再分析),interfaces指被代理的接口,可以是一个或者多个,handler指InvocationHandler接口实现类的对象。
看下面的例子:
抽象主题:
public interface Geometry {
//得到三角形面积的方法
public double getArea(double a,double b,double c);
//得到三角形周长的方法
public double getPerimeter(double a,double b,double c);
}
实际主题:
public class Triangle implements Geometry {
@Override
public double getArea(double a, double b, double c) {
double p = (a+b+c)/2.0;
double area = Math.sqrt(p*(p-a)*(p-b)*(p-c));
return area;
}
@Override
public double getPerimeter(double a, double b, double c) {
return a+b+c;
}
}
代理:
public class TriangleProxy implements InvocationHandler {
//实际对象
private Object target;
//得到实际对象的代理对象
public Object getProxy(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//得到方法的参数,参数是Object类型,用的时候需要强制转型
for(int i = 0;i<args.length;i++){
System.out.println(args[i]);
}
return method.invoke(target, args);
}
}
测试类:
public class Test {
public static void main(String [] args){
Geometry geometry = new Triangle();
TriangleProxy tp = new TriangleProxy();
//得到代理对象
Geometry proxy = (Geometry) tp.getProxy(geometry);
//代理对象调用方法
double area = proxy.getArea(20, 22, 26);
System.out.println("面积为:"+area);
double perimeter = proxy.getPerimeter(2, 3, 4);
System.out.println("周长为:"+perimeter);
}
}
结果:
20.0
22.0
26.0
面积为:213.7662274541982
2.0
3.0
4.0
周长为:9.0
可以看出,不管Geometry接口里面再加多少方法,TriangleProxy类中都不用修改,只要在测试类中调用相应的方法。而且参数已经得到args[],可以根据这个进行预处理和过滤。这里需要指出,我们没有显示的调用TriangleProxy类中的invoke()方法,那写着有什么用呢?其实则不然,其实虽然我们在测试类中写的proxy.getArea(),但是实际上调用的是TriangleProxy类中的invoke()方法,然后在invoke()方法中调用了getArea()方法,看invoke()方法中的一句代码method.invoke(target, args),前面也反射介绍了,target对象调用了方法method,参数是args。
那我们在invoke()方法中加上这么一句,也许你能看明白:
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("正在执行的方法:"+method);//加这句
//得到方法的参数,参数是Object类型,用的时候需要强制转型
for(int i = 0;i<args.length;i++){
System.out.println(args[i]);
}
return method.invoke(target, args);
}
结果:
正在执行的方法:public abstract double test.Geometry.getArea(double,double,double)
20.0
22.0
26.0
面积为:213.7662274541982
正在执行的方法:public abstract double test.Geometry.getPerimeter(double,double,double)
2.0
3.0
4.0
周长为:9.0
是不是豁然开朗。
当然其实得到代理对象的方法也可以写在测试类中,那么在TriangleProxy类中去掉得到代理对象的方法,加上一个带参构造函数:
public TriangleProxy(Object target){
this.target = target;
}
在测试类中得到代理对象:
Geometry geometry = new Triangle();
//得到代理对象
Geometry proxy = (Geometry) Proxy.newProxyInstance(geometry.getClass().getClassLoader(),
geometry.getClass().getInterfaces(), new TriangleProxy(geometry));
再用代理对象调用方法。