[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("张无忌");
    }
}


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

时间: 2024-10-06 00:40:11

[Java5新特性] 动态代理的相关文章

Java特性-动态代理

代理在开发中无处不在: 我们完成一个接口开发A,接口下有很多个实现类,这些类有些共同要处理的部分,比如每一个类都定义了接口A中的方法getXX(String name).我现在想把每次调用某个实现类的getXX方法时传的参数name记录在数据库某个表里,可问题是,,我们总不能在每个实现类里面去添加一个这样的处理模块吧?工作量太大了,把该处理逻辑写到一个static的工具类里面,然后每个实现类再去调用也挺麻烦.况且这个处理是给改接口专门使用的,放在工具类里也不合适啊.. 好办,我再写一个实现接口A

Java5新特性之静态导入、可变参数、增强for循环、自动拆装箱

JDK1.5已经发布很长时间,之所以还拿出来是因为它增加了很多个重要的特性,使用这些特性有助于我们简化开发,编写的代码更加简洁清晰安全,主要有以下几个特性: ?  静态导入 ?  可变参数 ?  增强for循环 ?  自动拆装箱 ? 泛型 ? 枚举 由于泛型.枚举内容比较多,也最重要,之后单拿出来讲.这里先介绍前面四个简单而又实用的小特性. 1. 静态导入 所谓"静态导入"只不过是在普通的import语句中加入关键字static,例如: ?  非静态导入:import java.lan

Java5新特性

"JDK1.5/Java5"的一个重要主题就是通过新增一些特性来简化开发. 这些特性包括泛型,for-each循环,自动装包/拆包,枚举,可变参数, 静态导入,注解. 使用这些特性有助于我们编写更加清晰,精悍,安全的代码. 1.泛型(Generic)C++通过模板技术可以指定集合的元素类型,而Java在1.5之前一直没有相对应的功能.一个集合可以放任何类型的对象,相应地从集合里面拿对象的时候我们也不得不对他们进行强制得类型转换.猛虎引入了泛型,它允许指定集合里元素的类型,这样你可以得到

java5 新特性

1.静态导入方法 Java代码   package com.java.new_features_jdk5; /** * * 一般我们导入一个类都用 import com.....ClassName;而静态导入是这样:import static com.....ClassName.*; * 这里的多了个static,还有就是类名ClassName后面多了个 .* ,意思是导入这个类里的静态方法.当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了. * 然后在这个类中,就可以直接用方

[Java5新特性]自动装箱/拆箱

自动装箱/拆箱概述 Java中具有基本类型(int,double,float,long,boolean,char,byte,short)和基本类型包装类(Integer,Double,Float,Long,Boolean,Char,Byte,Short),我们实现基本类型与包装类之间的转换基本有两种方式: 一种为JDK5之前的方式,比如Integer i = Integer.valueof(5);(这里5为基本类型int,Integer包装类利用valueof()方法将其转换为Integer类型

[Java5新特性]可变参数

什么是可变参数 Java基础内容中,关于函数具有一种特性:重载,如果我们要完成多个数字想加的需求,可以按照以下代码完成: public class Demo { public int add(int a, int b) { return a + b; } public int add(int a, int b, int c) { return a + b + c; } public static void main(String[] args) { int sum1 = new Demo().a

[Java5新特性]Annotation注解

Annotation概述 Annotation是JDK 5.0以后提供对元数据的支持,可以在编译.加载和运行时被读取,并执行相应的处理.所谓Annotation就是提供了一种为程序元素设置元数据的方法,可用于修饰包.类.构造器.方法.成员变量.参数和局部变量的声明,这些信息被存储在Annotation的"name=value"对中. Annotation能被用来为程序元素(类.方法.成员变量等)设置元数据,比如一段代码的作者或者告诉编译器禁止一些特殊的错误,不会影响代码的执行. 基本A

[Java5新特性]类加载器

类加载器概述 类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class实例.一旦一个类被加入JVM中,同一个类就不会被再次加入了.正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识.在Java中,一个类用其全限定类名(包括包名和类名)作为标识:但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识. 例如以下案例,在JVM中两个同名的Person类是完全不同的,之间也互不兼容,因为类加载器不同. 上述情况转换成代码如下: 定义一个Per

[Java5新特性]加强For循环

替换迭代器 我们先来回忆一下Java中的迭代器的用法,可以使用迭代器的有List和Set集合.原因在于它们都实现了Collection接口,而Collection接口拥有一个叫做Iterable父接口.下面我们来看一个案例: public class Demo { @Test public void demo() { List<String> list = new ArrayList<String>(); list.add("hello"); list.add(