什么是代理模式?
我们知道,java中一共有23种设计模式,其中代理模式是设计模式中的一个皇帝(king),代理模式是一个结构式模式,可以这么说,掌握了代理模式,工作的时候不会受欺负的,很厉害的,但是要想掌握代理模式,也不是那么容易的事情,代码不敲个一二十遍,那根本没望了,除非你很聪明哈。
那么到底什么是代理模式?通俗一点,就一句话,代理模式就是找个人帮你做事情,找个工具人。
举个例子:相信大家都看过西游记吧,里面有一个典型的情节,跟代理模式就很类似。八戒要娶高小姐为妻,悟空代替高小姐成亲。那悟空也就能代替高小姐与八戒kiss了。所以呢,代理模式就是这么个理,自己不想做的事情,找个人帮你做事情,这个人就拥有你做事情的能力。
代理模式包含哪些角色?
代理模式有一些特别官方的名词哈,这些东西虽然说不是很重要,但也需要了解一下。
- 抽象主题(Subject):通过接口或抽象类声明业务方法
- 真实主题(Real Subject):实现了抽象主题中的具体业务,是实施代理的目标对象,即代理对象所代表的真实对象,是最终要引用的对象。
- 代理 (Proxy):提供了与真实主题相同的接口,其内部含有对真实主题的引用,可以访问,控制或者扩展真实主题的功能。
还是用上面的例子,上面的例子用来说代理模式感觉最合适不过了。由此可知:与八戒kiss就是抽象主题了,高小姐本人就是真实主题了,用来实现抽象主题的行为(方法),悟空就是代理了,代替高小姐与八戒成亲,kiss。
代理模式又分为两种,一种是静态代理,另一种是动态代理。咱们一个一个的慢慢说。
静态代理
什么是静态代理?
由开发者针对抽象主题编写相关的代理类实现,编译之后生成代理类的class文件。静态代理在编译时就已经实现,代理关系在编译期就已经绑定了,编译完成之后代理类是一个实际的class文件。这就是静态代理。 还用上面那个例子说明哈。先画一下结构图,一会再再用代码说明。 先写一个接口,也就是带有kiss()这个方法的,学术用词成为:抽象主题。然后再写一个RealMissGao类,真实的高小姐,具有kiss八戒这个行为,也就是kiss()方法,学术用词称为:真实主题;最后再写一个ProxyMissGao代理类,也就是悟空,代替高小姐成亲,以及kiss()八戒这个行为,所以悟空也有一个kiss()的方法,悟空代替高小姐去成亲,那悟空也得有一个MissGao的属性,用来调用kiss这个方法,还得有一个构造方法。 图中红色部分是在编译期间就已经完成的,也就是在编译期间就能够确定代理类的属性、构造方法、成员方法等等,这种模式就是静态代理。 <font size="3"> 悟空与八戒成亲这个过程前期需要悟空变成高小姐,后期为了斩妖除魔,这才是悟空替高小姐与八戒成亲一个事情的完成过程。 所以,<font size="3" color="red">代理模式的目的:需要代理对象环绕在真实主题前后做一些事情。就比如悟空,成亲前:悟空需要编程高小姐;成亲后:悟空斩妖除魔。这样才是代理完完整整代替真实主题做完事情,也是代理对象的存在价值。</font></font>
不写代码的程序媛是不合格的程序媛。
package entity;
/** * 真实主题(高小姐)和代理主题(悟空)共同实现的接口 */
public interface MissGao {
/** * 跟八戒kiss */
public void kiss();
}
package entity;
/** * 真实的高小姐 */
public class RealMissGao implements MissGao{
@Override
public void kiss() {
System.out.println("高小姐跟八戒成亲,kiss。。。");
}
}
package entity;
/** * 代理主题,代替高小姐跟八戒成亲,kiss。。。 */
public class ProxyMissGao implements MissGao {
/** * 代理主题拥有一个真实主题,代替真实主题做事情, 两者是has a的关系 */
private MissGao missGao;
public ProxyMissGao(MissGao missGao) {
this.missGao=missGao;
}
@Override
/** * 代理主题环绕在真实主题前后 */
public void kiss() {
//前置环绕
System.out.println("悟空变成高小姐");
//代替主题做事情
this.missGao.kiss();
//后置环绕
System.out.println("悟空降妖除魔");
}
}
package test;
import entity.MissGao;
import entity.ProxyMissGao;
import entity.RealMissGao;
/** * 测试类 */
public class Test {
public static void main(String[] args) {
/** * 1,创建真实主题 * 2,创建代替主题 * 3,将真实主题注入代替主题中 * 4,调用代理主题的kiss行为 */
RealMissGao realGao=new RealMissGao();
ProxyMissGao proxyGao=new ProxyMissGao(realGao);
proxyGao.kiss();
}
}
运行结果是:
悟空变成高小姐
高小姐跟八戒成亲,kiss。。。
悟空降妖除魔
静态代理小结:
1,代理主题和真实主题之间的关系:has a 的关系。 2,代理主题需环绕在真实主题周围,前置环绕和后置环绕。也就是把事情前因后果做完整。 3,代理主题和真实主题必须实现用一个接口
静态代理优点:
可以找人帮我做事情。
静态代理缺点:
不够灵活。如果沙和尚、牛魔王等等都要和高小姐成亲,就需要手动创建多个代理类,比较麻烦,不太现实。
那该如何弥补静态代理的缺点呢?就用下面的动态代理来弥补。
动态代理
什么是动态代理?
动态代理从JDK1.3版本就已经引入的特性,它利用反射机制在运行时生成代理类的字节码,为Java平台带来了运行时动态扩展对象行为的能力。JDK动态代理的核心API是jaca.lang.reflect包下的InvocationHandler接口和Proxy类。 咱还用上面的例子,继续画结构图: 由上述可知,动态代理模式比较麻烦、复杂,结合了反射、JDK来实现。我们先来补充一下JDK提供的接口、类的方法。 <font size="3" color="red"> InvocationHandler接口:是代理方法的调用处理程序,负责为代理方法提供业务逻辑。包含的方法: Object invoke(Object proxy,Method method,Object[] args):用于在代理实例上处理方法调用并返回结果。参数proxy是正在执行方法的代理对象,参数method是正在被调用的接口方法,参数args是传递给接口方法的参数。 </font>
Proxy类:负责动态创建代理类及实例。主要方法: static Object newProxyInatance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h):返回一个实现了指定接口的代理类实例,对接口方法的调用会被指派到指定的调用处理程序。参数loader用来加载动态生成的代理类,参数interfaces是需要进行代理的接口类型,参数h是接口代理方法的调用处理程序。 <font size="3"> 由上述图可以看到,通过JVM动态生成的代理类与JDK提供的类、接口还没有建立连接,那他们之间有什么关系? 咱们先来写代码,一会再回过头来解决这个小问题。 </font>
package pojo;
public interface MissGao {
public void kiss();
}
package pojo;
public class RealMissGao implements MissGao{
@Override
public void kiss() {
System.out.println("高小姐和八戒kiss。。。。");
}
package pojo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/** * 我自己定义的“调用处理者(图中蓝色部分)”,用来产生代理类的对象,调用代理类的invoke方法(kiss()) * @param <T> */
public class ProInvocationHandle<T> implements InvocationHandler {
/** *调用处理者持有的真正高小姐 */
private T realMissGao;
/** *构造方法 */
public ProInvocationHandle(T realMissGao) {
this.realMissGao=realMissGao;
}
/** * 创建代理对象,并且把代理对象返回 */
public T createProxyInstance(){
//真实主题(真实对象)的类加载器
ClassLoader classLoader = realMissGao.getClass().getClassLoader();
//真实主题(真实对象)实现的接口
Class<?>[] interfaces = realMissGao.getClass().getInterfaces();
/** * 参数一:告诉JVM代理对象有哪个类加载器加载 * 参数二:告诉JVM代理对象实现了哪些接口 * 参数三:告诉JVM代理对象由哪个调用处理者来调用, * 此时的this表示当前“调用处理者”对象来调用代理对象的成员方法 */
return (T)Proxy.newProxyInstance(classLoader,interfaces,this);//有三个参数,ctrl+t查看。1,代理对象是由哪个类加载器加载的
// 2,代理对象实现了哪些接口3,代理对象是由哪个调用处理者来调用的
}
/** * 具体来调用代理对象的成员方法 * @param proxy 当前代理对象,此时是一个哑巴 * @param method 代理对象成员方法的方法签名 * @param args 代理对象成员方法的参数,此时的kiss()方法无参数,所以 args为null * @return 调用结果,此时的kiss()方法无参数,所以return null * @throws Throwable */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/** * 代理主题环绕在真实主题的周围 */
//前置环绕
System.out.println("悟空变成高小姐");
//使用反射调用代理对象的方法
//参数一:方法所属哪个对象,此时是真实对象,也就是realMissGao
//参数二:方法的参数列表,此时为null
Object result=method.invoke(realMissGao,args);
//后置环绕
System.out.println("悟空降妖除魔");
return result;
}
}
package test;
import pojo.MissGao;
import pojo.ProInvocationHandle;
import pojo.RealMissGao;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
/** * 1,创建真实对象 * 2,创建代理对象 * 3,调用代理主题对象的方法 */
RealMissGao realMissGao = new RealMissGao();
ProInvocationHandle<MissGao> handle = new ProInvocationHandle<MissGao>(realMissGao);
MissGao proMissGao=handle.createProxyInstance();
proMissGao.kiss();
}
}
运行之后的结果是:
悟空变成高小姐
高小姐和八戒kiss。。。。
悟空降妖除魔
现在我们还是不知道通过JVM创建的代理对象与JDK提供的Proxy之间是什么关系,代理类都有哪些属性、构造方法、成员方法等等,我们都还不知道。没关系,我们通过反射一个一个解决。 第一个问题:proMissGao是什么类型的对象?
//解决问题一:
Class<? extends MissGao> aClass = proMissGao.getClass();
System.out.println("动态获取类的Class是:"+aClass);
结果:
动态获取类的Class是:class com.sun.proxy.$Proxy0
所以,proMissGao是class com.sun.proxy.$Proxy0的对象。 注意:凡是以$开头的类,在java中都是JVM虚拟机动态帮你产生的。
第二个问题:代理类proMissGao是JVM帮我创建的,它跟JDK提供的类Proxy是什么关系?
//解决问题二:
System.out.println(proMissGao instanceof Proxy);
//通过反射拿到proMissGao继承了哪个父类
Class<?> superclass = proMissGao.getClass().getSuperclass();
System.out.println("proMissGao的父类是:"+superclass);//由此可知,红色部分继承了左边蓝色部分
//动态代理类实现了哪些接口
for (Class<?> anInterface : proMissGao.getClass().getInterfaces()) {
System.out.println(anInterface);
}
运行结果:
true
proMissGao的父类是:class java.lang.reflect.Proxy
动态代理类实现的接口:interface pojo.MissGao
运行结果是true,可以知道:动态代理对象proMissGao是Proxy的一个实例。
第三个问题:Proxy与Invocation之间的关系?
//解决问题三:
//通过反射拿到Proxy的所有属性
for (Field declaredField : superclass.getDeclaredFields()) {
System.out.println("Proxy类的属性:"+declaredField);
}
运行结果:
Proxy类的属性:protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h
由此可以知道,Proxy类有一个属性Invocation。两者是has a的关系。
第四个问题:动态代理对象有哪些属性?构造方法?成员方法?成员方法是如何调用的?
//解决问题四:
System.out.println("---------代理类的属性---------");
for (Field field : aClass.getDeclaredFields()) {
System.out.println("动态代理类的属性:"+field);
}
System.out.println("----------代理类的构造方法----------");
for (Constructor<?> constructor : aClass.getDeclaredConstructors()) {
System.out.println("动态代理类的构造方法:"+constructor);
}
System.out.println("----------代理类的成员方法----------");
for (Method method : aClass.getDeclaredMethods()) {
System.out.println("动态代理类的成员方法:"+method);
}
---------代理类的属性---------
动态代理类的属性:private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m3
动态代理类的属性:private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m1
动态代理类的属性:private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m0
动态代理类的属性:private static java.lang.reflect.Method com.sun.proxy.$Proxy0.m2
----------代理类的构造方法----------
动态代理类的构造方法:public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
----------代理类的成员方法----------
动态代理类的成员方法:public final boolean com.sun.proxy.$Proxy0.equals(java.lang.Object)
动态代理类的成员方法:public final java.lang.String com.sun.proxy.$Proxy0.toString()
动态代理类的成员方法:public final int com.sun.proxy.$Proxy0.hashCode()
动态代理类的成员方法:public final void com.sun.proxy.$Proxy0.kiss()
由上述运行结果可以看出代理类的属性、构造方法、成员方法,所以可以写出代理类的结构。如下:
public class $Proxy0 extends Proxy implements MissGao{
private static Method com.sun.proxy.$Proxy0.m3;
private static Method com.sun.proxy.$Proxy0.m1;
private static jMethod com.sun.proxy.$Proxy0.m0;
private static Method com.sun.proxy.$Proxy0.m2;
//构造方法
//形参h是InvocationHandler,实参h是ProInvocationHandle
public com.sun.proxy.$Proxy0(InvocationHandler h){
//子类构造方法通过“super(h)”将当前“调用处理者”传给父类Proxy
super(h);
}
//成员方法(暂时只看kiss()方法)
public final void kiss(){
super.h.invoke(this,m1,null);
}
}
由此,我们就可以继续完成上面那个结构图了。 通过反射,这样就一点点的推出了代理类ProMissGao的结构,这就是动态代理的一个小示例。这就如定义上说的,它利用反射机制在运行时生成代理类的字节码。这样就比较方便,不管有多少代理类,都不用手动敲代码了。
动态代理小结:
1,JDK动态代理是面向接口代理的。 2,JDK动态代理是运行的时候通过JVM动态生成代理类。
注意:JDK动态代理是面向接口代理的,Java中还有一种动态代理,CGLIB动态代理,这种事通过继承实现的。等到小编先去掌握了CGLIB,下次我们再分享CGLIB动态代理。