温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

java 代理模式及动态代理机制深入分析

发布时间:2020-10-20 23:18:45 来源:脚本之家 阅读:150 作者:lqh 栏目:编程语言

java 代理模式及动态代理机制深入分析

代理设计模式

       代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式一般涉及到的角色有:

抽象角色:声明真实对象和代理对象的共同接口;
代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便

在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象

图 1. 代理模式类图

java 代理模式及动态代理机制深入分析

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托 类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

java动态代理

相关的类和接口

要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:
· java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

清单 1. Proxy 的静态方法 

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器 
 
static InvocationHandler getInvocationHandler(Object proxy)  
 
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 
 
static Class getProxyClass(ClassLoader loader, Class[] interfaces)  
 
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类 
 
static boolean isProxyClass(Class cl)  
 
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 
 
static Object newProxyInstance(ClassLoader loader, Class[] interfaces,  
 
  InvocationHandler h)  

java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

清单 2. InvocationHandler 的核心方法

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象 
 
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行 
 
Object invoke(Object proxy, Method method, Object[] args)  

每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。
· java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何个 .class 文件中。

每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数)

代理机制及其特点

首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:

1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

清单 3. 动态代理对象创建过程

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 
 
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用 
 
InvocationHandler handler = new InvocationHandlerImpl(..);  
 
 
// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象 
 
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });  
 
 
// 通过反射从生成的类对象获得构造函数对象 
 
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });  
 
 
// 通过构造函数对象创建动态代理类实例 
 
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });  

实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如

清单 4. 简化的动态代理对象创建过程

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 
 
InvocationHandler handler = new InvocationHandlerImpl(..);  
 
// 通过 Proxy 直接创建动态代理类实例 
 
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,  
 
 new Class[] { Interface.class },  
 
 handler );  

下面我们来看一个简单实现动态代理的例子:

1.代理类和真实类接口:

public interface Subject 
 
{ 
 
public void request(); 
 
} 

2.真实类:

public class RealSubject implements Subject 
 
{ 
 
public void request() 
 
{ 
 
System.out.println("From real subject!"); 
 
}} 

3.具体代理类:

import java.lang.reflect.InvocationHandler; 
 
import java.lang.reflect.Method; 
 
public class DynamicSubject implements InvocationHandler 
 
{ 
 
private Object sub; 
 
public DynamicSubject(Object obj) 
 
{ 
 
this.sub = obj; 
 
} 
 
public Object invoke(Object proxy, Method method, Object[] args) 
 
throws Throwable 
 
{ 
 
System.out.println("before calling: " + method); 
 
method.invoke(sub, args);  
 
System.out.println(args == null);  
 
System.out.println("after calling: " + method); 
 
return null; 
 
} 

注:该代理类的内部属性是Object类型,实际使用的时候通过该类的构造方法传递进来一个对象。 此外,该类还实现了invoke方法,该方法中的method.invoke其实就是调用被代理对象的将要 执行的方法,方法参数是sub,表示该方法从属于sub,通过动态代理类,我们可以在执行真实对象的方法前后加入自己的一些额外方法。

4.客户端调用示例:

import java.lang.reflect.InvocationHandler; 
 
import java.lang.reflect.Proxy; 
 
public class Client 
 
{ 
 
public static void main(String[] args) 
 
{ 
 
RealSubject realSubject = new RealSubject(); 
 
InvocationHandler handler = new DynamicSubject(realSubject); 
 
Class<?> classType = handler.getClass(); 
 
// 下面的代码一次性生成代理 
 
Subject subject = (Subject) Proxy.newProxyInstance(classType 
 
.getClassLoader(), realSubject.getClass().getInterfaces(), 
 
handler); 
 
subject.request(); 
 
System.out.println(subject.getClass()); 
 
} 
 
} 

接下来让我们来了解一下 Java 动态代理机制 Proxy 的构造方法:

清单 6. Proxy 构造方法

// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用 
 
private Proxy() {}  
 
 
// 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用 
 
protected Proxy(InvocationHandler h) {this.h = h;}  

接着,可以快速浏览一下 newProxyInstance 方法,因为其相当简单:

清单 7. Proxy 静态方法 newProxyInstance

public static Object newProxyInstance(ClassLoader loader,  
 
      Class<?>[] interfaces,  
 
      InvocationHandler h)  
 
      throws IllegalArgumentException {  
 
   
 
  // 检查 h 不为空,否则抛异常 
 
  if (h == null) {  
 
    throw new NullPointerException();  
 
  }  
 
  // 获得与制定类装载器和一组接口相关的代理类类型对象 
 
  Class cl = getProxyClass(loader, interfaces);  
 
 
  // 通过反射获取构造函数对象并生成代理类实例 
 
  try {  
 
    Constructor cons = cl.getConstructor(constructorParams);  
 
    return (Object) cons.newInstance(new Object[] { h });  
 
  } catch (NoSuchMethodException e) { throw new InternalError(e.toString());  
 
  } catch (IllegalAccessException e) { throw new InternalError(e.toString());  
 
  } catch (InstantiationException e) { throw new InternalError(e.toString());  
 
  } catch (InvocationTargetException e) { throw new InternalError(e.toString());  
 
  }  
} 

     由此可见,动态代理真正的关键是在 getProxyClass 方法,该方法负责为一组接口动态地生成代理类类型对象。

     有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此 外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。
但是,不完美并不等于不伟大,伟大是一种本质,Java 动态代理就是佐例。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI