Java之动态代理

静态代理

先看一个例子,有个汽车记录功能,我们既要记录行驶的时间,又要记录其它日志,如果这些事全部交给Car这个对象来做,那么它要处理的事情就太多了,既要跑还有写,所以为了给Car减轻负担,代理类就诞生了,代码如下:

//接口类
public interface MoveAble {
    void move();
}

//Car类
public class Car implements MoveAble {
    @Override
    public void move() {
        System.out.println("car move中");
    }
}

//记录时间代理类
public class TimeProxy implements MoveAble{
    private MoveAble m;
    public TimeProxy(MoveAble m){
        this.m = m;
    }

    @Override
    public void move() {
        System.out.println("time开始");
        m.move();
        System.out.println("time结束");
    }
}

//记录日志代理类
public class LogProxy implements MoveAble {
    private MoveAble m;
    public LogProxy(MoveAble m) {
        this.m = m;
    }

    @Override
    public void move() {
        System.out.println("log开始");
        m.move();
        System.out.println("log结束");
    }
}

从以上的代码中可以看出我们创造了两个新的类去继承和Car一样的接口,这样做本质上来说它们三个就都有跑的方法了。那么我们可以像下面的代码那样调用它们:

public static void main(String[] args) {
        Car car = new Car();
        LogProxy logProxy = new LogProxy(car);
        TimeProxy timeProxy = new TimeProxy(logProxy);
        timeProxy.move();
}

上面这段代码中把Car先放进了LogProxy里,又把LogProxy放进了TimeProxy里,接着调用了TimeProxy对象的move方法,这时候会一层一层的调用move方法,从TimeProxy到LogProxy再到Car,这样子就把记录时间和日志的功能给完成了。
那么这种代理模式有什么好处呢,可以让不同的功能做不同的事,也就是单一职责,拆分了代码逻辑,使逻辑更加的清晰了。
但是这种静态的代理有个问题,就是我们需要自己去写代理类,如果功能少还好说,要是功能多的话就会很麻烦,所以,动态代理就开始登场了。

动态代理

jdk中为我们提供了创建动态代理对象的方式,即Proxy类。
Proxy类位于java.lang.reflect包中,包含有一个静态的创建方法newProxyInstance,代码如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException{
       ......//暂时可以忽略的逻辑
}

在上面的创建Proxy类代码中需要接口三个参数:

  • ClassLoader loader
    类加载器
  • Class<?>[] interfaces
    类实现的接口
  • InvocationHandler h 处理器

我们重点看一下第三个参数InvocationHandler,这个类是个接口,里面只有一个invoke方法,我们所要做的处理逻辑都会在这个方法里执行。代码如下:

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

invoke方法里也有三个参数:

  • Object proxy 调用invoke方法的对象实例
  • Method method 对象的方法
  • Object[] args 参数

那么我们应该怎么使用动态代理创建我们的代理类呢,基于上面Car记录时间和日志的例子,我们可以这样写:

public static void main(String[] args) {
        Car car = new Car();
        MoveAble m = (MoveAble) Proxy.newProxyInstance(car.getClass().getClassLoader(), car.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("log开始");
                System.out.println("time开始");

                method.invoke(car);

                System.out.println("time结束");
                System.out.println("log结束");

                return null;
            }
        });
        m.move();
}

是不是很简单,有个动态代理,我们就不用自己去创建LogProxy,TimeProxy等等代理类了。

动态代理原理

知道了通过动态代理的方式可以帮我们自动的生成一些代理类,那么Proxy究竟是怎么办到的呢,下面就开始分析一下Proxy的内部原理。首先,看一下newProxyInstance这个静态方法,代码如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        
        Class<?> cl = getProxyClass0(loader, intfs); //1

        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);//2
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                cons.setAccessible(true);
                AccessController.doPrivileged call.
            }
            return cons.newInstance(new Object[]{h});//3
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

为了逻辑清晰我删除了注释,这段代码的主要逻辑就是在1,2,3处,这三步分别是:

  • 1.得到Proxy的Class对象
  • 2.通过Class对象得到Constructor对象
  • 3.通过Constructor对象的newInstance方法获得实例对象

下面我们主要分析Class<?> cl = getProxyClass0(loader, intfs); //1这行代码,来看看这个方法是怎么生成的Class类对象的。
这个方法的代码很简单:

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
}

主要就是看最后一行代码,返回的就是Class对象

public V get(K key, P parameter) {

       ......

        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;//1
                }
            }

            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

这段代码我删除了一些注释和与主要逻辑关系不大的代码。
首先来看几个比较的对象:

  1. Supplier<V> supplier
  2. Factory factory

先看最后返回的value是通过supplier.get()的,也就是代码中1处的逻辑,那么这个supplier是什么呢,它是个接口,下面的factory是它的实现类,下面的while循环中就是给supplier赋值的过程。也就是说value的最终获取是通过factory里的get方法取得,那么我们再看一下这个get方法逻辑:

public synchronized V get() { // serialize access
            ......

            // create new value
            V value = null;
            try {
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));//1
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this);
                }
            }

            return value;
}

这里最重要的逻辑是代码1处,从valueFactory的apply里得到了value,也就是我们最终要得到的Class对象。那么我们再来看看这个valueFactory是什么。

 public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

valueFactory是BiFunction接口的实现类对象,WeakCache初始化的时候被传进来

public class Proxy implements java.io.Serializable {

    private static final long serialVersionUID = -2222568056686623797L;

    /** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

        ......
}

可以看出来是在Proxy类中被初始化的,也就是ProxyClassFactory类对象。这个ProxyClassFactory也实现了BiFunction,那么上面的apply方法最终的实现逻辑就是在这个类中:

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

           ......
                
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;

                return generateProxy(proxyName, interfaces, loader, methodsArray,
                                     exceptionsArray);
            }
}

这个方法代码很长,我们只需要关注这几行就行了,可以看出,这个方法设定了一个规则,生成一个字符串proxyName也就是我们的代理类的类名,最后的generateProxy方法实际上是调用了本地的一个方法,就是生成Class对象的方法:

@FastNative
    private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
                                                 ClassLoader loader, Method[] methods,
                                                 Class<?>[][] exceptions);

那么到现在为止就把动态代理怎么生成代理对象的机制分析完了。

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

推荐阅读更多精彩内容

  • java的动态代理机制是在运行期间为目标对象生成一个代理对象,而将自己格外需要处理的业务逻辑进行“插入”,以达到运...
    Sophie12138阅读 336评论 0 0
  • https://blog.csdn.net/luanlouis/article/details/24589193 ...
    小陈阿飞阅读 864评论 1 1
  • 《李鸿章传》是由梁启超所著的书籍,从李鸿章的早年落拓,写到他参加镇压太平军、甲午海战,创办洋务运动,周旋于世界外交...
    落扬虚虚阅读 1,159评论 1 4
  • 1.我们那边的村落跟北方的村落不太一样,北方那边的村落都比较大,而且各种姓氏的都有,也有很多同村落结婚的,而我们那...
    安山1阅读 123评论 0 1
  • 这个雨蒙蒙的夜,一部电影刚好契合我今晚的心情,庆幸的是,身边的好友也有此共鸣。ʕ•̫͡•ིʔྀʕ 对这部片子没...
    亦水菲甜阅读 168评论 0 0