• 设计模式笔记--代理


    JDK代理

    故事是:Tom过年回家没抢到车票,于是找黄牛强,于是黄牛加价100帮他抢了一张票。本故事纯属虚构,如有雷同,当为知己。

    src/main/java/com/xh/pattern/proxy/
    └── jdk
        ├── Huangniu.java
        ├── JDKProxyTest.java
        ├── Tom.java
        └── Travelers.java
    
    

    接口

    package com.xh.pattern.proxy.jdk;
    
    /**
     * Created by root on 3/13/18.
     */
    
    /**
     * 旅行者
     */
    public interface Travelers {
        /**
         * 买车票
         *
         * @param
         */
        void buyTrainTicket();
    }
    
    

    被代理者

    package com.xh.pattern.proxy.jdk;
    
    /**
     * Created by root on 3/13/18.
     */
    public class Tom implements Travelers {
        private String name;
    
        public Tom(String name) {
            this.name = name;
        }
    
        public void buyTrainTicket() {
            System.out.println("###我是:" + name + ",我要买一张票!");
        }
    }
    
    

    代理

    package com.xh.pattern.proxy.jdk;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * Created by root on 3/13/18.
     */
    public class Huangniu implements InvocationHandler {
        private Object target;
    
        public Object getInstance(Object target) {
            this.target = target;
            Class clazz = target.getClass();
            /**
             * 重要,重新生成的对象
             */
            return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
        }
    
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("===我是黄牛,说说需求");
            /**
             * 注意:
             * method.invoke(target, args);
             * 写成 method.invoke(proxy, args);就死循环啦
             */
            method.invoke(target, args);
            System.out.println("===OK,一张票多收100元");
            return null;
        }
    }
    

    测试类

    package com.xh.pattern.proxy.jdk;
    
    /**
     * Created by root on 3/13/18.
     */
    public class JDKProxyTest {
        public static void main(String[] args) {
            Travelers travelers = (Travelers) new Huangniu().getInstance(new Tom("TomJin"));
            travelers.buyTrainTicket();
        }
    }
    
    

    结果:

    ===我是黄牛,说说需求
    ###我是:TomJin,我要买一张票!
    ===OK,一张票多收100元
    

    CGLIB代理

    ├── cglib
    │   ├── CgHuangniu.java
    │   ├── CgProxyTest.java
    │   └── CgTom.java
    

    和JDK代理不一样,cglib代理不需要实现接口

    被代理者

    package com.xh.pattern.proxy.cglib;
    
    /**
     * Created by root on 3/13/18.
     */
    public class CgTom {
        private String name;
    
        public CgTom(String name) {
            this.name = name;
        }
    
        public CgTom() {
        }
    
    
        public void buyTrainTicket() {
            System.out.println("###我是CG:" + name + ",我要买一张票!");
        }
    }
    
    

    代理者

    package com.xh.pattern.proxy.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * Created by root on 3/13/18.
     */
    public class CgHuangniu implements MethodInterceptor {
    
        public Object getInstance(Object target) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("===我是CG黄牛,说说需求");
            /**
             * 注意:
             * methodProxy.invokeSuper(o, objects);
             * 这里是代理父类方法
             * 写成 method.invoke(o, objects) 就死循环啦
             */
            methodProxy.invokeSuper(o, objects);
            System.out.println("===OK,一张票多收100元");
            return null;
        }
    }
    
    

    测试类

    package com.xh.pattern.proxy.cglib;
    
    /**
     * Created by root on 3/13/18.
     */
    public class CgProxyTest {
        public static void main(String[] args) {
            CgTom cgTom = (CgTom) new CgHuangniu().getInstance(new CgTom("Tom2"));
            cgTom.buyTrainTicket();
        }
    }
    

    结果:

    ===我是CG黄牛,说说需求
    ###我是CG:null,我要买一张票!
    ===OK,一张票多收100元
    

    分析JDK代理

    观察发现jdk代理对象可以获取到被代理对象的属性,而cglib代理对象不能,why?
    看看Proxy.newProxyInstance

        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
    ...
            Class<?> cl = getProxyClass0(loader, intfs);
    
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
    ...
    

    由上可知,返回的对象应该是一个Proxy类型的,那么怎么又可以强转为Travelers呢?除非它也是Travelers类型

    修改测试类为:

    package com.xh.pattern.proxy.jdk;
    
    import sun.misc.ProxyGenerator;
    
    import java.io.FileOutputStream;
    
    /**
     * Created by root on 3/13/18.
     */
    public class JDKProxyTest {
        public static void main(String[] args) {
            Travelers travelers = (Travelers) new Huangniu().getInstance(new Tom("TomJin"));
            travelers.buyTrainTicket();
    
            //原理:
            //1、拿到被代理对象的引用,并且获取到它的所有的接口,反射获取
            //2、JDK Proxy类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口
            //3、动态生成Java代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)
            //4、编译新生成的Java代码.class
            //5、再重新加载到JVM中运行
            //以上这个过程就叫字节码重组
    
            //JDK中有个规范,只要要是$开头的一般都是自动生成的
    
            //通过反编译工具可以查看源代码
            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Travelers.class});
            FileOutputStream os = null;
            try {
                os = new FileOutputStream("/tmp/$Proxy0.class");
                os.write(bytes);
                os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
        }
    }
    
    

    通过idea打开class文件看看返回的是个什么

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    import com.xh.pattern.proxy.jdk.Travelers;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements Travelers {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void buyTrainTicket() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                m3 = Class.forName("com.xh.pattern.proxy.jdk.Travelers").getMethod("buyTrainTicket", 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 var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    
    

    其中:super.h.invoke(this, m3, (Object[])null);就相当于调用Huangniu的invoke方法啦。

    之前在网上看到有写用反射实例化对象[必须要有]无参构造器,很明显的是不对的。Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。

    分析CGLIB代理

    如果想看看cglib生成的代理类是不能用上面的方法的。有一个更牛逼的工具,可以查看运行时JVM里的类信息。
    1、在测试类最后添加一句

    public class CgProxyTest {
        public static void main(String[] args) throws InterruptedException {
            CgTom cgTom = (CgTom) new CgHuangniu().getInstance(new CgTom("Tom2"));
            cgTom.buyTrainTicket("tom4");
            System.out.println("END");//断点
    
        }
    }
    

    2、查看Java进程

    jps
    

    3、debug启动测试类,重复2,对比会发现多了一个进程,后面会用到
    4、启动HSDB

    java -classpath "/opt/Java/jdk1.8.0_121/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB
    

    点击File-->Attach to HotSpot process ,添加进程号
    再点击Tools-->Class Browser ,输入关键字CgTom过滤。

    5、生成class文件
    点击链接

    Create .class for all classes
    

    会在启动HSDB的目录下生成文件
    6、查看class文件
    如果直接丢到idea下会有一些内容看不到,只能看看方法名。
    like this

    public class CgTom$$EnhancerByCGLIB$$ee6f616a extends com.xh.pattern.proxy.cglib.CgTom implements net.sf.cglib.proxy.Factory {
        private boolean CGLIB$BOUND;
        public static java.lang.Object CGLIB$FACTORY_DATA;
        private static final java.lang.ThreadLocal CGLIB$THREAD_CALLBACKS;
        private static final net.sf.cglib.proxy.Callback[] CGLIB$STATIC_CALLBACKS;
    ...
        public CgTom$$EnhancerByCGLIB$$ee6f616a(java.lang.String s) { /* compiled code */ }
    
        public CgTom$$EnhancerByCGLIB$$ee6f616a() { /* compiled code */ }
    
        public final boolean equals(java.lang.Object o) { /* compiled code */ }
    
        public final java.lang.String toString() { /* compiled code */ }
    ...
    

    如果用jd-gui打开

    package com.xh.pattern.proxy.cglib;
    
    import java.lang.reflect.Method;
    import net.sf.cglib.proxy.Callback;
    import net.sf.cglib.proxy.Factory;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CgTom$$EnhancerByCGLIB$$ee6f616a
      extends CgTom
      implements Factory
    {
      private boolean CGLIB$BOUND;
      public static Object CGLIB$FACTORY_DATA;
      private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    ...
      
      public CgTom$$EnhancerByCGLIB$$ee6f616a(String paramString)
      {
        super(paramString);
        CGLIB$BIND_CALLBACKS(this);
      }
      
      public CgTom$$EnhancerByCGLIB$$ee6f616a()
      {
        CGLIB$BIND_CALLBACKS(this);
      }
      
      static {}
      
     ...
      public final void buyTrainTicket(String paramString)
      {
        MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
        if (tmp4_1 == null)
        {
          tmp4_1;
          CGLIB$BIND_CALLBACKS(this);
        }
        if (this.CGLIB$CALLBACK_0 != null) {
          return;
        }
        super.buyTrainTicket(paramString);
      }
      
      public final void buyTrainTicket()
      {
        MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
        if (tmp4_1 == null)
        {
          tmp4_1;
          CGLIB$BIND_CALLBACKS(this);
        }
        if (this.CGLIB$CALLBACK_0 != null) {
          return;
        }
        super.buyTrainTicket();
      }
      
      public void setCallback(int paramInt, Callback paramCallback)
      {
        switch (paramInt)
        {
        case 0: 
          this.CGLIB$CALLBACK_0 = ((MethodInterceptor)paramCallback);
          break;
        }
      }
    
     ...
    }
    

    分析这个class可以知道cglib生成的代理类继承了我们的CgTom,实现了Factory接口
    他有个属性private MethodInterceptor CGLIB$CALLBACK_0;这个便是我们的CgHuangniu
    再看看buyTrainTicket(String paramString),它会调用CGLIB$BIND_CALLBACKS(this);
    这样就和CgHuangniu关联了。
    其实这样分析一遍也就了解了大概,细节之处还是依旧理解的有限,比如我们发现CgTom有四个,有一个原始的,上面一个还有两个是也cglib生成的,看的是一脸懵逼啊。

  • 相关阅读:
    sonar6.7.2启动报错
    linux 查看/修改jdk版本
    idea一款颜值很高的theme
    生成唯一UUID
    连接池异常
    手机网页点击后出现蓝色框
    iScroll4中事件点击一次却触发两次解决方案
    base.js
    javascript与css3动画学习笔记
    javascript对象学习笔记
  • 原文地址:https://www.cnblogs.com/lanqie/p/8556574.html
Copyright © 2020-2023  润新知