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

这篇文章需要用到 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)。下载地址:https://github.com/cglib/cglib/releases

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 也是动态代理思想的体现,通过在方法执行前后织入新的方法,来达到增强方法的效果,动态代理在框架中应用广泛。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏python学习路

二、Python介绍

Python 是一门什么样的语言? python是一门动态解释性的强类型定义语言。 编程语言主要从以下几个角度为进行分类,编译型和解释型、静态语言和动态语言、...

3684
来自专栏阿杜的世界

如何设计接口的测试用例边界值测试组合条件测试

今天在帮同事review代码的时候,发现他的代码遗漏了一些场景的处理,就顺便跟他多聊了些为对这个话题的看法。

942
来自专栏工科狗和生物喵

【计算机本科补全计划】指令:计算机的语言(MIPS) Part3

正文之前 今天学的很尴尬,因为有事情,而且新认识了两个计算机学院的保研大佬,不得不感叹我找的导师之强,第一个去上交的,是被金老师推荐去的,听说是跟了目前亚洲第一...

3238
来自专栏大闲人柴毛毛

柴毛毛大话设计模式——开发常用的设计模式梳理

写在最前 本文是笔者的一点经验总结,主要介绍几种在Web开发中使用频率较高的设计模式。 本文篇幅较长,建议各位同学挑选感兴趣的设计模式阅读。 在阅读的同时,也...

4857
来自专栏新智元

Go 2.0发布在即,程序员有太多话要说

Go语言的开发者正着手准备开发2.0版本,并从以下三个方面发布了初步的设计方案(非官方正式版),以供社区开展讨论:

7001
来自专栏魂祭心

原 Introduction to the

3589
来自专栏我的技术专栏

Java多线程编程—锁优化

1517
来自专栏微信公众号:Java团长

Java 异常处理的误区和经验总结

本文着重介绍了 Java 异常选择和使用中的一些误区,希望各位读者能够熟练掌握异常处理的一些注意点和原则,注意总结和归纳。只有处理好了异常,才能提升开发人员的基...

601
来自专栏java工会

Java异常处理的误区和经验总结

1525
来自专栏Android机动车

设计模式——代理模式

现在有个非常流行的程序叫做面向切面编程(AOP),其核心就是采用了动态代理的方式。怎么用?Java为我们提供了一个便捷的动态代理接口 InvocationHan...

1021

扫码关注云+社区

领取腾讯云代金券