Java 动态代理,看这篇就够了

5,240 阅读6分钟

这篇文章需要用到 Java 的反射知识,如果对反射还不清楚的小伙伴,可以先移步到这里 《Java进阶-反射》

编程思想都是来自于生活的,“代理” 在生活中很常见。比如我们买一个东西时,一般都不会是直接从工厂里买的,而是去商店或者其他的商家买,这些商家做的事情就是代理。再比如,做微商的朋友经常在朋友圈里推广商品,它们也是代理商。Java 程序中的代理是用接口实现的,用户买某件商品的需求就可以看做是一种接口,而真正卖给用户商品的是代理商,代理商和厂家都和这种需求有直接关系,代理商又和厂家有关系(代理与被代理),也即是下面这幅图。

代理模式.png

在这里,图中的用户可以看做买东西的人,被代理的接口可以看做是某种商品,代理看做是中间代理商,真正被代理的对象看做是生产商品的工厂或者说是厂家吧。下面我们就以这个为例,实现代理模式。

注:这里将所有的类放在了一个文件夹内,将内部类用 static 修饰,是为了方便使用。

public class StaticProxy {
    // 商品接口
    static interface Goods {
        public void trade();
    }

    // 商品产地类
    static class ChangJia implements Goods {
        @Override
        public void trade() {
            System.out.println("厂家生产商品");
        }
    }

    // 商品代理类
    static class JingXiaoShang implements Goods {
        private ChangJia changJia;

        public JingXiaoShang(ChangJia changJia) {
            this.changJia = changJia;
        }

        @Override
        public void trade() {
            System.out.println("厂家生产产品,成本为1000元");
            changJia.trade();
            System.out.println("经销商卖出商品,利润为100元");
        }
    }

    // 用户购买东西
    public static void main(String[] args) {
        Goods proxy = new JingXiaoShang(new ChangJia());
        proxy.trade(); // 商品被交易
    }
}

输出结果为

厂家生产产品,成本为1000元
厂家生产商品
经销商卖出商品,利润为100

上面的代码便是代理模式的实现,这是一种 “静态代理”,也即是代理类在程序编译期就已经确定了,而在 Java 还可以实现 “动态代理”,代理类是在程序运行时生成的。

Java 实现动态代理有两种方式,一种是 Java 自带的 JDK 动态代理,还有一种是使用字节码增强技术实现的 CGLIB 库动态代理,下面就详细介绍并用这两种方式实现上面代码的功能。

1、JDK 动态代理

JDK 动态代理本质上使用的是 Java 中的反射。方法定义在接口(这里是 Goods 接口)中,被代理的类(这里是ChangJia类)要实现接口,进而实现接口中的方法。当调用接口中的方法时,拦截要执行的方法,添加其他的操作,还需要一个处理拦截的处理类(这里是 GoodsHander 类),这个处理类实现自 InvocationHandler 接口,其中 invoke() 方法会在调用接口中的方法时先执行,这样就起到了代理的作用。和上面代理模式中的实现不同,JDK动态代理并没有直接定义代理类,而是新增了接口中方法的处理类,当程序运行中动态生成一个代理类或者直接生成一个代理对象来执行方法,详情可以仔细阅读下面的代码及注释。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKDynamicProxy {
    // 商品接口
    static interface Goods {
        public void trade();
    }

    // 商品产地类
    static class ChangJia implements Goods {
        @Override
        public void trade() {
            System.out.println("厂家生产商品");
        }
    }

    // 商品处理类
    static class GoodsHander implements InvocationHandler {
        private Object object; // 要代理的对象,这里为商品

        public GoodsHander(Object object) {
            this.object = object;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // proxy 为生成的代理对象
            System.out.println("厂家生产产品,成本为1000元");
            Object result = method.invoke(object, args);
            System.out.println("经销商卖出商品,利润为100元");
            return result;
        }
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        GoodsHander goodsHander = new GoodsHander(new ChangJia()); // GoodHander类和ChangJia打交道
        // 先动态生成代理类,再生成代理类的对象
//        Class proxyClass = Proxy.getProxyClass(Goods.class.getClassLoader(),Goods.class.getInterfaces());
//        Goods proxy = (Goods) proxyClass.getConstructor(GoodsHander.class).newInstance(goodsHander);
        // 动态生成的代理对象,一步到位
        Goods proxy = (Goods) Proxy.newProxyInstance(Goods.class.getClassLoader(), Goods.class.getInterfaces(), goodsHander);
        proxy.trade(); // 执行的是GoodsHander中的 invoke() 方法,然后是ChangJia中的 trade() 方法
    }
}

2、CGLIB 动态代理

上面的 JDK 动态代理需要定义一个接口,实现类实现接口中的方法,如果实现类不能实现接口时,我们就无法使用上面这种方式代理了,而可以使用下面这种 CGLIB 动态代理。CGLIB 动态代理使用的字节码增强技术,也即是对编译好的 class(字节码)文件生成该类的子类。利用多态性,调用父类中的方法实际上是调用的子类中对应的方法。所以由于是继承,被代理的类或者方法不要用 final 关键字修饰。这个子类是在程序中运行的生成的,所以使用 CGLIB 库也是动态代理。使用 CGLIB 要引入 cglib 的 jar 包以及依赖 jar 包 asm.jar,这个 asm.jar 是字节码增强技术的核心。使用 CGLIB 库就不需要接口了,增加了一个方法增强类,通过 CGLIB 库中的 Enhance 增强类来生成被代理类的子类,详情可以仔细阅读下面的代码及注释。

注:使用 CGLIB 动态代理之前需要导入相关 jar 包,可以单独导入 cglib-*.jar 包和 asm-.jar 包(注意两者的版本搭配),也可以只导入一个 cglib-nodep-*.jar 包(包含了 asm)。下载地址:github.com/cglib/cglib…

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLIBDynamicProxy {
    // 商品产地类
    static class ChangJia{
        public void trade() {
            System.out.println("厂家生产商品");
        }
    }

    // 方法增强类
    static class GoodsTrade implements MethodInterceptor {
        @Override
        // o 代表要增强的对象,method代表要拦截的方法,objects 代表方法中的参数,methodProxy 代表对方法的代理
        public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("厂家生产产品,成本为1000元");
            methodProxy.invokeSuper(object, objects);
            System.out.println("经销商卖出商品,利润为100元");
            return object;
        }

        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer(); // 增强类对象
            enhancer.setSuperclass(ChangJia.class); // 设置要增强的类,这里为 ChangJia
            GoodsTrade goodsTrade = new GoodsTrade();
            enhancer.setCallback(goodsTrade); // 设置要增强的方法,这里为 GoodsTrade
            ChangJia changJia = (ChangJia) enhancer.create(); // 生成增强过的子类对象
            changJia.trade(); // 调用方法实际为增强过的方法
        }
    }
}

两种动态代理方式的区别:

  • JDK 动态代理使用的是反射技术,被代理的类要实现方法接口。
  • CGLIB 动态代理使用的是字节码增强技术,被代理的类不用实现方法接口。

在大名鼎鼎的 Spring 框架中,这两种代理方式都有使用,Spring 会根据被代理的类有没有实现接口,在 JDK 动态代理和 CGLIB 之间动态切换。另外面向切面编程的 AOP 也是动态代理思想的体现,通过在方法执行前后织入新的方法,来达到增强方法的效果,动态代理在框架中应用广泛。

编程心路