`

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接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。

分享到:
评论

相关推荐

    浅谈JDK动态代理与CGLIB代理去区别

    了解这两种代理机制的差异,并掌握如何在实际项目中灵活运用,能够提升我们的编程能力和解决复杂问题的能力。无论是JDK动态代理还是CGLIB代理,都是Java开发中不可或缺的工具,它们为我们的代码带来了强大的扩展性...

    spring jdk动态代理

    总结来说,Spring AOP中的JDK动态代理机制是一种高效且灵活的实现方式,它允许我们在不修改原始代码的前提下,通过代理对象添加额外的功能。这使得我们能够更专注于业务逻辑,而不是关注点的实现。通过理解和掌握这...

    关于jdk动态代理的源码剖析

    本文将深入分析JDK动态代理的工作原理及其内部实现机制。 #### 二、为什么需要JDK动态代理? 在实际开发中,经常会遇到需要为已有的类添加新功能的需求,但又不能直接修改这些类的源码。此时,动态代理技术就显得...

    Spring框架中JDK动态代理和cglib动态代理

    JDK 动态代理是 Java 自带的动态代理机制,它只能代理接口,而不能代理类。这是因为 JDK 动态代理是基于接口的代理,它需要一个接口来生成代理类。如果我们想使用 JDK 动态代理,必须提供一个接口,并且将其实现类...

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

    Java代理机制为我们提供了在运行时扩展功能的能力,无论是JDK动态代理还是CGLIB代理,都是为了在不修改源代码的前提下,增加新的行为或者监控已有行为。选择哪种代理方式取决于具体需求,如果目标类实现了接口,优先...

    JDK动态代理理解

    JDK动态代理机制是一种在运行时生成代理类的技术,主要用于实现AOP(面向切面编程)和IOC(控制反转)。它允许我们为一个或多个接口创建一个代理类,从而在不修改原有代码的情况下增加新的功能,如日志记录、事务...

    简谈jdk动态代理

    JDK动态代理机制是Java反射机制的一个重要应用,它允许程序在运行时创建一个实现了特定接口的新类实例,并且能够控制这些新类实例的方法调用行为。这种机制不仅提高了代码的灵活性,而且为诸如AOP(面向切面编程)等...

    JDK动态代理简单示例

    总之,JDK动态代理为我们提供了一种灵活的代码扩展机制,使得在不修改原有代码的情况下,可以方便地添加额外的功能或者监控代码行为。在实际项目中,如日志记录、性能监控、事务管理等方面,JDK动态代理都有广泛的...

    JDK动态代理 spring aop 的原理

    首先,JDK动态代理基于Java的反射机制,通过`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口来实现。Proxy类用于创建一个代理对象,而InvocationHandler接口定义了一个方法`invoke()`,该...

    jdk动态代理和CGlib动态代理

    JDK动态代理和CGlib动态代理是Java中实现这一目标的两种主要方式。 ### JDK动态代理 JDK动态代理基于Java的接口实现。如果一个类实现了至少一个接口,我们就可以为这个类创建一个动态代理。动态代理通过`java.lang....

    代理模式,JDK动态代理,SpringAOP来龙去脉

    - 分析JDK动态代理的源码,可以了解其内部如何生成代理类和调用处理程序的工作机制。 - Spring AOP的源码解析有助于理解其是如何创建代理、匹配切点并执行相应通知的过程。 5. **工具应用**: - JDK动态代理和...

    Jdk动态代理和cglib动态代理原理

    - **CGLIB代理**适用于目标类没有接口或者不希望修改原有接口的情况,其性能通常优于JDK代理,因为它是基于字节码生成的子类,而JDK代理需要反射调用接口方法。 在实际开发中,如Spring AOP框架就同时支持JDK和...

    模拟JDK动态代理内部实现

    通过模拟其内部实现,我们可以更深入地理解动态代理的工作原理,并根据需求定制自己的代理机制。通过阅读和分析提供的代码文件(3-1代码、3-2代码、3-3代码),我们可以看到具体的实现细节,如方法的调用链路、参数...

    JDK内置动态代理例子

    在Java开发中,JDK内置的动态代理机制是一种强大的工具,它允许我们在运行时创建具有额外功能的对象。这个“JDK内置动态代理例子”旨在演示如何利用Java的反射API和`java.lang.reflect.Proxy`类来实现类似拦截器的...

    spring_aop之JDK的动态代理

    本文将深入探讨Spring AOP中的JDK动态代理机制。 首先,我们要理解什么是动态代理。动态代理是在运行时创建一个实现了一组给定接口的代理类,这个代理类可以拦截并处理方法调用,从而实现一些额外的功能。JDK的动态...

    CGLIB 和 JDK生成动态代理类的区别

    CGLIB和JDK动态代理是两种常用的实现方式,它们各有优缺点,适用于不同的场景。下面将详细探讨这两种动态代理的区别。 首先,JDK动态代理主要依赖于`java.lang.reflect.Proxy`类和`java.lang.reflect....

    JDK动态代理proxy

    JDK动态代理,全称为Java Dynamic Proxy,是Java标准库提供的一种强大且灵活的机制,允许我们在运行时创建代理类来实现指定的接口。这种机制主要用于实现AOP(面向切面编程)或为已有接口提供额外的功能,如日志、...

    JDK动态代理在EJB3(包括WebService)中的应用

    在EJB3中,虽然RMI不再是最主要的通信机制,但动态代理的概念仍然适用,尤其是在自定义客户端和服务端通信逻辑时。 总结来说,JDK动态代理在EJB3和Web服务中扮演了重要角色,它使得开发者能够灵活地添加额外功能,...

    Java 动态代理详解(代理模式+静态代理+JDK动态代理+CGLIB动态代理)

    JDK 动态代理是 Java 中的一种动态代理方式,使用 Java 反射机制来生成代理类。JDK 动态代理的优点是可以在运行时生成代理类,无需编写代理类代码。 四、 CGLIB 动态代理 CGLIB 动态代理是另外一种动态代理方式,...

Global site tag (gtag.js) - Google Analytics