反射、内省与动态代理

反射:jdk中关于对java.util.reflect的叙述:提供类和接口,以获得关于类和对象的反射信息。在安全限制内,反射允许编程访问关于加载类的字段、方法和构造方法的信息,并允许使用反射字段、方法和构造方法对其底层对等项进行操作。

Class 类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。

Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。

Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。

Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。

示例:


内省:对反射的进一步封装。了解内省要先了解javabean,维基百科中对javabean的定义如下:

在基于Java平台的计算中,JavaBeans是将许多对象封装到单个对象(bean)中的类。它们是可序列化的,具有零参数构造函数,并允许使用getter和setter方法访问属性。“Bean”这个名称是为了包含这个标准,它旨在为Java创建可重用的 软件组件。

说白了,就是实现了序列化,并且每个属性都有相应的getset方法,而且有一个public构造方法。

获取javabean的信息要通过Introspector 类,api中有关Introspector 类的信息如下:

Introspector 类为通过工具学习有关受目标 Java Bean 支持的属性、事件和方法的知识提供了一个标准方法。

对于这三种信息,Introspector 将分别分析 bean 的类和超类,寻找显式或隐式信息,使用这些信息构建一个全面描述目标 bean 的 BeanInfo 对象。

对于每个 "Foo" 类,如果存在相应的 "FooBeanInfo" 类,显式信息可能是可用的,查询这些信息时,FooBeanInfo 类会提供一个非 null 值。通过获得目标 bean 类的完全受限定包名称并追加 "BeanInfo" 形成一个新类,首先查找 BeanInfo 类。如果此操作失败,则采用此名称的最终类名称组件,在 BeanInfo 包搜索路径中指定的每个包中搜索该类。

因此对于某个类,比如说 "sun.xyz.OurButton",首先需要查找称为 "sun.xyz.OurButtonBeanInfo" 的 BeanInfo 类,如果失败,则查找 BeanInfo 搜索路径中的每个包来搜索 OurButtonBeanInfo 类。对于默认搜索路径,这意味着将查找 "sun.beans.infos.OurButtonBeanInfo"。

如果某个类提供有关其自身的显式 BeanInfo,则将它添加到从分析所有派生类得到的 BeanInfo 信息中,并将显式信息视为当前类及其基类的确定的信息,无需进一步深入超类链进行分析。

如果没有在某个类上发现显式 BeanInfo,则使用低层次的反射来研究类的方法,并应用标准设计模式来标识属性存储器、事件源或公共方法。然后深入分析类的超类,从它那里(可能在超类链的顶部)添加信息。

获取到的beaninfo中包含BeanDescriptor(全局信息),MethodDescriptor(方法信息),PropertyDescriptor(属性描述) 

举例


动态代理:利用了java的字节码生成技术,在字节码层面做的反射,类似的还有cglib。api中关于动态代理的描述:

Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

创建某一接口 Foo 的代理:

     InvocationHandler handler = new MyInvocationHandler(...);
     Class proxyClass = Proxy.getProxyClass(
         Foo.class.getClassLoader(), new Class[] { Foo.class });
     Foo f = (Foo) proxyClass.
         getConstructor(new Class[] { InvocationHandler.class }).
         newInstance(new Object[] { handler });
 
或使用以下更简单的方法:
     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                          new Class[] { Foo.class },
                                          handler);
 

动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下面描述的行为。 代理接口 是代理类实现的一个接口。 代理实例 是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。通过其中一个代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的 Invoke 方法,并传递代理实例、识别调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。调用处理程序以适当的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果返回。

代理类具用以下属性:

  • 代理类是公共的、最终的,而不是抽象的。
  • 未指定代理类的非限定名称。但是,以字符串 "$Proxy" 开头的类名空间应该为代理类保留。
  • 代理类扩展 java.lang.reflect.Proxy
  • 代理类会按同一顺序准确地实现其创建时指定的接口。
  • 如果代理类实现了非公共接口,那么它将在与该接口相同的包中定义。否则,代理类的包也是未指定的。注意,包密封将不阻止代理类在运行时在特定包中的成功定义,也不会阻止相同类加载器和带有特定签名的包所定义的类。
  • 由于代理类将实现所有在其创建时指定的接口,所以对其 Class 对象调用 getInterfaces 将返回一个包含相同接口列表的数组(按其创建时指定的顺序),对其 Class 对象调用 getMethods 将返回一个包括这些接口中所有方法的 Method 对象的数组,并且调用 getMethod 将会在代理接口中找到期望的一些方法。
  • 如果 Proxy.isProxyClass 方法传递代理类(由 Proxy.getProxyClass 返回的类,或由 Proxy.newProxyInstance 返回的对象的类),则该方法返回 true,否则返回 false。
  • 代理类的 java.security.ProtectionDomain 与由引导类加载器(如 java.lang.Object)加载的系统类相同,原因是代理类的代码由受信任的系统代码生成。此保护域通常被授予 java.security.AllPermission
  • 每个代理类都有一个可以带一个参数(接口 InvocationHandler 的实现)的公共构造方法,用于设置代理实例的调用处理程序。并非必须使用反射 API 才能访问公共构造方法,通过调用 Proxy.newInstance 方法(将调用 Proxy.getProxyClass 的操作和调用带有调用处理程序的构造方法结合在一起)也可以创建代理实例。

代理实例具有以下属性:

  • 提供代理实例 proxy 和一个由其代理类 Foo 实现的接口,以下表达式将返回 true:
         proxy instanceof Foo
     
    并且以下的强制转换操作将会成功(而不抛出 ClassCastException):
         (Foo) proxy
     
  • 每个代理实例都有一个关联的调用处理程序,它会被传递到其构造方法中。静态 Proxy.getInvocationHandler 方法将返回与作为其参数传递的代理实例相关的调用处理程序。
  • 代理实例上的接口方法调用将按照该方法的文档描述进行编码,并被指派到调用处理程序的 Invoke 方法。
  • 在代理实例上的 java.lang.Object 中声明的 hashCodeequalstoString 方法的调用将按照与编码和指派接口方法调用相同的方式进行编码,并被指派到调用处理程序的 invoke 方法,如上所述。传递到 invokeMethod 对象的声明类是 java.lang.Object。代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。

在多代理接口中重复的方法

当代理类的两个或多个接口包含一个具有相同名称和参数签名的方法时,代理类的接口顺序变得非常重要。在代理实例上调用重复方法 时,传递到调用处理程序的 Method 对象没有必要成为其声明类可以从接口(通过该接口调用代理方法)的引用类型指派的对象。此限制存在的原因是,生成的代理类中的相应方法实现无法确定它通过哪一个接口调用。因此,在代理实例上调用重复方法时,第一个接口中的方法的 Method 对象包含接口的代理类列表中的方法(直接或通过超级接口继承),该对象会传递到调用处理程序的 invoke 方法,无论该方法调用通过哪一种引用类型发生。

如果代理接口包含某一方法,它的名称和参数签名与 java.lang.ObjecthashCodeequalstoString 方法相同,那么在代理实例上调用这样的方法时,传递到调用处理程序的 Method 对象将使 java.lang.Object 成为其声明类。换句话说,java.lang.Object 公共的非最终方法理论上在所有代理接口之前,以便确定哪一个 Method 对象传递到调用处理程序。

还要注意,当重复方法被指派到调用处理程序时,invoke 方法只可以抛出经过检查的异常类型,该异常类型可以使用所有 代理接口(可以通过它调用)中方法的 throws 子句指派一种异常类型。如果 invoke 方法抛出一个经过检查的异常,该异常没有指派给任何由一个代理接口(可以通过它调用)中的方法声明的异常类型,那么该代理实例上的调用将抛出一个未经检查的 UndeclaredThrowableException。此限制表示并非所有的由传递到 invoke 方法的 Method 对象上调用 getExceptionTypes 返回的异常类型都可以由 invoke 方法成功抛出。 

示例:



猜你喜欢

转载自blog.csdn.net/yinweicheng/article/details/80873902