cglib 教程 - 使用cglib實現動態代理

cglib是一款優秀的Java 字節碼生成框架,它可以生成并操縱Java字節碼(底層基于ASM)。

cglib - Byte Code Generation Library is high level API to generate and transform Java byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access.

動態代理實現

JDK 動態代理

Java 實現動態代理主要涉及以下幾個類:

  • java.lang.reflect.Proxy: 這是生成代理類的主類,通過 Proxy 類生成的代理類都繼承了 Proxy 類,即 DynamicProxyClass extends Proxy。
  • java.lang.reflect.InvocationHandler: 這里稱他為"調用處理器",他是一個接口,我們動態生成的代理類需要完成的具體內容需要自己定義一個類,而這個類必須實現 InvocationHandler 接口。
package com.bytebeats.codelab.cglib.proxy.impl;

import com.bytebeats.codelab.cglib.proxy.ProxyFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * JDK動態代理實現
 * @author Ricky
 *
 */
public class JdkProxyFactory implements ProxyFactory {

    @Override
    public <T> T getProxy(final Object target) {

        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                }
                String methodName = method.getName();
                //打印日志
                System.out.println("[before] The method " + methodName + " begins with " + (args!=null ? Arrays.asList(args) : "[]"));

                //調用目標方法
                Object result = null;
                try {
                    //前置通知
                    result = method.invoke(target, args);
                    //返回通知, 可以訪問到方法的返回值
                } catch (NullPointerException e) {
                    e.printStackTrace();
                    //異常通知, 可以訪問到方法出現的異常
                }
                //后置通知. 因為方法可以能會出異常, 所以訪問不到方法的返回值
                //打印日志
                System.out.println("[after] The method ends with " + result);
                return result;
            }
        });
    }
}

JDK 動態生成的代理類具有幾個特點:

  • 繼承 Proxy 類,并實現了在Proxy.newProxyInstance()中提供的接口數組。
  • 命名方式為 $ProxyN,其中N會慢慢增加,一開始是 $Proxy1,接下來是$Proxy2...
  • 有一個參數為 InvocationHandler 的構造函數。這個從 Proxy.newProxyInstance() 函數內部的clazz.getConstructor(new Class[] { InvocationHandler.class }) 可以看出。

Java 實現動態代理的缺點:因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),只能針對接口創建代理類,不能針對類創建代理類。

CGLib 動態代理

net.sf.cglib.proxy.Enhancer 類提供了非常簡潔的API來創建代理對象,有兩種回調的防方式:InvocationHandler和MethodInterceptor。

1、InvocationHandler

@Test
public void testInvocationHandler() throws Exception {
  Enhancer enhancer = new Enhancer();
  enhancer.setSuperclass(SampleClass.class);
  enhancer.setCallback(new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
      if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello cglib!";
      } else {
        throw new RuntimeException("Do not know what to do.");
      }
    }
  });
  SampleClass proxy = (SampleClass) enhancer.create();
  assertEquals("Hello cglib!", proxy.test(null));
  assertNotEquals("Hello cglib!", proxy.toString());
}

2、

@Test
public void testMethodInterceptor() throws Exception {
  Enhancer enhancer = new Enhancer();
  enhancer.setSuperclass(SampleClass.class);
  enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
        throws Throwable {
      if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello cglib!";
      } else {
        return proxy.invokeSuper(obj, args);
      }
    }
  });
  SampleClass proxy = (SampleClass) enhancer.create();
  assertEquals("Hello cglib!", proxy.test(null));
  assertNotEquals("Hello cglib!", proxy.toString());
  proxy.hashCode(); // Does not throw an exception or result in an endless loop.
}

對上面代碼稍作封裝,如下:

package com.bytebeats.codelab.cglib.proxy.impl;

import com.bytebeats.codelab.cglib.proxy.ProxyFactory;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * CgLib動態代理實現
 * @author Ricky
 *
 */
public class CgLibProxyFactory implements ProxyFactory {
    private final Enhancer en = new Enhancer();

    @Override
    public <T> T getProxy(Object target) {

        //進行代理
        en.setSuperclass(target.getClass());
        en.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                }
                String methodName = method.getName();
                //打印日志
                System.out.println("[before] The method " + methodName + " begins with " + (args!=null ? Arrays.asList(args) : "[]"));
                Object result = null;
                try{
                    //前置通知
                    result = methodProxy.invokeSuper(o, args);
                    //返回通知, 可以訪問到方法的返回值
                    System.out.println(String.format("after method:%s execute", method.getName()));
                } catch (Exception e){
                    e.printStackTrace();
                    //異常通知, 可以訪問到方法出現的異常
                }
                //后置通知. 因為方法可以能會出異常, 所以訪問不到方法的返回值
                //打印日志
                System.out.println("[after] The method ends with " + result);
                return result;
            }
        });
        //生成代理實例
        return (T)en.create();
    }
}

完整測試類:

package com.bytebeats.codelab.cglib.proxy;

import com.bytebeats.codelab.cglib.proxy.impl.CgLibProxyFactory;
import com.bytebeats.codelab.cglib.proxy.impl.JdkProxyFactory;
import com.bytebeats.codelab.cglib.service.HelloService;
import com.bytebeats.codelab.cglib.service.HelloServiceImpl;

public class ProxyDemo {

    public static void main(String[] args) {

        //需要被代理的類
        HelloService helloService = new HelloServiceImpl();

        //jdk代理
        ProxyFactory jdkProxyFactory = new JdkProxyFactory();
        HelloService jdkProxy = jdkProxyFactory.getProxy(helloService);
        jdkProxy.echo("ricky");
        jdkProxy.hashCode();

        //CgLib代理
        ProxyFactory cgLibProxyFactory = new CgLibProxyFactory();
        HelloService cgLibProxy = cgLibProxyFactory.getProxy(helloService);
        cgLibProxy.echo("ricky");
        jdkProxy.hashCode();
    }

}

參考資料
CGLIB Tutorial
Java 動態代理機制分析及擴展,第 1 部分

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容