1.概念
为其他对象提供一种代理,以控制对这个对象的访问。代理对象起到中介作用,可去掉功能或者增加额外功能。
2.角色
抽象主题角色(Subject):声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
具体主题角色(RealSubject):也称为委托角色或者被代理角色。定义了代理对象所代表的目标对象。
代理主题角色(Proxy):也叫委托类、代理类。代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。
3.代码实现
代理模式分为动态代理和静态代理两种模式。静态代理是程序员手动实现代理类,在编码之前,就已经产生了静态代理类的class文件。动态代理是在程序运行时,动态生成代理类的编码,无需程序员手动创建代理类。
3.1静态代理
静态代理是程序员手动书写静态代理类,首先,我们要定义产品接口,我们以明星和替身为例,如下代码:
/*** 产品类,明星演员,规范方法是表演*/
public interface Star {void proferm();
}
然后,我们定义产品实现,即具体明星演员,我们以杨幂为例,如下代码:
public class YMStar implements Star {@Overridepublic void proferm() {System.out.println("杨幂出演节目");}
}
现在,在杨幂出演的某个影片中,一些危险的动作,需要替身来做,而大部分情节,还需要杨幂本人出演。对于这种场景,我们就可以使用代理模式,替身就是杨幂的代理对象,代理杨幂去做危险的动作。代码如下:
/*** 静态代理,手动创建代理对象,代理真实对象执行接口方法*/
public class ProxyYMStar implements Star {private YMStar ym;public ProxyYMStar(YMStar star){this.ym=star;}/*** 代理方法,替身表演危险动作,杨幂表现普通情节*/@Overridepublic void proferm() {System.out.println("替身出演危险情节");ym.proferm();//杨幂出演普通情节}
}
这样,我们调用代理对象,就可以完成表演,代码如下:
public class Main {public static void main(String[] args) {Star star=new ProxyYMStar(new YMStar());star.proferm();}
}
运行结果如下图所示:
由此可见,静态代理是程序员提前写好的,来进行调用的,这里我们可以感觉到,编写一个和真实对象几乎一样的代理类来实现代理模式,很浪费效率,下面,我们来看动态代理的实现方式。
注意:
在上面的静态代理实现中,我们在ProxyYMStar 代理类中,定义的成员属性是YMStar类型,这也就固定了我们代理的范围,只能是YMStar,而不能是其他明星的代理。这就是静态代理的特点,只针对一个对象做代理。那有同学会想,成员属性定义成Star类型,那不是就可以对很多明星做代理了吗?是的,这么做可以为多个明星做代理,但是,这种实现方式,就不叫静态代理了,而是我们前面讲过的装饰者模式的实现方式了。这也是静态代理和装饰者模式的区别所在。
3.2JDK动态代理
JDK动态代理是基于接口的。这句话的意思是,JDK生成的代理对象,是生成的接口的代理对象。我们接收这个代理对象时,需要是一个接口类型。如果是一个Class类型的对象接收JDK代理对象,会报错。JDK代理模式的工作流程如下:
1.获取被代理对象上的所有接口列表
2.确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX;
3.根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码;
4.将对应的字节码转换为对应的class对象;
5.创建InvocationHandler实例handler,用来处理Proxy所有方法的调用;
6.Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象;
这里,我们主要讲解代理模式的实现方式,JDK代理模式的源码,我会在单独一篇文章中进行讲解。我们这里讲解一下JDK动态代理的API。
Jdk通过java.lang.reflect.Proxy包来支持动态代理,在Java中要创建一个代理对象,必须调用Proxy类的静态方法newProxyInstance,该方法的原型如下:
Object Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
这几个参数的意思分别是:
loader,表示类加载器,来加载下一个参数interfaces里的接口。
interfaces,表示接口或对象的数组,它就是前述代理对象和真实对象都必须共有的父类或者接口;
handler,表示调用处理器,它必须是实现了InvocationHandler接口的对象,其作用是定义代理对象中需要执行的具体操作。
我们在调用代理对象的接口方法时,代理对象都会执行handler对象的invoke方法,我们可以在invoke里面,执行方法的加强等操作。invoke方法API如下:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
其中:
proxy,表示执行这个方法的代理对象;
method,表示真实对象实际需要执行的方法(关于Method类参见Java的反射机制);
args,表示真实对象实际执行方法时所需的参数。
在实际的编程中,需要优先定义一个实现InvocationHandler接口的调用处理器对象,然后将它作为创建代理类实例的参数。(抑或在调用newProxyInstance方法时使用匿名内部类。)这样就得到了代理对象。
真实对象本身的实例化在调用处理器对象内部完成,实例化时需要的参数也应该及时传入调用处理器对象中。这样一来就完成了代理对象对真实对象的包装,而代理对象需要执行的额外操作也在invoke方法中处理。
其后,在客户端中,如果需要使用真实对象时,就可以用代理对象来替代它了(有时需要类型强制转化)。
下面,我们来实现代码:例子还是上面的明星演出,产品规范和产品实现无需改动,我们需要创建接口的代理对象,代码如下:
/*** 动态代理类,首先实现InvocationHandler接口,实现invoke方法,* 在JDK生成Proxy对象的方法里,传入的handler就是这个类本身,因为它实现了invoke方法。* 这样生成的代理类,执行接口方法时,就走handler里的invoke方法了。*/
public class ProxyStar implements InvocationHandler {private Object obj;public Object getProxyObj(Object obj){this.obj=obj;return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("替身演出");method.invoke(obj,args);//本身出演return null;}
}
我们在使用时,用这个代理类就可以,
public class Main {public static void main(String[] args) {Star star=(Star)new ProxyStar().getProxyObj(new YMStar());star.proferm();}
}
运行结果如下:
这就是JDK的动态代理实现。我们可以看到,这里的核心就是实现InvocationHandler接口,实现其invoke方法,在这个方法里,我们来做方法的增强。然后调用JDK里Proxy类的静态生成代理对象方法,获得代理对象。
在静态代理中,我们手动定义了杨幂的代理类。在JDK动态代理中,我们手动定义了代理类,这似乎看起来都需要定义代理类,那为什么还说动态代理更灵活呢?
因为在静态代理中,我们只是定义了杨幂的代理类,找到了杨幂的替身。那么其他明星需要替身,怎么办呢?我们需要手动定义其他明星的代理类。但是在动态代理中,我们定义的代理类,可以生成所有明星的代理对象,我们无需每个明星都实现一个代理类,这就是两者的区别。
3.3CGLIB动态代理
在JDK动态代理中,生成的代理对象,是面向接口生成的,我们只能为一个接口的实现类,生成代理对象。如果一个对象,没有实现任何接口,而我们要用其代理对象时,应该怎么办呢?这时,我们就应该采用第三方类库中的动态代理方式了。CGLIB是针对类来实现的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
CGLIB代理类的代码如下:
public class ProxyCglib implements MethodInterceptor
{private Object target;public Object getInstance(Object target){this.target = target;//Cglib中的加强器,用来创建动态代理Enhancer enhancer = new Enhancer();//设置要创建动态代理的类enhancer.setSuperclass(this.target.getClass());//设置回调,这里相当于是对于代理类上所有方法的调用,都会调用Callback,而Callback则需要实现intercept()方法进行拦截enhancer.setCallback(this);Object obj = enhancer.create();return obj;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable{System.out.print("I'm Proxy, I'm invoking...");Object object = proxy.invokeSuper(obj, args);System.out.println(object);return object;}
}
通过上面的代码,我们可以生成一个类的代理对象,然后执行代理对象的方法时,都会执行intercept方法。
4.代理模式的应用
在java领域,代理模式的实现方式,就是以上介绍的几种模式。遵循这些实现方式,我们可以在很多情况下使用代理模式,如:
1.远程代理:即为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实;在java中RMI就是远程代理模式。java的RMI模式我会在另一篇文章中进行详解,我们这里不做过多讲解。
2.虚拟代理:即根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象;例如:网页中的无图模式,就是代理对象产生的无图效果,当用户需要图时,才产生真正的图片。这里的实现方式,也是在Handler的invoke方法里进行实现。
3.安全代理:用来控制真实对象访问时的权限;这种应用的实现,也是遵循上面的代理方式实现方式,在invoke方法里,进行业务判断,如果有权限执行某方法,则调用真实对象的方法,如果没有权限执行方法,则不调用真实对象的方法,以此达到权限的控制。
4.智能指引:即当调用真实对象时,代理处理另外一些事。
5.方法增强的方式总结
方法增强的意思是增强某个对象的原始方法,使其在原有方法的基础上,有额外的功能。下面我们总结一下有哪些方式,可以增强对象的原始方法。
1.继承:继承是最简单的方法加强方式,我们想加强谁的方法,继承它,并在子类里覆盖要增强的方法即可。
2.装饰者模式:装饰者模式是面向接口增强方法的。装饰者实现接口,并将接口定义成成员变量,以此来接收要加强的对象,并通过实现接口的方法,调用成员变量,进行方法加强。不熟悉装饰者模式的,可以阅读《设计模式之装饰者模式(Decorator Pattern)》
3.代理模式:我们本篇讲的代理模式,也是用于加强方法的方式和手段