动态代理
动态代理是反射的一个非常重要的应用场景,常被用于一些java框架中,如Spring的AOP就是基于动态代理实现的。java中的动态代理又分为jdk动态代理和cglib动态代理
jdk动态代理
在jdk动态代理中有两个十分重要的类和接口,分别是Proxy类以及InvoationHandler接口,如果我们想通过代理对象调用一个方法时,这个方法就会将调用转发给InvocationHandler这个接口的invoke方法进行处理
InvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
该方法传入了三个参数
- proxy:jdk动态生成的最终代理对象
- method:我们要调用真实对象的某个方法的Method对象
- args:调用真实对象传递的参数
Proxy
一般情况下我们基于Proxy的静态方法newProxyInstance()来构建代理对象
- loader:ClassLoader对象,依据我们构造的代理对象实例的ClassLoader传入
- interfaces:接口对象的数组,依据我们传入的接口提供接口对应的实现方法
- Hanler:传入InvocationHandler对象 我们依据其生成对应的代理对象,在我们调用代理对象的方法实际上调用的是InvocationHandler的invoke方法
例子
// 代理类上层接口
public interface Person {
void rentHouse();
}
-----------------------------------------------------------------------------------------------
// 接口实现类
@AllArgsConstructor
public class Tenant implements Person {
private String name;
@Override
public void rentHouse() {
System.out.println("正在租房中");
}
}
-----------------------------------------------------------------------------------------------
// 动态代理方法
@AllArgsConstructor
public class RentInvocationHandler implements InvocationHandler {
private Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before procuration");
Object returnValue = method.invoke(target, args);
System.out.println("after procuration");
return returnValue;
}
}
-----------------------------------------------------------------------------------------------
// 测试用例
public class Test {
public static void main(String[] args) {
Intermediary intermediary = new Intermediary("中介1号", new Tenant("李四"));
RentInvocationHandler handler = new RentInvocationHandler(intermediary);
Person person = (Person) Proxy.newProxyInstance(intermediary.getClass().getClassLoader(), intermediary.getClass().getInterfaces(), handler);
System.gc();
person.rentHouse();
}
}
步骤
- 通过代理对象的引用,使用反射获取其所有接口
- 通过Proxy类重新生成新类,新类实现代理类所有的接口
- 动态生成java代码,将代理中的逻辑加入代码中
- 编译生成新的class
- 将新的class文件重新加载到JVM中运行
CGLIB动态代理
CGLIB通过继承被代理对象来实现动态代理,其需要实现MethodInterceptor接口并重写intercept方法,使用Enhancer增强类来代理
CGLIB通过字节码技术为代理类创建一个子类,并在子类中采用方法拦截的技术拦截父类所有的方法调用,随后植入自定义逻辑,因为通过继承的方式来实现,所以final和private方法无法被重写
MethodInterceptor
- o:动态代理生成的对象
- method:实际调用方法
- objects:方法入参
- methodProxy:Method的代理类,可以实现委托对象的方法的调用,我们通常使用methodProxy.invokeSuper(proxy,args)来拦截
例子
// 需要代理的对象
public class Cat {
public void call2() {
System.out.println("执行");
}
}
-----------------------------------------------------------------------------------------------
// 代理实现类
public class DynamicInterceptor implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
// 构建enhancer对象
public Object getEnhancer(Class clazz) {
// 设置需要创建子类的类
enhancer.setSuperclass(clazz);
// 设置回调方法
enhancer.setCallback(this);
// 使用字节码技术动态创建子类实例
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("执行前");
Object invoke = methodProxy.invokeSuper(o, objects);
System.out.println("执行后");
return invoke;
}
}
-----------------------------------------------------------------------------------------------
// 代理工厂
public class LogObjectFactory {
public static <T> T getObject(Class<T> t) {
DynamicInterceptor dynamicInterceptor = new DynamicInterceptor();
Object enhancer = dynamicInterceptor.getEnhancer(t);
return (T) enhancer;
}
}
-----------------------------------------------------------------------------------------------
// 测试用例
public class BaseDynamicTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Cat cat = LogObjectFactory.getObject(Cat.class);
cat.call2();
}
}
结果
日志记录前
执行
日志记录后
JDK动态代理和CGLIB动态代理的区别
| JDK动态代理 | CGLIB动态代理 | |
|---|---|---|
| 代理方式 | 实现被代理对象所实现的接口 | 继承被代理对象 |
| 生成字节码方式 | 直接写Class字节码 | 使用ASM框架写Class字节码 |
| 代理方法调用 | 使用反射调用 | 通过FastClass机制直接调用 |
| 运行效率 | 生成代理对象效率比CGLIB高 | 执行效率比JDK动态代理效率高 |
| 实现原理 | 利用反射生成一个实现代理接口的匿名类,再调用具体方法前调用InvokeHandler来处理,核心实现在InvocationHandler.invoke()方法 | 利用ASM开源包,对代理对象类的class文件进行加载,修改其字节码生成子类来处理,核心实现在MethodInterceptor.intercept()方法 |
基于动态代理构建日志解析
此处我们直接用上述例子进行改造
-----------------------------------------------------------------------------------------------
public class DynamicInterceptor implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getEnhancer(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (objects.length > 0) {
for (Object object : objects) {
// 使用ToStringBuilder读取传参并进行解析和打印
String reflectionToString = ToStringBuilder.reflectionToString(object);
System.out.println(reflectionToString);
}
}
System.out.println("日志记录前");
Object invoke = methodProxy.invokeSuper(o, objects);
System.out.println("日志记录后");
return invoke;
}
}
上述只是一个简易的模板,在实际开发中我们可以结合spring的切面以及自定义注解实现入参和出参的打印