动态代理

动态代理

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值