博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
动态代理
阅读量:6573 次
发布时间:2019-06-24

本文共 9734 字,大约阅读时间需要 32 分钟。

hot3.png

动态代理

JDK原生动态代理

jdk动态只能代理接口,所以我们只能代理实现了接口的类或者接口

public interface ProxyInterface throws SQLException {    String proxyTest(String s);}public class ProxyInterfaceImpl implements ProxyInterface {    @Override    public String proxyTest(String s) throws SQLException {        System.out.println(s);        return s+"  Hello world";    }}

定义一个实现InvocationHandler接口的类

方法调用会被转发到该类的invoke()方法中,你可以在该方法中增加处理逻辑

public class TestInvocationHandler implements InvocationHandler {    private  Object proxyObject;    public TestInvocationHandler(Object proxyObject) {        this.proxyObject = proxyObject;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("before method");        Object o= null;        try {            o = method.invoke(proxyObject,args);        }  catch (InvocationTargetException e) {            throw e.getTargetException();        }        System.out.println("after method");        return o;    }}

利用Proxy生成代理类

public class JdkProxyInstance {    /**     *      * @param proxyObject 被代理类     * @param proxyInterface  代理接口     * @return     */    public static   Object instance(Object proxyObject,Class ... proxyInterface){       return Proxy.newProxyInstance(JdkProxyInstance.class.getClassLoader(),proxyInterface,new TestInvocationHandler(proxyObject));    }}public class ProxyTest {    @Test    public void jdkProxyTest(){        ProxyInterface proxyInterface=new ProxyInterfaceImpl();        proxyInterface= (ProxyInterface)JdkProxyInstance.instance(proxyInterface,ProxyInterface.class);        String result=proxyInterface.proxyTest("chenbk");        System.out.println(result);    }}

生成代理类的方法

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler handler)

loader 指定代理类的类加载器

interfaces 代理对象需要实现的接口数组
handler 方法调用的实际处理者,代理对象的方法调用都会转发到这里

我们可以在handler的invoke方法中加入代码逻辑,例如日志打印、安全检查等

对于从Object中继承的方法,JDK Proxy会把hashCode()、equals()、toString()这三个非接口方法转发给InvocationHandler,其余的Object方法则不会转发。

查看JDK代理生成的Java类

jdk代理在运行时生成字节码,我们可以在将生成的字节码保存到文件中,然后利用反编译工具查看Java代码

public class ProxyGeneratorUtil {    public static void writeJdkProxyClassFile(String path,Class ... clazzs)throws IOException{        byte[] classFile=ProxyGenerator.generateProxyClass("ProxyTest",clazzs);        Files.write(Paths.get(path),classFile);    }        @Test    public  void createJdkProxyClass(){        try {          ProxyGeneratorUtil.writeJdkProxyClassFile("D:\\test\\ProxyTest.class",ProxyInterface.class);        } catch (IOException e) {            e.printStackTrace();        }    }}

生成的类大概就是这个样子

import com.chenbk.utils.proxy.ProxyInterface;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class ProxyTest extends Proxy  implements ProxyInterface{  private static Method m1;  private static Method m3;  private static Method m2;  private static Method m0;  public ProxyTest(InvocationHandler paramInvocationHandler)    throws   {    super(paramInvocationHandler);  }    public final String proxyTest(String paramString)    throws   {    try    {      return (String)this.h.invoke(this, m3, new Object[] { paramString });    }    catch (Error|SQLException|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  } // 省略了equal()、toString()、hashCode() 方法  static  {    try    {      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });      m3 = Class.forName("com.chenbk.utils.proxy.ProxyInterface").getMethod("proxyTest", new Class[] { Class.forName("java.lang.String") });      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);      return;    }    catch (NoSuchMethodException localNoSuchMethodException)    {      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());    }    catch (ClassNotFoundException localClassNotFoundException)    {      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());    }  }}
public class Proxy implements java.io.Serializable {    private static final long serialVersionUID = -2222568056686623797L;    /** ***省略代码 */      /**     * the invocation handler for this proxy instance.     * @serial     */    protected InvocationHandler h;    /**     * Constructs a new {@code Proxy} instance from a subclass     * (typically, a dynamic proxy class) with the specified value     * for its invocation handler.     *     * @param  h the invocation handler for this proxy instance     *     * @throws NullPointerException if the given invocation handler, {@code h},     *         is {@code null}.     */    protected Proxy(InvocationHandler h) {        Objects.requireNonNull(h);        this.h = h;    }    /** ***省略代码 */}

我们可以看到生成的类继承了Proxy,实现了我们我们需要代理的接口,实际上JDK代理只是生成了一个实现我们指定接口的类,该类的构造方法,需要传入一个InvocationHandler,初始化父类的h实例变量,在proxyTest方法中会将调用转发到hinvoke()方法中,由于Java只支持单继承,所以Jdk代理只能代理接口,

异常抛出处理

通过上面的程序,我们已经知道,方法会被转发到InvocationHandlerinvoke()方法中,如果在调用invoke()方法中抛出了受检查的的异常,并且这个受检查的异常没有在方法中声明,就会被UndeclaredThrowableException包装并抛出

try    {      return (String)this.h.invoke(this, m3, new Object[] { paramString });    }    catch (Error|SQLException|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }

然后在invoke()方法中,我们是利用反射去调用方法,当方法抛出异常时,会被反射包装成InvocationTargetException在抛出,这样当代理类处理时,会发现异常不会是,方法声明的异常,然后在被UndeclaredThrowableException包装

try {            o = method.invoke(proxyObject,args);        }  catch (InvocationTargetException e) {            throw e.getTargetException();        }

我们可以将InvocationTargetException异常捕获,然后抛出真的异常

CGLIB动态代理

CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。

定义一个实现了MethodInterceptor接口的类,方法会被转发到该类的intercept()方法

public class TestMethodInterceptor implements MethodInterceptor {    @Override    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {        System.out.println("beford method");        Object object=methodProxy.invokeSuper(o,objects);        System.out.println("after method");        return object;    }}

生成代理对象

public class CglibProxyInstance {    public static Object instance(Class clazz){        Enhancer enhancer=new Enhancer();        enhancer.setCallback(new TestMethodInterceptor());        enhancer.setSuperclass(clazz);        return enhancer.create();    }}public class ProxyTest {    @Test    public void cglibProxyTest(){        ProxyInterfaceImpl proxyInterface  =(ProxyInterfaceImpl)CglibProxyInstance.instance(ProxyInterfaceImpl.class);        String result=proxyInterface.proxyTest("chenbk");        System.out.println(result);    }}

对代理对象进行方法调用都会转发到TestMethodInterceptorintercept()方法,我们可以在intercept()方法中写代码逻辑

对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不会,因为它是final方法,CGLIB无法代理。final类型不能有子类,所以CGLIB也不能代理final类型。

查看生成的Java类代码

public static final String DEBUG_LOCATION_PROPERTY = "cglib.debugLocation";public class ProxyGeneratorUtil {    public static void writeCglibProxyClassFile(String path,Class  clazz){        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);        CglibProxyInstance.instance(clazz);    }    @Test    public  void createCglibProxyClass(){        ProxyGeneratorUtil.writeCglibProxyClassFile("D:\\test\\ProxyTest1",ProxyInterfaceImpl.class);    }}

设置系统变量cglib.debugLocation,指定生成的字节码要保存的文件目录,当生成代理对象时会自动将字节码保存到指定的目录

package com.chenbk.utils.proxy;import java.lang.reflect.Method;import net.sf.cglib.core.ReflectUtils;import net.sf.cglib.core.Signature;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 ProxyInterfaceImpl$$EnhancerByCGLIB$$296efbd extends ProxyInterfaceImpl  implements Factory{  private boolean CGLIB$BOUND;  private static final ThreadLocal CGLIB$THREAD_CALLBACKS;  private static final Callback[] CGLIB$STATIC_CALLBACKS;  private MethodInterceptor CGLIB$CALLBACK_0;  private static final Method CGLIB$proxyTest$0$Method;  private static final MethodProxy CGLIB$proxyTest$0$Proxy;  private static final Object[] CGLIB$emptyArgs;  private static final Method CGLIB$finalize$1$Method;  private static final MethodProxy CGLIB$finalize$1$Proxy;  private static final Method CGLIB$equals$2$Method;  private static final MethodProxy CGLIB$equals$2$Proxy;  private static final Method CGLIB$toString$3$Method;  private static final MethodProxy CGLIB$toString$3$Proxy;  private static final Method CGLIB$hashCode$4$Method;  private static final MethodProxy CGLIB$hashCode$4$Proxy;  private static final Method CGLIB$clone$5$Method;  private static final MethodProxy CGLIB$clone$5$Proxy;	/** ***省略代码 */	  final String CGLIB$proxyTest$0(String paramString)  {    return super.proxyTest(paramString);  }  public final String proxyTest(String paramString)  {    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;    if (tmp4_1 == null)    {      tmp4_1;      CGLIB$BIND_CALLBACKS(this);    }    MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;    if (tmp17_14 != null)      return (String)tmp17_14.intercept(this, CGLIB$proxyTest$0$Method, new Object[] { paramString }, CGLIB$proxyTest$0$Proxy);    return super.proxyTest(paramString);  }	/** ***省略代码 */  static  {    CGLIB$STATICHOOK1();  }}

可以看到,当代理对象的方法调用时,会将调用先转发到MethodInterceptorintercept()方法中

转载于:https://my.oschina.net/chenbkit/blog/1942206

你可能感兴趣的文章
2.2. MongoDB 管理
查看>>
Codeforces 626A Robot Sequence(模拟)
查看>>
知方可补不足~CSS中margin,padding,border-style有几种书写规范
查看>>
连接第二个 insance 到 first_local_net - 每天5分钟玩转 OpenStack(83)
查看>>
Android Studio 插件开发详解一:入门练手
查看>>
windows下bat批处理实现守护进程
查看>>
Jenkins+Gitlab+ansible-playbook上线流程
查看>>
回车提交、连续点击、layer提示
查看>>
kafka学习笔记
查看>>
微信小程序实质是什么? Hybrid App
查看>>
[禅悟人生]鹰和蜗牛都能登上金字塔尖
查看>>
Apache与Tomcat有什么关系和区别
查看>>
Swift语言精要 - Dictionary(字典)
查看>>
<转>好婚姻是彼此放心
查看>>
4.18. 创建与修改时间
查看>>
SAP MM GR/IR Account Maintenance的DEMO
查看>>
C#根据日期DateTime和持续时间int找到日期
查看>>
高性能日记--show profile剖析sql语句
查看>>
蹭着 Java 热点出生的 JavaScript 已经 22 岁了!
查看>>
mysqldump简单解析
查看>>