`

JDK的动态代理机制

    博客分类:
  • java
 
阅读更多

dk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。看了一下jdk带的动态代理api,发现没有例子实在是很容易走弯路,所以这里写一个加法器的简单示例。

1
2
3
4
5
6
7
// Adder.java
 
package test;
 
public interface Adder {
    int add(int a, int b);
}
1
2
3
4
5
6
7
8
9
10
// AdderImpl.java
 
package test;
 
public class AdderImpl implements Adder {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}
1
现在我们有一个接口Adder以及一个实现了这个接口的类AdderImpl,写一个Test测试一下。
1
2
3
4
5
6
7
8
9
10
11
// Test.java
 
package test;
 
public class Test {
    public static void main(String[] args) throws Exception {
        Adder calc = new AdderImpl();
        int result = calc.add(1, 2);
        System.out.println("The result is " + result);
    }
}

很显然,控制台会输出:

1
The result is 3

然而现在我们需要在加法器使用之后记录一些信息以便测试,但AdderImpl的源代码不能更改,就像这样:

1
2
Proxy: invoke add() at 2009-12-16 17:18:06
The result is 3

动态代理可以很轻易地解决这个问题。我们只需要写一个自定义的调用处理器(实现接口java.lang.reflect.InvokationHandler),然后使用类java.lang.reflect.Proxy中的静态方法生成Adder的代理类,并把这个代理类当做原先的Adder使用就可以。

第一步:实现InvokationHandler,定义调用方法时应该执行的动作。

自定义一个类MyHandler实现接口java.lang.reflect.InvokationHandler,需要重写的方法只有一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// AdderHandler.java
 
package test;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
class AdderHandler implements InvocationHandler {
    /**
     * @param proxy 接下来Proxy要为你生成的代理类的实例,注意,并不是我们new出来的AdderImpl
     * @param method 调用的方法的Method实例。如果调用了add(),那么就是add()的Method实例
     * @param args 调用方法时传入的参数。如果调用了add(),那么就是传入add()的参数
     * @return 使用代理后将作为调用方法后的返回值。如果调用了add(),那么就是调用add()后的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // ...
    }
}

使用代理后,这个方法将取代指定的所有接口中的所有方法的执行。在本例中,调用adder.add()方法时,实际执行的将是invoke()。所以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这时我们马上会想到这样做:

1
Object returnValue = method.invoke(proxy, args);

如果你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例,实际上就是使用代理之后adder引用所指向的对象。由于我们调用了adder.add(1, 2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy, args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:

1
2
3
4
5
6
// 被代理的对象
private Object target;
 
public AdderHandler(Object target) {
    this.target = target;
}

喜欢的话还可以加上getter/setter。接着,invoke()就可以这么用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// AdderHandler.java
 
package test;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
 
class AdderHandler implements InvocationHandler {
    // 被代理的对象
    private Object target;
 
    public AdderHandler(Object target) {
        this.target = target;
    }
     
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 调用被代理对象的方法并得到返回值
        Object returnValue = method.invoke(target, args);
        // 调用方法前后都可以加入一些其他的逻辑
        System.out.println("Proxy: invoke " + method.getName() + "() at " + new Date().toLocaleString());
        // 可以返回任何想要返回的值
        return returnValue;
    }
}

 

第二步:使用jdk提供的java.lang.reflect.Proxy生成代理对象。

使用newProxyInstance()方法就可以生成一个代理对象。把这个方法的签名拿出来:

1
2
3
4
5
6
7
8
9
10
/**
 * @param loader 类加载器,用于加载生成的代理类。
 * @param interfaces 需要代理的接口。这些接口的所有方法都会被代理。
 * @param h 第一步中我们建立的Handler类的实例。
 * @return 代理对象,实现了所有要代理的接口。
 */
public static Object newProxyInstance(ClassLoader loader,
          Class<?>[] interfaces,
          InvocationHandler h)
throws IllegalArgumentException

这个方法会做这样一件事情,他将把你要代理的全部接口用一个由代码动态生成的类类实现,所有的接口中的方法都重写为调用InvocationHandler.invoke()方法。这个类的代码类似于这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 模拟Proxy生成的代理类,这个类是动态生成的,并没有对应的.java文件。
 
class AdderProxy extends Proxy implements Adder {
    protected AdderProxy(InvocationHandler h) {
        super(h);
    }
 
    @Override
    public int add(int a, int b) {
        try {
            Method m = Adder.class.getMethod("add", new Class[] {int.class, int.class});
            Object[] args = {a, b};
            return (Integer) h.invoke(this, m, args);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

据api说,所有生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,而且Proxy里面也是调用sun.XXX包的api生成;一般情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类加载并实例化一个对象作为代理返回。

看明白这个方法后,我们来改造一下main()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Test.java
 
package test;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
 
public class Test {
    public static void main(String[] args) throws Exception {
        Adder calc = new AdderImpl();
         
        // 类加载器
        ClassLoader loader = Test.class.getClassLoader();
        // 需要代理的接口
        Class[] interfaces = {Adder.class};
        // 方法调用处理器,保存实际的AdderImpl的引用
        InvocationHandler h = new AdderHandler(calc);
        // 为calc加上代理
        calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h);
         
        /* 什么?你说还有别的需求? */
        // 另一个处理器,保存前处理器的引用
        // InvocationHandler h2 = new XXOOHandler(h);
        // 再加代理
        // calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h2);
         
        int result = calc.add(1, 2);
        System.out.println("The result is " + result);
    }
}

输出结果会是什么呢?

1
2
Proxy: invoke add() at 2009-12-16 18:21:33
The result is 3

对比一下之前的结果,你会发现这点东西写了我一个多小时。再来看看JDK有多懒:

target完全可以在代理类中生成。 
实际方法都需要手动调用,可见代理类中重写所有的方法都只有一句话:return xxx.invoke(ooo);

不过这么写也有他的理由,target自己管理,方法你爱调不调 ﹃_﹃;如果他调了,InvocationHandler接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。

分享到:
评论

相关推荐

    java代理机制 JDK动态代理和cglib代理 详解

    java代理机制 JDK动态代理和cglib代理 详解

    JDK动态代理(AOP)使用及原理分析视频教程课件

    动态代理是使用jdk的反射机制,创建对象的能力, 创建的是代理类的对象。 而不用你创建类文件。不用写java文件。 动态:在程序执行时,调用jdk提供的方法才能创建代理类的对象。jdk动态代理,必须有接口,目标类必须...

    jdk机制实现面向切面编程

    通过JDK动态代理机制实现增强,来理解AOP底层的实现

    java设计模式【之】JDK动态代理【源码】【场景:帮爸爸买菜】.rar

    * 动态代理(JDK代理、接口代理、拦截器模式) * 动态代理中的静态方法:java.lang.reflect.Proxy.newProxyInstance (ClassLoader(类加载器),interface(接口),handler(监听处理器)) * * 代码示例:《帮爸爸...

    Jdk动态代理 底层

    java动态代理主要有2种,Jdk动态代理、Cglib动态代理,本文主要讲解Jdk动态代理的使用、运行机制、以及源码分析。当spring没有手动开启Cglib动态代理,即:或@EnableAspectJAutoProxy(proxyTargetClass = true),...

    cgLib与JDK动态代理的用法

    代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等...动态代理:在程序运行时,运用反射机制动态创建而成。

    你必须会的 JDK 动态代理和 CGLIB 动态代理

    我们在阅读一些 Java 框架的源码时,基本上常会看到使用动态代理机制,它可以无感的对既有代码进行方法的增强,使得代码拥有更好的拓展性。 通过从静态代理、JDK 动态代理、CGLIB 动态代理来进行本文的分析。 静态...

    AOP的实现机制

    Java在JDK1.3后引入的动态代理机制,使我们可以在运行期动态的创建代理类。使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类...

    SpringAOP的实现机制(底层原理)、应用场景等详解,模拟过程的实例

    本资源将深入探讨Spring框架中使用的两种关键的动态代理方式:JDK动态代理和CGLib动态代理。这两种代理方式在Spring AOP中起到关键作用,用于实现横切关注点的切面编程。通过学习它们的原理和实际应用,您将能够更好...

    Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

    NULL 博文链接:https://bijian1013.iteye.com/blog/2382393

    jdk动态代理

    剖析java动态代理实现机制,只有明白了原理才会应用。详细请看代码

    三种动态代理的实现

    三种动态代理的实现(静态,jdk,cglib),jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的

    Java动态代理方法详解.docx

    使用jdk的反射机制,创建对象的能力, 创建的是代理类的对象。 而不用你创建类文件。不用写java文件。 动态:在程序执行时,调用jdk提供的方法才能创建代理类的对象。 jdk动态代理,必须有接口,目标类必须实现接口...

    JAVA的反射机制与动态代理.pdf

    本文档先讲解了JDK的反射机制,然后是Proxy的动态代理、CGLIB的动态代理,因为这些是Spring AOP的底层技术,清楚了它们,你就更能够理解Spring AOP是如何工作的。在文档的最后简要写了Spring AOP的使用,因为这不是...

    动态代理.xmind

    该思维导图主要讲解了代理模式的具体实现,包括...其中jdk代理主要讲解了其具体的实现方式、原理、缺点、缓存机制等。Cglib代理主要讲解了其原理、与JDK代理的对比、Enhancer源码解析、methodProxy和Fastclass源码等。

    cglib 和asm jar包

    jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,...

    jdk反射机制资料大合集

    8本书,中英文都有,对反射讲解的很透彻。 希望对大家有些帮助! 具体内容下载看看,就知道了,要是不值这分,以后接不发了,呵呵

    动态代理和AOP详解

    本资源利用文档加源码的方式较详细的介绍了JDK 动态代理和Spring的AOP机制.相信对您理解相关方面的知识有很大作用.

    代理机制及AOP原理实现

    spring中动态代理机制的实现原理及AOP实现原理,JDK的反射,cglib类。

    反射实现 AOP 动态代理模式(Spring AOP 的实现原理)

    AOP的意思就是面向切面编程。本文主要是通过梳理JDK中自带的反射机制,实现 AOP动态代理模式,这也是Spring AOP 的实现原理

Global site tag (gtag.js) - Google Analytics