设计模式笔记--代理

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生成的,看的是一脸懵逼啊。

原文地址:https://www.cnblogs.com/lanqie/p/8556574.html