基于动态代理+ToStringBuilder构建日志记录参数

76 阅读4分钟

动态代理

动态代理是反射的一个非常重要的应用场景,常被用于一些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();
    }
}

步骤

  1. 通过代理对象的引用,使用反射获取其所有接口
  2. 通过Proxy类重新生成新类,新类实现代理类所有的接口
  3. 动态生成java代码,将代理中的逻辑加入代码中
  4. 编译生成新的class
  5. 将新的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的切面以及自定义注解实现入参和出参的打印