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();
}
}