设计模式之代理模式【小白学这一篇就够了】

       一生猿,一世猿。
                    —— 猿医生·yys 

                                                

 

目录

一、前言

二、简介

三、代理模式 - 静态代理

 四、代理模式 - JDK动态代理                       

 五、代理模式 - CGLib动态代理                       

 

一、前言

       上篇 设计模式之原型模式【选用鸣人影分身阐述】得到 CSDN官方大大以及各位猿友的推崇,小主很是倍感荣幸。

       今天呢 ,利用【单身狗遇见媒婆后的美好爱情】此案例来阐述下 设计模式之代理模式。

       在此,记录一下,分享给大家。

扫描二维码关注公众号,回复: 11585385 查看本文章

二、简介

       姓名:代理模式

       英文名:Proxy Pattern

       个人介绍:代理模式(proxy pattern)是指为其他对象提供一种代理,以控制对这个对象的访问。

                         原型模式属于结构型的设计模式。

                         两个目的:一保护目标对象,二增强目标对象。

                         讲述分为 静态代理、动态代理 (JDK动态代理、CGLib动态代理 )

       特长爱好:1.事务代理;

                         2.非侵入式日志监听;

                         3.异常集中处理;

       相信大家读到这里... 针对于一个合格的开发者来说,或多或少脑海中还是存在一定印象的。

                                                                                                            

三、代理模式 - 静态代理

       “我和你,本应该”,

       “各自好,各自坏”,

       “各自生活的自在”,

       “毫无关联的存在”,

       ... ...  

       “直到你,出现在”,

       “我眼中,躲不开”,

       ... ...  

       “为何出现在彼此的生活又离开”,

       “只留下在心里深深浅浅的表白”,

       ... ...         

                           

       听着博哥的曲子,脑海中浮现出 她的身影,又哭又笑,又爱又恨,那是逝去的青春,心底默许的美好 ~

       顺便给大家安利一波博哥(梁博),唱歌的时候是舞台上最有魅力的蓝孩子,马上出三专了,

       今天的演唱会,必须去... 感受下万人齐唱的赶脚 ~ 哈哈  ~ 有跟我共同想法的小伙伴,联系我哦 ~ 一齐出发 ~ 

  

       岁月的洪流,卷走了青春,卷走了年华,剩下的只是一个被岁月刻下深深印痕的伤痕累累的躯壳,和一颗沧桑的心。

       回忆的美好,现实的苦感,突然发现告别校园,步入社会,职场的恐惧刚刚克服,却逃不过年龄枷锁。

       ... ...                                                                                     

       引入主题《相亲》,人到了一定的年龄,父母总是迫不及待希望子女成婚早日抱孙子,这也是父母一生最重要的任务。

     随着儿女年龄日益增长,父母变得越来越着急,于是出现了相亲的字眼,父母之间相互介绍,找人,找婚介....

                                                                                                      

       接下来 小主将采用 【单身狗遇见媒婆后的美好爱情】的案例来阐述代理模式之 静态代理方式: 

       首先,定义人类接口  Person 类:

/**
 * 人类行为:
 *      谈恋爱/买房/买车...
 * @author yys
 */
public interface Person {

    void findLove();

}

       接下来,定义目标对象  BoySingleDog <男单身狗> 类:

/**
 * 静态代理
 *      男单身狗 <目标对象>
 * @author yys
 */
public class BoySingleDog implements Person {

    public void findLove() {
        System.out.println("男单身狗:要求不高,白富美!");
    }

}

        接下来,定义代理对象父类 BaseMeiPo 类 :

/**
 * 静态代理
 *      代理父类
 * @author yys
 */
public abstract class BaseMeiPo {

    // 前置方法
    public void before() {
        System.out.println("\n媒婆:请告诉我您的择偶标准!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 后置方法
    public void after() {
        try {
            Thread.sleep(800);
            System.out.print("\n~");
            Thread.sleep(800);
            System.out.print(" ~");
            Thread.sleep(800);
            System.out.println(" ~");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("网络姻缘一线牵,敬请等待你的爱...");
    }

}

        接下来,定义代理对象 MeiPoProxy <媒婆> 类:

/**
 * 静态代理
 *      媒婆 <代理对象>
 * @author yys
 */
public class MeiPoProxy extends BaseMeiPo implements Person {

    private Person target;

    public MeiPoProxy(Person target) {
        this.target = target;
    }

    public void findLove() {
        before();
        target.findLove();
        after();
    }

}

      代理模式 - 静态代理测试类及测试结果: 

/**
 * 静态代理
 *      Test
 * @author yys
 */
public class ProxyTest {

    public static void main(String[] args) {

        // 创建目标对象 - 男单身狗
         Person target = new BoySingleDog();
        // 创建代理对象 - 媒婆
        MeiPoProxy proxy = new MeiPoProxy(target);

        // 择偶 - start
        proxy.findLove();

    }

}

                        

      接下来,看下类图:

                                

      代理模式 - 静态代理 总结:

        优点: 

            1、对目标对象功能增强,再调用目标对象方法前后可以实现逻辑增强;

            2、降低耦合,易扩展;

        缺点: 

            1、开发者需自己实现代理对象;

            2、目标对象新增方法,代理类需要同步新增,违背开闭原则;

      从测试结果以及类图可以清楚看出:

            静态代理中,代理对象需持有目标对象的引用,再调用引用对象(目标对象)的方法前 / 后进行逻辑的增强。

            在实际开发应用场景中(数据库分库分表),可以利用静态代理方式,再调用 service方法前 实现动态切换数据源。

      从开发者的宏观角度来看,能尽量少些,那肯定不写。显然,静态代理需要自己手写代理对象,而且后期维护不便。

      这时候,动态代理方式 就该闪亮登场了...   

       

 四、代理模式 - JDK动态代理                       

       接下来 阐述代理模式之 JDK动态代理方式:                                                                                                           

       首先,定义目标对象  GirlSingleDog <女单身狗> 类:

/**
 * 动态代理(JDK实现方式)
 *      女单身狗 <目标对象>
 * @author yys
 */
public class GirlSingleDog implements Person {

    public void findLove() {
        System.out.println("女单身狗:有房有车,还要长得帅!");
    }

}

       接下来,定义代理对象  JDKMeiPoProxy <JDK媒婆> 类:

/**
 * 动态代理(JDK实现方式)
 *      媒婆 <代理对象>
 * @author yys
 */
public class JDKMeiPoProxy extends BaseMeiPo implements InvocationHandler {

    private Object target;

    public Object getInstance(Object target) {
        this.target = target;
        Class<?> clazz = target.getClass();

        // 参数1:目标对象使用的类加载器
        // 参数2:目标对象实现的接口/继承的抽象类
        // 参数3:事件处理,执行目标对象的方法时,会触发事件处理器(InvocationHandler)的方法,会把当前执行目标对象的方法作为参数传入
        //       (动态处理器,执行目标对象的方法时,会触发事件处理器的方法)
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object obj = method.invoke(target, args);
        after();
        return obj;
    }

    /**
     * 匿名内部类实现
     * @return
     */
    /*public Object getInstance(final Object target) {
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                before();
                Object obj = method.invoke(target, args);
                after();
                return obj;
            }
        });
    }*/

}

       代理模式 - JDK动态代理 测试类及测试结果: 

/**
 * JDK动态代理
 *      Test
 * @author yys
 */
public class ProxyTest {

    /**
     * (JDK方式)结论:
     *      1、目标对象需实现接口
     */
    public static void main(String[] args) throws Exception {

        // 1.创建目标对象
        // Person target = new GirlSingleDog();
        Person target = new BoySingleDog();

        // 2.创建代理对象
        Person proxy = (Person) new JDKMeiPoProxy().getInstance(target);

        // 3.方法执行
        proxy.findLove();

        // 4.通过反编译工具查看生成代理类源代码
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
        FileOutputStream fos = new FileOutputStream("E:\\$Proxy0.class");
        fos.write(bytes);
        fos.close();

    }

}

                          

      接下来,看下类图:

                                

      代理模式 - JDK动态代理 总结:

        优点: 

            1、对目标对象功能增强,且不需手动实现代理对象;

            2、降低耦合,易扩展;

            3、JDK动态代理是直接通过 Class字节码技术生成代理类,生成代理类效率高;

        缺点: 

            1、目标对象必须有实现的接口,底层它是实现了目标对象的接口;

            2、JDK动态代理是通过反射机制调用,执行效率低;

        实现方式: 

            1、创建目标对象并实现接口;

            2、创建代理对象并实现 InvocationHandler接口,重写 invoke方法 (在方法体中进行增强);

      从测试结果以及类图可以清楚看出:

            JDK动态代理中,目标对象需有实现接口,代理对象实现 InvocationHandler接口并重写 invoke方法,再调用引目标对象的方法前 / 后进行逻辑的增强。

     

      在测试类中,第四步,运行结束后,我们发现在E盘会找到 $Proxy0.class文件,如下图所示:

              

      然后再使用 jad反编译后得到 java文件,代码如下 (此处省略部分代码,小伙伴可自行生成查看完整代码)。

public final class $Proxy0 extends Proxy
    implements Person
{

    public $Proxy0(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

    // ...(重写equals方法)省略
    // ...(重写toString方法)省略
    // ...(重写hashCode方法)省略

    public final void findLove()
    {
        try
        {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m3 = Class.forName("com.yys.demo.design.patterns.proxy.Person").getMethod("findLove", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

       从上述代码中,我们发现 $Proxy0 继承了 Proxy 类,同时还实现了Person 接口,而且重写了 findLove()等方法。

       在静态块中用反射查找到了目标对象的所有方法并保存了所有方法的引用,在重写的方法用反射调用目标对象的方法。

       这些代码就是JDK动态代理帮我们自动生成的,无需我们手动创建对应的代理类。

 五、代理模式 - CGLib动态代理                       

       接下来 ,阐述代理模式之 CGLib动态代理方式: 

       首先,定义目标对象  GirlSingleDog <女单身狗> 类:

/**
 * 动态代理(CGlib实现方式)
 *      女单身狗 <目标对象>
 * @author yys
 */
public class GirlSingleDog { // 这里无需实现接口即可

    public void findLove() {
        System.out.println("女单身狗:有房有车,还要长得帅!");
    }

}

       接下来,定义代理对象  CGlibMeiPoProxy <CGLib媒婆> 类:

/**
 * 动态代理(CGlib实现方式)
 *      媒婆 <代理对象>
 * @author yys
 */
public class CGlibMeiPoProxy extends BaseMeiPo implements MethodInterceptor {

    public Object getInstance(Class<?> clazz) {
        // 相当于Proxy,代理的工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类(目标对象)
        enhancer.setSuperclass(clazz);
        // 设置回调函数,设置方法拦截器
        enhancer.setCallback(this);
        // 创建子类(代理对象)
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o, objects);
        after();
        return obj;
    }

    /**
     * 匿名内部类实现方式
     * @return
     */
    /*public Object getInstance(Class<?> clazz) {
        // 相当于Proxy,代理的工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类(目标对象)
        enhancer.setSuperclass(clazz);
        // 设置回调函数,设置方法拦截器
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                before();
                Object obj = methodProxy.invokeSuper(o, objects);
                after();
                return obj;
            }
        });
        // 创建子类(代理对象)
        return enhancer.create();
    }*/

}

       代理模式 - CGLib动态代理 测试类及测试结果: 

/**
 * CGLib动态代理
 *      Test
 * @author yys
 */
public class ProxyTest {

    /**
     * (CGlib方式)结论:
     *      1、目标对象不实现接口
     *      2、static final 修饰的方法 -> proxy fail
     */
    public static void main(String[] args) {

        // 1.利用 cglib 的代理类可以将内存中的class文件写入本地磁盘
        String path = "D:\\java\\yys\\yys-demo\\yys-design-pattern\\yys-pattern-proxy\\src\\main\\java";
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);

        // 创建目标对象
        // BoySingleDog target = new BoySingleDog();
        GirlSingleDog target = new GirlSingleDog();

        // 创建代理对象
        // BoySingleDog proxy = (BoySingleDog) new CGlibMeiPoProxy().getInstance(target.getClass());
        GirlSingleDog proxy = (GirlSingleDog) new CGlibMeiPoProxy().getInstance(target.getClass());

        // 方法执行
        proxy.findLove();

    }

}

                          

      接下来,看下类图:

                         

      代理模式 - CGLib动态代理 总结:

        优点: 

            1、对目标对象功能增强,且不需手动实现代理对象;

            2、降低耦合,易扩展;

            3、目标对象无需实现接口,底层它是通过动态继承目标对象实现的动态代理;

            4、CGLib动态代理是通过 FastClass机制直接调用,执行效率较JDK动态代理效率高;

        缺点: 

            1、目标对象必须有接口实现;

            2、CGLib动态代理是使用 ASM框架写Class字节码生成代理类,生成代理类效率较JDK动态代理低;

        实现方式: 

            1、创建目标对象 (无需实现接口);

            2、创建代理对象并实现 MethodInterceptor接口,重写 intercept方法 (在方法体中进行增强);

      从测试结果以及类图可以清楚看出:

            CGLib动态代理中,目标对象无需实现接口,代理对象实现 MethodInterceptor接口并重写 intercept方法,再调用引目标对象的方法前 / 后进行逻辑的增强。

      在测试类中,第一步,运行结束后,我们发现在 E:\\cglib_proxy_class目录下多了三个文件,如下图所示:

             

                                                                                                  

      ... 天煞的产品又来需求了,今天只能就先到这里了,有想了解CGlib动态代理实现原理的小伙伴,给个关注吧

      后续出一篇关于CGLib动态代理实现原理、以及手写JDK动态代理的文章不要吝啬您的赞以及关注 ~

      ...

                                                                                                      

      这个时候,相信结合上述简介中的特长爱好(代理模式的适用场景)的你,又将码出新高度 ,码出新逼格 ~ 

      最后,都看到这里了,顺手点个赞,加个收藏吧 ,加个关注吧 ~  

                       Now ~ ~ ~写到这里,就写完了,如果有幸帮助到你,请记得关注我,共同一起见证我们的成长

猜你喜欢

转载自blog.csdn.net/qq_42175986/article/details/107014479