Java代理机制

#静态代理

    静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码 。

例如:

```

    //声明一个接口

    package com.ceshi.proxy;

    public interface Moveable {

        //提供一个move方法

void move();

}

package com.ceshi.proxy;

import java.util.Random;

//写一个实现类实现这个接口

public class Tank implements Moveable {

@Override

public void move() {

System.out.println("Tank Moving...");

try {

Thread.sleep(new Random().nextInt(10000));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

package com.ceshi.proxy;

//再写一个tank的时间代理类实现Moveable接口

public class TankTimeProxy implements Moveable {

    public TankTimeProxy(Moveable t) {

        super();

        this.t = t;

    }

    Moveable t;

    @Override

    public void move() {

        //在执行move方法前后加上有关时间的代码逻辑,以此来实现类似日志管理

        long start = System.currentTimeMillis();

        System.out.println("starttime:" + start);

        t.move();

        long end = System.currentTimeMillis();

        System.out.println("time:" + (end-start));

    }

}

package com.ceshi.proxy;

public class Client {

public static void main(String[] args) throws Exception {

Tank t = new Tank();

TankTimeProxy time = new TankTimeProxy(t);

time.move();

}

}

```

运行结果为:

```

starttime:1526992632651

Tank Moving...

time:3604

```

通过上述例子,我们看到,静态代理需要手动的取创建,并且在以后我们有可会对tank这个类进行日志管理,权限管理,事务管理等等,总之,在他前后添加代码逻辑,并且实现各种代理顺序的调换,那我们岂不是要写很多代理类去实现那个接口。代码太冗余。

#动态代理

动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能。

接续上面的例子,看看用动态代理怎么实现

```

//声明一个接口

    package com.ceshi.proxy;

    public interface Moveable {

        //提供一个move方法

void move();

}

package com.ceshi.proxy;

import java.util.Random;

//写一个实现类实现这个接口

public class Tank implements Moveable {

@Override

public void move() {

System.out.println("Tank Moving...");

try {

Thread.sleep(new Random().nextInt(10000));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

package com.ceshi.proxy.test;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

//写一个事物操作的Handler实现jdk中的InvocationHandler。

public class TransactionHandler implements InvocationHandler {

private Object target;

//写一个构造方法将被代理的对象传进去

public TransactionHandler(Object target) {

super();

this.target = target;

}

@Override

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

throws Throwable {

System.out.println("Transaction Start");

try {

method.invoke(target);

} catch (Exception e) {

e.printStackTrace();

}

System.out.println("Transaction Commit");

return null;

}

}

package com.ceshi.proxy.test;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

import com.ceshi.proxy.TimeHandler;

public class Client {

public static void main(String[] args) throws Exception {

Tank tank = new Tank();

InvocationHandler h = new TransactionHandler(tank );

Moveable m = (Moveable )Proxy.newProxyInstance(Moveable .class.getClassLoader(),new Class[]{Moveable .class},h);

m.move();

}

}

```

运行结果为:

```

Transaction Start

Tank Moving...

Transaction Commit

```

#总结

通过上面的动态代理实例我们来仔细分析研究一下动态代理的实现过程

(1)首先我要说的就是接口,为什么JDK的动态代理是基本接口实现的呢?

  因为通过使用接口指向实现类的实例的多态实现方式,可以有效的将具体的实现与调用之间解耦,便于后期修改与维护。

再具体的说就是我们在代理类中创建一个私有成员变量(private修饰),使用接口来指向实现类的对象(纯种的多态体现,向上转型的体现),然后在该代理类中的方法中使用这个创建的实例来调用实现类中的相应方法来完成业务逻辑功能。

这么说起来,我之前说的“将具体实现类完全隐藏”就不怎么正确了,可以改成,将具体实现类的细节向调用方完全隐藏(调用方调用的是代理类中的方法,而不是实现类中的方法)。

  这就是面向接口编程,利用java的多态特性,实现程序代码的解耦。

(2)创建代理类的过程

  如果你了解静态代理,那么你会发现动态代理的实现其实与静态代理类似,都需要创建代理类,但是不同之处也很明显,创建方式不同!

  不同之处体现在静态代理我们知根知底,我们知道要对哪个接口、哪个实现类来创建代理类,所以我们在编译前就直接实现与实现类相同的接口,直接在实现的方法中调用实现类中的相应(同名)方法即可;而动态代理不同,我们不知道它什么时候创建,也不知道要创建针对哪个接口、实现类的代理类(因为它是在运行时因需实时创建的)。

  虽然二者创建时机不同,创建方式也不相同,但是原理是相同的,不同之处仅仅是:静态代理可以直接编码创建,而动态代理是利用反射机制来抽象出代理类的创建过程。

  让我们来分析一下之前的代码来验证一下上面的说辞:

    第一点:静态代理需要实现与实现类相同的接口,而动态代理需要实现的是固定的Java提供的内置接口(一种专门提供来创建动态代理的接口)InvocationHandler接口,因为java在接口中提供了一个可以被自动调用的方法invoke,这个之后再说。

    第二点:private Object object;

        public UserProxy(Object obj){this.object = obj;}

  这几行代码与静态代理之中在代理类中定义的接口指向具体实现类的实例的代码异曲同工,通过这个构造器可以创建代理类的实例,创建的同时还能将具体实现类的实例与之绑定(object指的就是实现类的实例,这个实例需要在测试类中创建并作为参数来创建代理类的实例),实现了静态代理类中private Iuser user = new UserImpl();一行代码的作用相近,这里为什么不是相同,而是相近呢,主要就是因为静态代理的那句代码中包含的实现类的实例的创建,而动态代理中实现类的创建需要在测试类中完成,所以此处是相近。

    第三点:invoke(Object proxy, Method method, Object[] args)方法,该方法是InvocationHandler接口中定义的唯一方法,该方法在调用指定的具体方法时会自动调用。其参数为:代理实例、调用的方法、方法的参数列表

  在这个方法中我们定义了几乎和静态代理相同的内容,仅仅是在方法的调用上不同,不同的原因与之前分析的一样(创建时机的不同,创建的方式的不同,即反射),Method类是反射机制中一个重要的类,用于封装方法,该类中有一个方法那就是invoke(Object object,Object...args)方法,其参数分别表示:所调用方法所属的类的对象和方法的参数列表,这里的参数列表正是从测试类中传递到代理类中的invoke方法三个参数中最后一个参数(调用方法的参数列表)中,在传递到method的invoke方法中的第二个参数中的(此处有点啰嗦)。

    第四点:测试类中的异同

  静态代理中我们测试类中直接创建代理类的对象,使用代理类的对象来调用其方法即可,若是别的接口(这里指的是别的调用方)要调用Iuser的方法,也可以使用此法

动态代理中要复杂的多,首先我们要将之前提到的实现类的实例创建(补充完整),然后利用这个实例作为参数,调用代理来的带参构造器来创建“代理类实例对象”,这里加引号的原因是因为它并不是真正的代理类的实例对象,而是创建真正代理类实例的一个参数,这个实现了InvocationHandler接口的类严格意义上来说并不是代理类,我们可以将其看作是创建代理类的必备中间环节,这是一个调用处理器,也就是处理方法调用的一个类,不是真正意义上的代理类,可以这么说:创建一个方法调用处理器实例。

  下面才是真正的代理类实例的创建,之前创建的”代理类实例对象“仅仅是一个参数

    Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);

  这里使用了动态代理所依赖的第二个重要类Proxy,此处使用了其静态方法来创建一个代理实例,其参数分别是:类加载器(可为父类的类加载器)、接口数组、方法调用处理器实例

  这里同样使用了多态,使用接口指向代理类的实例,最后会用该实例来进行具体方法的调用即可。

(3)InvocationHandler

  InvocationHandler是JDK中提供的专门用于实现基于接口的动态代理的接口,主要用于进行方法调用模块,而代理类和实例的生成需要借助Proxy类完成。

  每个代理类的实例的调用处理器都是实现该接口实现的,而且是必备的,即每个动态代理实例的实现都必须拥有实现该接口的调用处理器,也可以这么说,每个动态代理实例都对应一个调用处理器。

  这里要区分两个概念,代理类和代理实例,调用处理器是在创建代理实例的时候才与其关联起来的,所以它与代理实例是一一对应的,而不是代理类。

(4)Proxy

  Proxy类是JDK提供的用于生成动态代理类和其实例的类。

  我们可以通过Proxy中的静态方法getProxyClass来生成代理类,需要的参数为类加载器和接口列表(数组),然后再通过反射调用代理类的构造器来生成代理实例,需要以一个InvocationHandler作为参数(体现出方法调用是与实例相关的,而非类)。

```

    InvocationHandler handler = new MyInvocationHandler(...);

    Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

    Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);

```

  我们也可以直接通过Proxy中的静态方法newProxyInstance方法来直接生产代理实例,需要提供参数为上面的三个参数,即类加载器,接口数组,InvocationHandler。

```  Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);```

(5)、总结

  我们总结下JDK动态代理的实现步骤:

    第一步:创建接口,JDK动态代理基于接口实现,所以接口必不可少(准备工作)

    第二步:实现InvocationHandler接口,重写invoke方法(准备工作)

    第三步:调用Proxy的静态方法newProxyInstance方法生成代理实例(生成实例时需要提供类加载器,我们可以使用接口类的加载器即可)

    第四步:使用新生成的代理实例调用某个方法实现功能。

  我们的动态代理实现过程中根本没有涉及到真实类实例。

#参考文献

https://www.cnblogs.com/V1haoge/p/5860749.html

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

推荐阅读更多精彩内容

  • Java代理相关主要知识如下: (1)利用代理可以在运行时创建一个实现了一组给定接口的新类。 这种功能只有在编译时...
    尚学先生阅读 343评论 0 1
  • https://blog.csdn.net/luanlouis/article/details/24589193 ...
    小陈阿飞阅读 848评论 1 1
  • 一直以来我对于节日的重要性看的并不是很重。但是喜欢吃汤圆,在预料到正月十五是周六的时候,我准备自己买一些糯米面自己...
    奈奈奈米酱阅读 280评论 0 0
  • 芒种,麦子成熟的季节。 2017年6月5日,迎来芒种。 它是夏季的第三个节气,代表仲夏时节的正式开始。 关于芒种 ...
    金勇Maya阅读 463评论 0 2
  • 及至幽然阁,只见采英在后门口走来走去,甚是焦急,见了山鬼忙跑上来说道:“我的小祖宗,怎么才回来呢?再迟一刻,恐就要...
    山鬼琉璃阅读 589评论 0 0