您的位置:首页 > 编程语言 > Java开发

对Java反射机制的总结

2016-05-19 09:59 162 查看

Java反射原理

   反射机制(Reflection)是 Java 提供的一项较为高级的功能,它提供了一种动态功能,而此功能的体现在于通过反射机制相关的 API 就可以获取任何 Java 类的包括属性、方法、构造器、修饰符等信息。元素不必在 JVM 运行时进行确定,反射可以使得它们在运行时动态地进行创建或调用。反射技术在中间件领域应用得较多。

   Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;其中class代表的时类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。

   每个类都有一个Class对象,程序运行时,java运行时系统会对所有的对象进行运行时类型的处理。这项信息记录了每个对象所属的类,虚拟机通常使用运行时类型信息选择正确的方法来执行。但是这些信息我们怎么得到啊,就要借助于class类对象了啊。在Object类中定义了getClass()方法。我们可以通过这个方法获得指定对象的类对象。然后我们通过分析这个对象就可以得到我们要的信息了。运行时,当我们想生成这个类的对象时,运行这个程序的Java虚拟机(JVM)会确认这个类的Class对象是否已经加载,如果尚未加载,JVM就会根据类名查找.class文件,并将其载入,一旦这个类的Class对象被载入内存,它就被用来创建这个类的所有对象。Class支持反射,java.lang.reflect中包含了Field/Method/Constructor类,每个类都实现了Member接口。这些类型的对象都是由JVM在运行时创建的,用来表示未知类里对应的成员。如可以用Constructor类创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。同时,还可以调用getFields()、getMethods()、getConstructors()等方法来返回表示字段、方法以及构造器的对象数组。这样,未知的对象的类信息在运行时就能被完全确定下来,而在编译时不需要知道任何信息。

    在java虚拟机中有两种类装载器: 启动类装载器 和 自定义类装载器。 前者是jvm的一部分,后者是java程序的一部分。不同的类装载器放在不懂得命名空间中。

类转载子系统涉及java的其它几个部分,及来自lang库的类。比如自定义的类装载器必须派生自java.lang.ClassLoader。 ClassLoader中定义的方法为程序提供了访问类装载器机制的接口。

其实在java内置的类装载器有三种。

1)Bootstrap ClassLoader 此加载器采用c++编写,一般开发中很少见。

2)Extension ClassLoader 用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类

3)AppClassLoader 加载classpath指定的类,是最常用的加载器。同时也是java中默认的加载器。

工作流程

         装载:查找并装载类型的二进制数据。

        链接: 验证 准备 解析

        初始化 : 把类变量初始化为正确的初始值。

    类的ClassLoader 的 protected final Class findSystemClass(String name);接受一个字符串作为参数.

    反射射就是一面镜子,能够在镜子中看到 这个类中的“所有”的东西.有三种可以在程序中得到class对象的方式:

   第一中就是 在编译时不知道其类名但在运行期可以得到该类名  使用class类的forname()静态方法获得class对象  如: class c=class.forname("得到的类名的全名  包括属于的工程,包");

   第二中就是当我们得到该类的一个对象我们就可以直接用该对象的getclass();方法得到给类的class对象如:class c=对象名.getclass();

   第三种就是我们在运行 前就已经知道其类名的  可以直接使用类名.class来得到一个给类的class对象

如:  class c = 类名.class;

   除了系统类,扩展库和classpath的自定义的装载,java还支持动态扩展,包括运行时决定使用的类型,装载,使用它们。通过反射的java.lang.Class 的forName()方法,或者用户自定义的loadClass()方法,都可以自动扩展java程序。

    对于Class。forName() 来讲主要有两种形式:

static Class<?>

forName(String className)

Returns the Class object associated with the class or interface with the given string name.

static Class<?>

forName(String name, boolean initialize, ClassLoader loader)

Returns the Class object associated with the class or interface with the given string name, using the given class loader.

  三参数的解释 如果initalize设为true,类型会在forName()方法返回前连接并初始化;如果是false,类型会被加载,可能会连接但是不会被明确的初始化。如果loader 为null则使用默认的加载器,也可以选用自定义的加载器。

  两个forName()方法都返回Class实例的引用,代表被装载的类型。如果不能装载抛出ClassNotFoundException。

如果使用用户自定义的装载器,那么loadClass()方法就要调用 

Class<?>

loadClass(String name)

Loads the class with the specified  binary name.

protected Class<?>

loadClass(String name, boolean resolve)

Loads the class with the specified  binary name.

这两个方法来装载新的请求的类型,如果找不到,会抛出ClassNotFoundException 异常。

 

Java反射的性能问题

   用于字段和方法接入时反射要远 慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况 下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。

   java反射之所以慢,最主要的就是就是编译器没法对反射相关的代码做优化。稍微了解像gcc这类编译器的人都知道,他们是可以把你又臭又长的代码优化的多么好,java的编译器也是类似 请参考:http://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow。

其实慢的原因还有安全检查,访问控制等。比如说这个方法你能不能获得,能不能执行等,你传进的参数的类型检查等。比如说在使用反射调用方法的时候,传进的参数需要检查是否符合方法参数类型要求,可以通过缓存一些方法来提高速度和性能。

 

Java反射的安全性

  在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,我们可能希望框架能够全面接入代码,无需考虑常规的接入限制。但是,在其它情况下,不受控制的接入会带来严重的安全性风险,例如当代码在不值得信任的代码共享的环境中运行时。

由于这些互相矛盾的需求,Java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施与应用于源代码接入相同的限制:

n 从任意位置到类公共组件的接入

n 类自身外部无任何到私有组件的接入

n 受保护和打包(缺省接入)组件的有限接入

   不过至少有些时候,围绕这些限制还有一种简单的方法。我们可以在我们所写的类中,扩展一个普通的基本类java.lang.reflect.AccessibleObject 类。这个类定义了一种setAccessible方法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否许可了这样做。如果未许可,安全性管理器抛出一个例外。

   下面是一段程序,在TwoString 类的一个实例上使用反射来显示安全性正在运行:

public class ReflectSecurity {

public static void main(String[] args) {

try {

TwoString ts = new TwoString("a", "b");

Field field = clas.getDeclaredField("m_s1");

//field.setAccessible(true);

System.out.println("Retrieved value is " +field.get(inst));

} catch (Exception ex) {

ex.printStackTrace(System.out);

} } }

   如果我们编译这一程序时,不使用任何特定参数直接从命令行运行,它将在field .get(inst)调用中抛出一个IllegalAccessException异常。如果我们不注释field.setAccessible(true)代码行,那么重新编译并重新运行该代码,它将编译成功。最后,如果我们在命令行添加了JVM参数-Djava.security.manager以实现安全性管理器,它仍然将不能通过编译,除非我们定义了ReflectSecurity类的许可权限。

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: