[Java5新特性]动态代理

动态代理概述

代理模式是Java设计模式中的一种,其特征为代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现业务,而是通过调用委托类对象的相关方法来提供具体业务。

在Java中的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和接口可以生成JDK动态代理或动态代理对象。

按照代理的创建时间不同,可以分为两种:

  • 静态代理:手动创建,再对其编译。在程序运行前,代理类的.class文件就已经存在。
  • 动态代理:在程序运行时,通过反射机制动态创建而成。

动态代理原理

动态代理的实现原理有些类似于过滤器的实现原理,但有所不同。动态代理的代理类与委托类之间的关系更像是明星与经纪人之间的关系,也就是说,如果你想找某个明星演出的话,并不是找他本人,而是找到他的经纪人就可以了。动态代理的实现过程很类似于这个过程,具体请看下图:

技术分享

Proxy代理类

Proxy类是Java的java.lang.reflect包下提供的,该类用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态地生成实现类,就可以用Proxy类来创建动态代理类;如果需要为一个或多个接口动态地创建实例,也可以使用Proxy类来创建动态代理实例。

方法摘要
static InvocationHandler getInvocationHandler(Object proxy)
static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)
static boolean isProxyClass(Class<?> cl)
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces):创建一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口。第一个ClassLoader参数指定生成动态代理类的类加载器。
  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke()方法。

InvocationHandler

InvocationHandler接口提供了invoke()方法,用于替换代理对象的每一个方法。真实业务类可以通过代理类对象调用InvocationHandler接口提供的invoke()方法,来替代调用委托类的真实方法。

以下是InvocationHandler的API内容:

方法摘要
Object invoke(Object proxy, Method method, Object[] args)

Object invoke(Object proxy, Method method, Object[] args):在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。

  • 参数proxy:表示代理类对象,也就是Proxy.newProxyInstance()方法返回的对象,通常用不上。
  • 参数method:表示当前被调用方法的反射对象。
  • 参数args:表示调用目标方法时传入的实参。

实现动态代理

利用Java提供的Proxy类和InvocationHandler接口来生成动态代理类或动态代理对象,具体实现步骤如下:

  • 定义一个业务接口,该接口提供具体业务方法的定义。
public interface Person {
    void sayMe();
    void sayHello(String name);
}
  • 定义一个InvocationHandler接口的实现类,并重写invoke()方法。
public class MyInvocationHandler implements InvocationHandler {
    /**
     * 执行动态代理对象的所有方法时,都会被替换成执行下面的invoke()方法.
     *  * 参数proxy:代表动态代理对象.
     *  * 参数method:代表正在执行的方法.
     *  * 参数args:代表调用目标方法时传入的实参.
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("---正在执行的方法: "+method);
        if(args == null){
            System.out.println("当前调用的方法没有参数.");
        }else{
            System.out.println("当前调用的方法需要传入的实参为:");
            for (Object val : args) {
                System.out.println(val);
            }
        }
        return null;
    }
}
  • 编写一个用于测试动态代理的测试类。
public class ProxyTest {
    public static void main(String[] args) {
        // 创建一个InvocationHandler对象
        InvocationHandler handler = new MyInvocationHandler();
        // 通过Proxy类使用指定的InvocationHandler来生成动态代理对象
        Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
        // 调用动态代理对象的业务方法
        p.sayMe();
        p.sayHello("张无忌");
    }
}

动态代理的作用

通过Java提供的Proxy类和InvocationHandler接口生成的动态代理类,可以阻止调用委托类的方法、过滤参数及修改对应方法的返回值等作用。实现业务接口方法的实现类即委托类,具体操作如下:

  • 创建一个实现类,实现Person接口,并重写业务方法。
public class Fanbingbing implements Person {
    @Override
    public void sayMe() {
        System.out.println("我真的是范冰冰哦!");
    }
    @Override
    public String sayHello(String name) {
        System.out.println("你好:"+name+",我等你很久了...");
        return "我终于见到范冰冰啦!";
    }
}
  • 编写一个用于测试动态代理的测试类。
public class FanbingbingTest {
    public static void main(String[] args) {
        Person p = (Person) Proxy.newProxyInstance(
                Person.class.getClassLoader(),
                Fanbingbing.class.getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        // 通过method的getName()方法获取业务方法名,进行阻止.
                        if (method.getName().equals("sayMe")) {
                            System.out.println("你想多了,哪那么容易见到啊!");
                            return null;
                        }
                        // 通过args获取实参,进行修改
                        if(method.getName().equals("sayHello")){
                            String name = (String)args[0];
                            method.invoke(Class.forName("app.java.proxy.Fanbingbing").newInstance(), "某局长");
                        }
                        // 修改返回值
                        if(method.getName().equals("sayHello")){
                            return "都是假的!";
                        }
                        return null;
                    }
                });
        p.sayMe();
        p.sayHello("张无忌");
    }
}

转载说明:请注明作者及原文链接,谢谢!

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。