JDK动态代理

在Java中,动态代理使用的频率是很高的,比如Spring的AOP实现中就使用到了动态代理。作为一名有追求的码农,把这些基础搞清楚也是应该的。动态代理的实现方式有多种,各有特点,从JDK自带的动态代理,到CGLib、Javassist等。通常,动态代理用来给已有的接口实现增加通用的强化逻辑。

概要

本文主要涉及JDK动态代理的相关话题,包含以下内容:

  1. 什么是JDK动态代理?
  2. 如何使用JDK动态代理?
  3. 动态代理是如何实现的?
  4. JDK动态代理的使用场景和优缺点是什么?

什么是动态代理

所谓动态代理是相对于静态代理而言的。静态代理需要从代码层面为每种需要代理的类编写静态代理类,使用起来很不灵活。如果代理的类方法很多,会让你崩溃。而动态代理则更加灵活,它会在代码运行时或者编译时自动封装被代理类,大大降低编码量。

如何使用JDK动态代理

JDK为我们提供了一套自动生成代理Class的机制,以此来实现动态代理。JDK首先定义了接口InvocationHandler,这个接口有两个重要的作用。

  • 该接口唯一的方法提供了强化接口功能的入口。方法定义如下:
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    在此方法中,强化功能的逻辑被定义。比如:在某个方法调用前统一打印日志等。一旦调用该方法,被代理对象的功能和强化功能都会被实现。实现该接口的实现类中,在创建对象时,是需要传入原始的被代理对象的,以便在invoke方法中触发被代理对象的实现逻辑。
  • 为JDK自动生成的代理类提供统一的调用。也就是说,自动生成的代理类中,是调用这个接口的方法真正触发了接口本身功能及其强化功能。实际该接口隔离了动态代理类对被代理对象的耦合,这样和动态代理类耦合的仅仅是被代理的接口(可能是多个接口)和功能强化接口InvocationHandler。

实例

  1. 定义两个服务接口。
public interface UserService {
    UserInfo getUserById(int id);
    String getUserNameById(int id);
}
public interface SchoolService {
    String getSchoolName();
}
  1. 服务接口的实现类,注意这里实现了上面两个接口。
public class UserServiceImpl implements UserService, SchoolService {
    // 用户信息列表
    static final List<UserInfo> USERS = new ArrayList<UserInfo>();

    static {
        // 模拟三个用户
        USERS.add(createUser(1, 30, "user1", "male"));
        USERS.add(createUser(2, 28, "user2", "female"));
        USERS.add(createUser(3, 20, "user3", "male"));
    }

    static private UserInfo createUser(int id, int age, String name, String sex) {
        UserInfo info = new UserInfo();
        info.setId(id);
        info.setAge(age);
        info.setName(name);
        info.setSex(sex);
        return info;
    }

    public UserInfo getUserById(int id) {
        for (UserInfo info : USERS) {
            if (info.getId() == id) {
                System.out.println(info.toString());
                return info;
            }
        }

        return null;
    }

    public String getUserNameById(int id) {
        UserInfo userInfo = getUserById(id);
        if (userInfo != null) {
            return userInfo.getName();
        }
        return "User id is invalid!";
    }

    public String getSchoolName() {
        System.out.println("QingHua University");
        return "QingHua University";
    }
}
  1. 功能强化类
/**
 * 使用JDK的动态代理
 */
public class UserServiceJdkProxy implements InvocationHandler {

    // 被代理对象,这里就是UserServiceImpl对象。
    private Object target;

    // 通过构造函数传入被代理对象
    public UserServiceJdkProxy(Object target) {
        this.target = target;
    }

    // proxy是自动生成的代理类对象;
    // method是代理的接口方法,调用哪个就传入哪个;由于这里每个接口方法都会被传入,所以如果只针对某个方法做强化,就需要根据方法名区分。比如,本例中只对方法getUserById做强化,在其调用前做before(),调用后做after()。
    // args是方法参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        if ("getUserById".equals(method.getName())) {
            before();//功能强化
            result = method.invoke(target, args);
            after();//功能强化
        } else {
            result = method.invoke(target, args);
        }
        return result;
    }

    // 方法调用前的强化逻辑
    private void before() {
        System.out.println("before");
    }

    // 方法调用后的强化逻辑
    private void after() {
        System.out.println("after");
    }
}
  1. 客户端代码
public class MainApp {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(),
                userService.getClass().getInterfaces(),
                new UserServiceJdkProxy(userService));
        userServiceProxy.getUserById(1);

        SchoolService schoolService = (SchoolService) userServiceProxy;
        schoolService.getSchoolName();
    }
}

这里,Proxy.newProxyInstance方法是关键,该方法中会动态生成代理类$Proxy0,并实例化。userServiceProxy实际就是这个动态代理类对象。代理类同时实现了被代理对象实现的两个接口,所以可以强制转换成UserService和SchoolService。类似如下的定义:

public class $Proxy0 implements UserService, SchoolService {
    ...略...
}

看来,如果要想知道动态代理的实现机制,研究Proxy类是关键。

动态代理是如何实现的

JDK实际是定义了一套动态代理的套路给我们,有了这个套路,我们就只需要实现InvocationHandler接口即可。下面来说说这个套路。

  • 首先我们实现接口InvocationHandler,其中定义了我们的强化逻辑;
  • 然后,根据ClassLoader、被代理类实现的接口数组(可能是多个)以及实现了接口InvocationHandler的对象来生成统一的代理类$Proxy0。这正是Proxy.newProxyInstance方法的三个参数。
  • 最终返回给客户端的就是JDK自动生成的动态类对象,它代理了原始实现类,加上了强化逻辑。

自动生成的动态代理类$Proxy0源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.ws.springframework.aop.model.UserInfo;
import com.ws.springframework.aop.service.SchoolService;
import com.ws.springframework.aop.service.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UserService, SchoolService {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m5;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String getUserNameById(int var1) throws  {
        try {
            return (String)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String getSchoolName() throws  {
        try {
            return (String)super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final UserInfo getUserById(int var1) throws  {
        try {
            return (UserInfo)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.ws.springframework.aop.service.UserService").getMethod("getUserNameById", Integer.TYPE);
            m5 = Class.forName("com.ws.springframework.aop.service.SchoolService").getMethod("getSchoolName");
            m3 = Class.forName("com.ws.springframework.aop.service.UserService").getMethod("getUserById", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

这里有几点需要说明下:

  1. 除了被代理对象的接口方法之外,还有Object类下的三个函数也可能同时被强化。(这里如果没有“getUserById”的限制,那么所有的方法都会被强化)
  2. 代理类的基本逻辑就是遍历被代理对象所实现接口的所有method(外加Object的三个方法),自动生成代理类的Method。这些Method中统一调用InvocationHandler接口的方法,以触发强化后的接口逻辑。
  3. 从代理类的定义可以看出来,因为Java不支持多重继承,所以只能代理实现接口的实现类。
  4. Proxy中封装了生成代理class的逻辑。具体在ProxyClassFactory.apply方法中,最终的class文件是在ProxyGenerator.generateProxyClass中生成,有兴趣的可以去研究下。提醒下,ProxyClassFactory实现了BiFunction,其基本语意是输入ClassLoader、Class数组(接口数组),生成Class文件。

JDK动态代理的使用场景和优缺点

如果被代理类实现了接口,我们就使用JDK自带的代理机制。

  • 优点:不再依赖其他的类库,比如:CGlib等。据说,随着JDK版本的升级,JDK动态代理的效率越来越好,甚至高于CGlib的方式。
  • 缺点:只能代理实现了接口的类。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343