类加载机制及反射

类加载机制及反射

一、java类加载机制

  1、概述

         Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能。

      虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

  2、工作机制

        类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤:

      (1) 加载:查找和导入Class文件

      (2) 链接:把类的二进制数据合并到JRE中

          (a)验证:检查载入Class文件数据的正确性

          (b)准备:给类的静态变量分配存储空间

          (c)解析:将符号引用转成直接引用

      (3) 初始化:对类的静态变量,静态代码块执行初始化操作

    Java程序可以动态扩展是由运行期动态加载和动态链接实现的;比如:如果编写一个使用接口的应用程序,可以等到运行时再指定其实际的实现(多态),解析过程有时候还可以在初始化之后执行;比如:动态绑定(多态)。

  加载:

    (1) 通过一个类的全限定名来获取定义此类的二进制字节流。

    (2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

    (3) 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

  虚拟机规范中并没有准确说明二进制字节流应该从哪里获取以及怎样获取,这里可以通过定义自己的类加载器去控制字节流的获取方式。

  验证

    (1)文件格式验证。

    (2)元数据验证。

    (3)字节码验证。

    (4)符号引用验证。

    虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统奔溃。

  准备:

    准备阶段是正式为类变量分配并设置类变量初始值的阶段,这些内存都将在方法区中进行分配,需要说明的是:这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

    这里所说的初始值“通常情况”是数据类型的零值,假如:public static int value = 123。

    value在准备阶段过后的初始值为0而不是123,而把value赋值的putstatic指令将在初始化阶段才会被执行。

  解析:

    解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

  符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用的目标不一定已经加载到内存中。

  直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用的目标必定已经在内存中存在。

  初始化:

    初始化阶段是执行类构造器<clinit>()方法的过程。

  <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,收集的顺序由语句在源文件中出现的顺序决定。

  3、类加载的时机 

    什么情况下需要开始类加载过程的第一个阶段:加载?java虚拟机规范中并没有强制约束,但是对于初始化阶段,虚拟机规范则严格规定了有且只有5种情况必须立即对类进行"初始化"(而加载、验证、准备阶段自然必须在此之前开始)

    (1) 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

    (2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

    (3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

    (4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

 

    (5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。

  4、类加载器

    (1)启动类加载器(Bootstrap ClassLoader):将存放于<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。

    (2)扩展类加载器(Extension ClassLoader):将<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。

    (3)应用程序类加载器(Application ClassLoader):由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。

  在Java中,任意一个类都需要由加载它的类加载器和这个类本身一同确定其在java虚拟机中的唯一性,即比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类来源于同一个Class类文件,只要加载它的类加载器不相同,那么这两个类必定不相等(这里的相等包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof关键字的结果)。

  类加载器之间的层次关系,称为类加载器的双亲委派模型:

    类加载机制及反射 

    上述三个JDK提供的类加载器虽然是父子类加载器关系,但是没有使用继承,而是使用了组合关系。 

    从JDK1.2开始,java虚拟机规范推荐开发者使用双亲委派模式(ParentsDelegation Model)进行类加载,其加载过程如下:

      (1)如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器去完成。

      (2)每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。

      (3)如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出ClassNotFoundException,而不再调用其子类加载器去进行类加载。

    双亲委派 模式的类加载机制的优点是java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。双亲委派模式的实现:

类加载机制及反射
protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
    //check the class has been loaded or not
    Class c = findLoadedClass(name);
    if(c == null){
        try{
            if(parent != null){
                c = parent.loadClass(name,false);
            }else{
                c = findBootstrapClassOrNull(name);
            }
        }catch(ClassNotFoundException e){
            //if throws the exception ,the father can not complete the load
        }
        if(c == null){
            c = findClass(name);
        }
    }
    if(resolve){
        resolveClass(c);
    }
    return c;
}
类加载机制及反射

 

     在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。双亲委派模型是Java设计者推荐给开发者的类加载器的实现方式,并不是强制规定的。大多数的类加载器都遵循这个模型,但是JDK中也有较大规模破坏双亲模型的情况,例如线程上下文类加载器(Thread Context ClassLoader)的出现。

 

二、反射

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。注意:反射是在运行的时候进行的,不是在编译的时候运行的。

  Java反射机制主要提供了以下功能:

    >在运行时判断任意一个对象所属的类。

    >在运行时构造任意一个类的对象。

    >在运行时判断任意一个类所具有的成员变量和方法。

    >在运行时调用任意一个对象的方法,生成动态代理。

  API为我们提供了反射机制中的类:

    >java.lang.Class:代表一个类

    >java.lang.reflect.Constructor:代表类的构造方法 

    >java.lang.reflect.Field:代表类的成员变量(成员变量也称为类的属性)

    >java.lang.reflect.Method:代表类的方法

    >java.lang.reflect.Array:提供了动态创建数组,以及访问数组的元素的静态方法

  注意:java中无论生成某个类的多少对象, 这些对象都会对应于同一个Class对象。

  1、获取Class对象

  >使用Class类的forName(String className)静态方法。改方法需要传入字符串参数,改字符串参数的值是某个类的全限定类名(必须添加完整的包名)。

  >调用某个类的class属性来获取该类对应的class对象。

  >调用某个对象的getClass方法。

类加载机制及反射
package com.yq.test;

/**
 * Created by yq on 2016/11/26.
 */
public class Animal {
    public int age;
    private int level;
    private static String name = "动物";
    public static final String sex = "雄";

    public Animal() {
        System.out.println("Animal 无参构造器");
    }

    private Animal(int age, int level, String name) {
        this.age = age;
        this.level = level;
        this.name = name;
        System.out.println("Animal 有参构造器");
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public static String getName() {
        return name;
    }

    public static void setName(String name) {
        Animal.name = name;
    }

    public static String getSex() {
        return sex;
    }

    private void eat() {
        System.out.println("Animal 吃东西");
    }
public static void sleep() {
        System.out.println("Animal 睡觉");
    }
}
类加载机制及反射

 

  

类加载机制及反射
package com.yq.test;

/**
 * Created by yq on 2016/11/26.
 */
public class Reflect {
    public static void main(String[] args) {
        Class animalClass = null;
        try {
            animalClass = Class.forName("com.yq.test.Animal");
            System.out.println(animalClass);
            animalClass = Animal.class;
            System.out.println(animalClass);
            Animal animal = new Animal();
            animalClass = animal.getClass();
            System.out.println(animalClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//output
class com.yq.test.Animal
class com.yq.test.Animal
Animal 无参构造器
class com.yq.test.Animal
类加载机制及反射

  2、获取构造方法

    Class类提供了四个public方法,用于获取某个类的构造方法。

  >Constructor getConstructor(Class[] params):根据构造函数的参数,返回一个具体的具有public属性的构造函数。

  >Constructor getConstructors():返回所有具有public属性的构造函数数组。

  >Constructor getDeclaredConstructor(Class[] params):根据构造函数的参数,返回一个具体的构造函数(不分public和非public属性)。

  >Constructor getDeclaredConstructors():返回该类中所有的构造函数数组(不分public和非public属性)。

类加载机制及反射
package com.yq.test;

import java.lang.reflect.Constructor;
import java.util.Arrays;

/**
 * Created by yq on 2016/11/26.
 */
public class Reflect {
    public static void main(String[] args) {
        Class animalClass = null;
        try {
            animalClass = Class.forName("com.yq.test.Animal");
            Constructor[] con1 = animalClass.getConstructors();
            Constructor[] con2 = animalClass.getDeclaredConstructors();
            Constructor con3 = null;
            Constructor con4 = null;
            try {
                con3 = animalClass.getConstructor(int.class, int.class, String.class);
            } catch (Exception e2) {
                System.out.println("=====e2=====: " + e2.toString());
            }
            try {
                con4 = animalClass.getDeclaredConstructor(int.class, int.class, String.class);
            } catch (Exception e3) {
                System.out.println("====e3====: " + e3.toString());
            }
            System.out.println("====con1====: " + Arrays.toString(con1));
            System.out.println("====con2====: " + Arrays.toString(con2));
            System.out.println("====con3====: " + con3);
            System.out.println("====con4====: " + con4);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//output
=====e2=====: java.lang.NoSuchMethodException: com.yq.test.Animal.<init>(int, int, java.lang.String)
====con1====: [public com.yq.test.Animal()]
====con2====: [public com.yq.test.Animal(), private com.yq.test.Animal(int,int,java.lang.String)]
====con3====: null
====con4====: private com.yq.test.Animal(int,int,java.lang.String)
类加载机制及反射

 

  3、获取类的成员方法

    与获取构造方法的方式相同,存在四种获取成员方法的方式。 

  >Method getMethod(String name, Class[] params):根据方法名和参数,返回一个具体的具有public属性的方法。

  >Method[] getMethods():返回所有具有public属性的方法数组。

  >Method getDeclaredMethod(String name, Class[] params):根据方法名和参数,返回一个具体的方法(不分public和非public属性)。

  >Method[] getDeclaredMethods():返回该类中的所有的方法数组(不分public和非public属性)。

类加载机制及反射
package com.yq.test;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * Created by yq on 2016/11/26.
 */
public class Reflect {
    public static void main(String[] args) {
        Class animalClass = null;
        try {
            animalClass = Class.forName("com.yq.test.Animal");
            Method[] con1 = animalClass.getMethods();
            Method[] con2 = animalClass.getDeclaredMethods();
            Method con3 = null;
            Method con4 = null;
            Method con5 = null;
            Method con6 = null;
            try {
                con3 = animalClass.getMethod("eat");
            } catch (Exception e2) {
                System.out.println("=====e2=====: " + e2.toString());
            }
            try {
                con4 = animalClass.getMethod("sleep");
            } catch (Exception e3) {
                System.out.println("=====e3=====: " + e3.toString());
            }
            try {
                con5 = animalClass.getDeclaredMethod("eat");
                con6 = animalClass.getDeclaredMethod("sleep");
            } catch (Exception e4) {
                System.out.println("====e4====: " + e4.toString());
            }
            System.out.println("====con1====: " + Arrays.toString(con1));
            System.out.println("====con2====: " + Arrays.toString(con2));
            System.out.println("====con3====: " + con3);
            System.out.println("====con4====: " + con4);
            System.out.println("====con5====: " + con5);
            System.out.println("====con6====: " + con6);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//output
=====e2=====: java.lang.NoSuchMethodException: com.yq.test.Animal.eat()
====con1====: [public static java.lang.String com.yq.test.Animal.getName(), 
public static void com.yq.test.Animal.setName(java.lang.String),
public static void com.yq.test.Animal.sleep(),
public int com.yq.test.Animal.getAge(),
public void com.yq.test.Animal.setLevel(int),
public void com.yq.test.Animal.setAge(int),
public static java.lang.String com.yq.test.Animal.getSex(),
public int com.yq.test.Animal.getLevel(),
public final void java.lang.Object.wait() throws java.lang.InterruptedException,
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException,
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException,
public boolean java.lang.Object.equals(java.lang.Object),
public java.lang.String java.lang.Object.toString(),
public native int java.lang.Object.hashCode(),
public final native java.lang.Class java.lang.Object.getClass(),
public final native void java.lang.Object.notify(),
public final native void java.lang.Object.notifyAll()] ====con2====: [public static java.lang.String com.yq.test.Animal.getName(),
public static void com.yq.test.Animal.setName(java.lang.String),
public static void com.yq.test.Animal.sleep(),
private void com.yq.test.Animal.eat(),
public int com.yq.test.Animal.getAge(),
public void com.yq.test.Animal.setLevel(int),
public void com.yq.test.Animal.setAge(int),
public static java.lang.String com.yq.test.Animal.getSex(),
public int com.yq.test.Animal.getLevel()] ====con3====: null ====con4====: public static void com.yq.test.Animal.sleep() ====con5====: private void com.yq.test.Animal.eat() ====con6====: public static void com.yq.test.Animal.sleep()
类加载机制及反射

 

  4、获取类的成员变量(成员属性)

    存在四种获取成员属性的方法。

  >Field getField(String name):根据变量名,返回一个具体的具有public属性的成员变量。

  >Field[] getFields():返回具有public属性的成员变量的数组。

  >Field getDeclaredField(String name):根据变量名,返回一个成员变量(不分public和非public属性)。

  >Field[] getDelcaredFields():返回所有成员变量组成的数组(不分public和非public属性)。

类加载机制及反射
package com.yq.test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * Created by yq on 2016/11/26.
 */
public class Reflect {
    public static void main(String[] args) {
        Class animalClass = null;
        try {
            animalClass = Class.forName("com.yq.test.Animal");
            Field[] con1 = animalClass.getFields();
            Field[] con2 = animalClass.getDeclaredFields();
            Field con3 = null;
            Field con4 = null;
            Field con5 = null;
            Field con6 = null;
            Field con7 = null;
            Field con8 = null;
            Field con9 = null;
            Field con10 = null;
            con3 = animalClass.getField("age");
            try {
                con4 = animalClass.getField("level");
            } catch (Exception e2) {
                System.out.println("=====e2=====: " + e2.toString());
            }
            try {
                con5 = animalClass.getField("name");
            } catch (Exception e3) {
                System.out.println("=====e3=====: " + e3.toString());
            }
            try {
                con6 = animalClass.getField("sex");
            } catch (Exception e4) {
                System.out.println("=====e4=====: " + e4.toString());
            }
            try {
                con7 = animalClass.getDeclaredField("age");
                con8 = animalClass.getDeclaredField("level");
                con9 = animalClass.getDeclaredField("name");
                con10 = animalClass.getDeclaredField("sex");
            } catch (Exception e5) {
                System.out.println("====e5====: " + e5.toString());
            }
            System.out.println("====con1====: " + Arrays.toString(con1));
            System.out.println("====con2====: " + Arrays.toString(con2));
            System.out.println("====con3====: " + con3);
            System.out.println("====con4====: " + con4);
            System.out.println("====con5====: " + con5);
            System.out.println("====con6====: " + con6);
            System.out.println("====con7====: " + con7);
            System.out.println("====con8====: " + con8);
            System.out.println("====con9====: " + con9);
            System.out.println("====con10====: " + con10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//output
=====e2=====: java.lang.NoSuchFieldException: level
=====e3=====: java.lang.NoSuchFieldException: name
====con1====: [public int com.yq.test.Animal.age, public static final java.lang.String com.yq.test.Animal.sex]
====con2====: [public int com.yq.test.Animal.age, private int com.yq.test.Animal.level, private static java.lang.String com.yq.test.Animal.name, public static final java.lang.String com.yq.test.Animal.sex]
====con3====: public int com.yq.test.Animal.age
====con4====: null
====con5====: null
====con6====: public static final java.lang.String com.yq.test.Animal.sex
====con7====: public int com.yq.test.Animal.age
====con8====: private int com.yq.test.Animal.level
====con9====: private static java.lang.String com.yq.test.Animal.name
====con10====: public static final java.lang.String com.yq.test.Animal.sex
类加载机制及反射