秒懂Java代理模式 静态代理、动态代理

本文阅读 15 分钟
首页 代码,Java 正文

什么是代理模式?

        我们知道,java中一共有23种设计模式,其中代理模式是设计模式中的一个皇帝(king),代理模式是一个结构式模式,可以这么说,掌握了代理模式,工作的时候不会受欺负的,很厉害的,但是要想掌握代理模式,也不是那么容易的事情,代码不敲个一二十遍,那根本没望了,除非你很聪明哈。

        那么到底什么是代理模式?通俗一点,就一句话,代理模式就是找个人帮你做事情,找个工具人。

        举个例子:相信大家都看过西游记吧,里面有一个典型的情节,跟代理模式就很类似。八戒要娶高小姐为妻,悟空代替高小姐成亲。那悟空也就能代替高小姐与八戒kiss了。所以呢,代理模式就是这么个理,自己不想做的事情,找个人帮你做事情,这个人就拥有你做事情的能力。

代理模式包含哪些角色?

        代理模式有一些特别官方的名词哈,这些东西虽然说不是很重要,但也需要了解一下。

  1. 抽象主题(Subject):通过接口或抽象类声明业务方法
  2. 真实主题(Real Subject):实现了抽象主题中的具体业务,是实施代理的目标对象,即代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理 (Proxy):提供了与真实主题相同的接口,其内部含有对真实主题的引用,可以访问,控制或者扩展真实主题的功能。

        还是用上面的例子,上面的例子用来说代理模式感觉最合适不过了。由此可知:与八戒kiss就是抽象主题了,高小姐本人就是真实主题了,用来实现抽象主题的行为(方法),悟空就是代理了,代替高小姐与八戒成亲,kiss。

        代理模式又分为两种,一种是静态代理,另一种是动态代理。咱们一个一个的慢慢说。

静态代理

什么是静态代理?

        由开发者针对抽象主题编写相关的代理类实现,编译之后生成代理类的class文件。静态代理在编译时就已经实现,代理关系在编译期就已经绑定了,编译完成之后代理类是一个实际的class文件。这就是静态代理。         还用上面那个例子说明哈。先画一下结构图,一会再再用代码说明。 img         先写一个接口,也就是带有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类。         咱还用上面的例子,继续画结构图: img         由上述可知,动态代理模式比较麻烦、复杂,结合了反射、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);
    }
}

        由此,我们就可以继续完成上面那个结构图了。 img         通过反射,这样就一点点的推出了代理类ProMissGao的结构,这就是动态代理的一个小示例。这就如定义上说的,它利用反射机制在运行时生成代理类的字节码。这样就比较方便,不管有多少代理类,都不用手动敲代码了。

        动态代理小结:

        1,JDK动态代理是面向接口代理的。         2,JDK动态代理是运行的时候通过JVM动态生成代理类。

        注意:JDK动态代理是面向接口代理的,Java中还有一种动态代理,CGLIB动态代理,这种事通过继承实现的。等到小编先去掌握了CGLIB,下次我们再分享CGLIB动态代理。

本文为互联网自动采集或经作者授权后发布,本文观点不代表立场,若侵权下架请联系我们删帖处理!文章出自:https://blog.csdn.net/qq_46540738/article/details/112873168
-- 展开阅读全文 --
安全面试之XSS(跨站脚本攻击)
« 上一篇 07-24

发表评论

成为第一个评论的人

热门文章

标签TAG

最近回复