Java OpenResty Spring Spring Boot MySQL Redis MongoDB PostgreSQL Linux Android Nginx 面试 小程序 Arthas JVM AQS juc Kubernetes Docker 诊断工具


Java 中的动态代理

Java cglib 面试 大约 5114 字

Java 内置 API

使用Proxy类代理一个对象,该对象必须实现一个接口。故Java内置的API只能代理实现了接口的对象。

private static class ProxyFactory {

    // 返回代理类的对象
    public static Object getProxyInstance(Object obj) {
        MyInvocationHandler handler = new MyInvocationHandler(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }

}

private static class MyInvocationHandler implements InvocationHandler {

    // 被代理类的对象
    private Object obj;

    public MyInvocationHandler(Object obj) {
        this.obj = obj;
    }

    // 当通过代理类的对象调用方法A时,就是自动调用invoke方法
    // proxy: 代理类的对象
    // method: 代理类调用的方法

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 被代理对象的方法返回值
        System.out.println("调用真实方法前");
        Object returnValue = method.invoke(obj, args);
        System.out.println("调用真实方法后");
        return returnValue;
    }
}

测试

public static void main(String[] args) {
    SuperMan superMan = new SuperMan();
    Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
    System.out.println(proxyInstance.getBelief());
}

private interface Human {
    String getBelief();

    void eat(String food);
}

private static class SuperMan implements Human {

    @Override
    public String getBelief() {
        System.out.println("调用getBelief方法");
        return "I can fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("调用eat方法");
        System.out.println("喜欢吃#" + food);
    }
}

输出

调用真实方法前
调用getBelief方法
调用真实方法后
I can fly

第三方库 cglib

不能对final类以及final方法进行代理。

测试类

public class App {

    public String test(String input) {
        System.out.println("test~~~~~~~~~~~~~input#" + input);
        return input;
    }

}

拦截方法

public void methodIntercept() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(App.class);
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("调用 " + method.getName() + " 前");
            Object returnValue = proxy.invokeSuper(obj, args);
            // 不能使用method调用,会造成递归 StackOverflow
            //Object returnValue = method.invoke(obj, args);
            System.out.println("调用 " + method.getName() + " 后");
            return returnValue;
        }
    });
    App obj = (App) enhancer.create();
    obj.test("hello cglib");
}

输出:

调用 test 前
test~~~~~~~~~~~~~input#hello cglib
调用 test 后

拦截指定方法

不拦截Object类中的方法以及方法返回值不为String的方法。

public static void methodFilterIntercept() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(App.class);
    CallbackHelper callbackHelper = new CallbackHelper(App.class, new Class[0]) {
        @Override
        protected Object getCallback(Method method) {
            if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){
                return new FixedValue() {
                    @Override
                    public Object loadObject() throws Exception {
                        return "From cglib";
                    }
                };
            }else{
                return NoOp.INSTANCE;
            }
        }
    };
    enhancer.setSuperclass(App.class);
    enhancer.setCallbackFilter(callbackHelper);
    enhancer.setCallbacks(callbackHelper.getCallbacks());
    App obj = (App) enhancer.create();

    System.out.println("自定义test方法#" + obj.test(null));
    System.out.println("toString方法#"+obj.toString());
    System.out.println("getClass方法#" + obj.getClass());
    System.out.println("hashCode方法#" + obj.hashCode());
}

输出:

自定义test方法#From cglib
toString方法#com.example.App$$EnhancerByCGLIB$$280c2728@2d8e6db6
getClass方法#class com.example.App$$EnhancerByCGLIB$$280c2728
hashCode方法#764308918

拦截返回值

修改返回值为固定值

public void fixValueIntercept() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(App.class);
    enhancer.setCallback(new FixedValue() {
        @Override
        public Object loadObject() throws Exception {
            return "From cglib FixedValue";
        }
    });

    App obj = (App) enhancer.create();
    System.out.println("自定义test方法#" + obj.test(null));
    System.out.println("toString方法#"+obj.toString());
    System.out.println("getClass方法#" + obj.getClass());
    System.out.println("hashCode方法#" + obj.hashCode());
}

输出:可以看到testtoString因为都是非final方法的,所以都被拦截修改了返回值;getClassfinal修饰,故无法修改;hashCode方法返回int类型,但我们修改后返回自己输入的From cglib FixedValue字符串类型,故抛出类型转换异常。

自定义test方法#From cglib FixedValue
toString方法#From cglib FixedValue
getClass方法#class com.example.App$$EnhancerByCGLIB$$de29798b
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
    at com.example.App$$EnhancerByCGLIB$$de29798b.hashCode(<generated>)
    at com.example.App.fixValueIntercept(App.java:40)
    at com.example.App.main(App.java:19)

注意

由于cglib大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。

cglib 开源地址

https://github.com/cglib/cglib

阅读 2667 · 发布于 2021-04-01

————        END        ————

Give me a Star, Thanks:)

https://github.com/fendoudebb

扫描下方二维码关注公众号和小程序↓↓↓

扫描二维码关注我
昵称:
随便看看 换一批