Java-Java反射

来源:互联网 发布:2016网络暴力案例 编辑:程序博客网 时间:2024/05/16 09:55

  • Java反射概述
  • 示例
    • Code
    • 分析
  • 类装载器ClassLoader
    • 工作机制
    • ClassLoader分类
    • 全盘负责委托机制
    • 重要方法
      • loadClassString name
      • defineClassString name byte b int off int len
      • findSystemClassString name
      • findLoadedClassString name
      • getParent
    • 总结
  • Java反射机制
    • 三个主要的反射类
      • Constructor
      • Method
      • Field

Java反射概述

Java语言允许通过程序化的方式间接对Class进行操作。

Class文件由类装载器装载后,在JVM中形成一份描述Class结构的元信息对象,通过该元对象可以获知Class的结构信息,如构造函数、属性和方法等。

Java允许用户借由这个与Class相关的元信息对象间接调用Class对象的功能, 这就为使用程序化方式操作Class对象开辟了途径。

使用反射不同于常规的Java编程,其中它与 元数据–描述其它数据的数据协作。Java语言反射接入的特殊类型的原数据是JVM中类和对象的描述。

Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)获得任何一个类的字节码。包括接口、变量、方法等信息。还可以让我们在运行期实例化对象,通过调用get/set方法获取变量的值。


示例

Code

我们将用下面这个例子来了解Java反射机制。

package com.xgj.master.ioc.reflect;public class Car {    private String brand ;    private String color;    private int speed;    public String getBrand() {        return brand;    }    public void setBrand(String brand) {        this.brand = brand;    }    public String getColor() {        return color;    }    public void setColor(String color) {        this.color = color;    }    public int getSpeed() {        return speed;    }    public void setSpeed(int speed) {        this.speed = speed;    }    /**     *      * @Title:Car     * @Description:默认构造函数     */    public Car(){    }    /**     *      * @Title:Car     * @Description:带参构造函数     * @param brand     * @param color     * @param speed     */    public Car(String brand ,String color ,int speed){        this.brand = brand;        this.color = color;        this.speed = speed;    }    public void introduceCar(){        System.out.println("Car : " + this.brand + " , " + this.color + " , " + this.speed);    }}

通常情况下,我们实例化类,调用类中的方法如下:

    Car car = new Car("BMW","Black",180);    car.introduceCar();

输出: Car : BMW , Black , 180

这是使用传统的方式来直接调用目标类的方法。

如果使用Java的反射机制 该如何控制目标类呢?

来看代码

package com.xgj.master.ioc.reflect;import java.lang.reflect.Constructor;import java.lang.reflect.Method;public class ReflectTest {    public static Car initCarByDefaultConstrut() throws Exception {        // (1)通过类装载器获取Car类对象        ClassLoader loader = Thread.currentThread().getContextClassLoader();        Class claz = loader.loadClass("com.xgj.master.ioc.reflect.Car");        // (2)获取类的默认构造函数,并通过它实例化Car        Constructor constructor = claz.getDeclaredConstructor(null);        Car car = (Car) constructor.newInstance();        // (3)通过反射方法设置属性        Method method = claz.getMethod("setBrand", String.class);        method.invoke(car, "BMW");        Method method2 = claz.getMethod("setColor", String.class);        method2.invoke(car, "black");        Method method3 = claz.getMethod("setSpeed", int.class);        method3.invoke(car, 100);        return car;    }    public static void main(String[] args) throws Exception {        initCarByDefaultConstrut().introduceCar();    }}

运行结果: Car : BMW , black , 100


分析

我们完全可以通过编程方式调用Class的各项功能,与通过构造函数和方法直接调用类的功能的效果是一致的,只不过是间接调用罢了。

几个重要的反射类

  • ClassLoader
  • Class
  • Constructor
  • Method .
    通过这些反射类我们就可以间接的调用目标Class的各项功能。

在(1)处,我们获取当前线程的ClassLoader, 然后通过指全限定类名com.xgj.master.ioc.reflect.Car 来装载Car类对应的反射实例。

在(2)处,我们通过Car的反射类对象获取Car的默认构造函数对象,通过构造函数对象的newInstance()方法实例化Car对象,等同于 new Car()

在(3)处,我们又通过Car的反射类对象的getMethod(String methodName, Class paramsClass)获取属性的Setter方法对象,其中第一个参数是目标Class的方法名,第二个参数是方法入参的对象类型。

在获取到方法反射对象后,就可以通过invoke(Object ob, Object param)方法调用目标类的方法了。 该方法的第一个禅师是操作的目标类对象实例,第二个参数目标方法的入参。


类装载器ClassLoader

工作机制

类装载器就是寻找类的字节码文件并构造类在JVM内部表示对象的组件。

类装载器把一个类装入JVM中,步骤如下:

  1. 装载:查找和导入Class
  2. 链接:执行校验、准备和解析步骤(解析步骤可选)
  3. 初始化:对类的静态变量、静态代码块执行初始化工作
    其中第二步操作包括:
    (1). 检验:检查载入Class文件数据的正确性
    (2). 准备:给类的静态变量分配存储空间
    (3). 解析:将符号引用转换为直接引用

类装载工作由ClassLoader及其子类负责,负责在运行时查找和装入Class直接码文件。


ClassLoader分类

JVM运行期间会产生3个ClassLoader

  • 根装载器
  • ExtClassLoader(扩展类装载器)
  • AppClassLoader(应用类装载器)

    其中 ExtClassLoader和AppClassLoader 是 ClassLoader的子类
    根装载器不是ClassLoader的子类,它是C++编写。

  • 根装载器负责装载JRE的核心类库,比如JRE目标下的JAR

    这里写图片描述

  • ExtClassLoader负责装载JRE扩展目录ext中的JAR包

这里写图片描述

  • AppClassLoader负责装载应用Classpath路径下的类包

三者关系: 根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。 默认情况下,使用AppClassLoader来装载应用程序的类。

验证下:

package com.xgj.master.ioc.classloader;public class ClassLoaderTest {    public static void main(String[] args) {        // TODO Auto-generated method stub        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();        System.out.println("current loader:" + classLoader);        System.out.println("parent laoder:" + classLoader.getParent());        System.out.println("grandparent laoder:" + classLoader.getParent().getParent());    }}

输出:

current loader:sun.misc.Launcher$AppClassLoader@8391b0cparent laoder:sun.misc.Launcher$ExtClassLoader@5d1eb50bgrandparent laoder:null

根装载器在Java中无法获取到它的句柄,因此返回null .

全盘负责委托机制

JVM装载类时使用“全盘负责委托机制”。

全盘负责:是指当一个ClassLoader装载一个类时,除非显示地使用另外一个ClassLoader,该类所依赖以及引用的类也由这个ClassLoader载入。

委托机制:是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。

这一点是从安全角度考虑,举个例子,比如有人恶意编写了一个基础类如java.lang.String 并装载到JVM中,如果没有委托机制,jvm就会读取了这个恶意基础类,全盘负责委托机制保证了java.lang.String永远由根装载器来装载,避免了安全隐患的发生。

如何查看JVM从哪个JAR包中加载指定类呢?

请看 Java-查看JVM从哪个JAR包中加载指定类


重要方法

loadClass(String name)

 public Class<?> loadClass(String name) throws ClassNotFoundException {        return loadClass(name, false);    }
protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {        synchronized (getClassLoadingLock(name)) {            // First, check if the class has already been loaded            Class c = findLoadedClass(name);            if (c == null) {                long t0 = System.nanoTime();                try {                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // ClassNotFoundException thrown if class not found                    // from the non-null parent class loader                }                if (c == null) {                    // If still not found, then invoke findClass in order                    // to find the class.                    long t1 = System.nanoTime();                    c = findClass(name);                    // this is the defining class loader; record the stats                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);                    sun.misc.PerfCounter.getFindClasses().increment();                }            }            if (resolve) {                resolveClass(c);            }            return c;        }    }

name参数指定类装载器需要装载的类的名字,必须使用全限定类名。

该方法有一个重载方法 loadClass(String name ,boolean resolve) .resolve参数告诉类装载器是否需要解析该类, 如果JVM只需要知道该类是否存在或者找出该类的超类,那么就不需要进行解析。


defineClass(String name, byte[] b, int off, int len)

将类文件的字节数组转换成JVM内部的java.lang.Class对象。 参数name为字节数组对应的全限定类名。


findSystemClass(String name)

protected final Class<?> findSystemClass(String name)        throws ClassNotFoundException    {        ClassLoader system = getSystemClassLoader();        if (system == null) {            if (!checkName(name))                throw new ClassNotFoundException(name);            Class cls = findBootstrapClass(name);            if (cls == null) {                throw new ClassNotFoundException(name);            }            return cls;        }        return system.loadClass(name);    }

从本地文件系统装载Class文件,不存在则抛出ClassNotFoundException。 该方法为AJVM默认使用的装载机制。

findLoadedClass(String name)

调用该方法查看ClassLoader是否已经载入某个类,如果载入,返回java.lang.Class对象,否则返回null.

如果强行装载已经存在的类,将抛出链接错误。


getParent()

  @CallerSensitive    public final ClassLoader getParent() {        if (parent == null)            return null;        SecurityManager sm = System.getSecurityManager();        if (sm != null) {            checkClassLoaderPermission(parent, Reflection.getCallerClass());        }        return parent;    }

获取类装载器的父装载器。 除了根装载器外,所有的类装载器都有且有且只有一个父装载器。 ExtClassLoader的父装载器是根装载器。 因为根装载器非Java语言编写,因此无法获得,返回null.


总结

除了JVM默认的3个ClassLoader外,用户也可以编写自己的第三方类装载器,以实现一些特殊的需求。

类文件被装载并解析后,在JVM内将拥有一个对应的java.lang.Class类描述对象,该类的实例都拥有指向这个类描述对象的引用,而类描述对象又拥有指向关联ClassLoader的引用。

如下图《类实例、类描述对象及装载器的关系》所示

这里写图片描述

每个类在JVM中都有一个对应的java.lang.Class对象。它提供了类结构信息的描述。

Class没有public的构造方法,Class对象是在装载类时由JVM通过调用类装载器中的defineClass()方法自动构造的。


Java反射机制

Class反射对象描述类定义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。

这些反射对象定义在java.lang.reflect包中。

三个主要的反射类

Constructor

类的构造函数反射类。

通过Class#getConstructors()方法可以获取类的所有构造函数反射对象数组。

在Java5.0中,还可以通过getConstructor(Class...parameterTypes)获取拥有特定入参的构造函数反射对象。

Constructor的一个主要方法是newInstance(Object[] initargs),通过该方法可以创建一个对象类的实例。相当于new关键字。

在Java5.0中,该方法演化为更为灵活的形式:newInstance(Object...initargs)


Method

类方法的反射类。

通过Class#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[].

在Java5.0中,可以通过getDeclaredMethod(String name,Class...parameterTypes)获取特定签名的方法。其中name为方法名,Class...为方法入参类型列表。

Method最主要的方法是invoke(Object obj , Object[] args) , 其中obj表示操作的目标对象;args为方法入参。

在Java5.0中,该方法调整为invoke(Object obj, Object...args) .

此外,其他比较常用的方法:

  • Class getReturnType():获取方法的返回值烈性
  • Class[] getParamaterTypes():获取方法的入参类型数组
  • Class[] getExceptionTypes() 获取该方法的异常类型数组
  • Annotation[][] getParameterAnnotations() 获取方法的注解洗洗,是Java5.0中新增的方法。

Field

类的成员变量的反射类。

通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组,
通过Class#getDeclaredField(String name)则可以获取某个特定名称的成员变量反射对象。

Field类的主要方法是set(Object obj , Object value) 其中obj表示操作的目标对象,通过value为目标对象的成员变量设置值。

如果成员变量为基础类型,则可以使用Field类中提供的带类型名的值设置方法,比如setBoolean(Object obj , Object value)、setInt(Object obj , Object value)等。

此外Java还未包提供了Package反射类,在Java5.0中还未注解提供了AnnotatedElement反射类。

对于private或者procted成员变量和方法,只要JVM的安全机制允许,也可以通过反射调用。比如:

package com.xgj.master.ioc.reflect;public class PrivateCar {    private String brand;    protected void introduce() {        System.out.println("brand:" + brand);    }}
package com.xgj.master.ioc.reflect;import java.lang.reflect.Field;import java.lang.reflect.Method;public class PrivateCarTest {    public static void main(String[] args) throws Exception {        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();        Class claz = classLoader.loadClass("com.xgj.master.ioc.reflect.PrivateCar");        PrivateCar pcar = (PrivateCar) claz.newInstance();        Field field = claz.getDeclaredField("brand");        // 取消Java语言访问检查以便访问private变量        field.setAccessible(true);        field.set(pcar, "BMW");        Method method = claz.getDeclaredMethod("introduce", (Class[]) null);        // 取消Java语言访问检查以便访问protected方法        method.setAccessible(true);        method.invoke(pcar, (Object[]) null);    }}

在访问private 或者 protected成员变量和方法时,必须通过setAccessible(boolean access)方法取消Java语言检查,否则将抛出IllegalAccessException. 如果JVM的安全管理器(SecurityManager)设置了相应的安全机制,那么调用该方法会抛出SecurityException