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 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 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 部分