JAVA

Java安全-Java反射及类加载

转载:Java安全-Java反射及类加载

一、反射介绍


计算器案例

首先先来看案例,基于“正射”调用计算器:

public class Test {
    public static void main(String[] args) throws IOException {
        java.lang.Runtime.getRuntime().exec("calc.exe");
    }
}

基于反射调用如下:

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//        java.lang.Runtime.getRuntime().exec("calc.exe");
        Class<?> aClass = Class.forName("java.lang.Runtime");
        Method exec = aClass.getMethod("exec", String.class);
        Method getRuntime = aClass.getMethod("getRuntime");
        Object invoke = getRuntime.invoke(null);
        exec.invoke(invoke,"calc.exe");
    }
}
  • 1.首先利用Class.forName动态加载java.lang.Runtime
  • 2.获取getRuntime、exec的方法对象
  • 3.获取静态的runtime实例,不需要传递参数所以传递null值。
  • 4.获取exec实例,其中传递的runtime的对象实例和需执行的命令即可。

 

二、java反射概念


(1)反射概念

首先,Java是静态语言;为了实现一定的动态性,我们可以通过反射机制获得类似动态语言的特性,Java的动态性让编程变得更加灵活。

反射可以用来编写能够动态操纵Java代码的程序。使用反射,Java可以支持用户界面生成器、对象关系映射器以及很多其他需要动态查询类能力的开发工具。Java的反射是指程序在运行期可以拿到一个对象的所有信息,并能直接操作任意对象内部属性及方法。

加载完类之后,在堆内存的方法区中就生成了一个Class类类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。 这个对象就类似一个镜子,透过镜子可以看到类结构,所以我们称之为反射。

各类框架底层,都需要用到反射。

正常引用:引入包路径 ➡  将对象实例化 ➡ 操作实例化对象

 

(2)反射机制作用

  • 1.在运行时动态获取对象的信息(对象的属性和方法)。
  • 2.动态调用对象的属性和方法。
  • 3.在运行时获取泛型信息

 

(3)反射原理图

Java程序在计算机中有三个阶段:

  • 1.源代码阶段:指.java文件,也就是源码;存储在硬盘中;
  • 2.编译阶段:使用javac对.java文件进行编译,生成.class字节码文件;同样存储在硬盘中;
  • 3.Class类对象阶段:基于类加载器将.class加载到内存中 ,这里加载的是一个类对象。
  • 4.Runtime运行时阶段:从内存中取到需要用的.class字节码文件,使用java解释器将字节码文件解释为机器码,执行并返回结果。

类加载过程:

  • 1.通过类的全类名获得二进制字节流
  • 2.将获得得字节流所代表得静态存储结构转换成方法区运行时的数据结构
  • 3.并在堆内存中创建一个java.lang.Class对象 ,作为方法区中数据结构得访问入口。

 

(4)类加载机制

类加载过程

类的加载过程为三步,分别是:类的加载、链接、初始化。

1.类的加载(Loading)

由类加载器ClassLoader将类的class字节码文件读入JVM内存中,并创建其Class对象。

可以从以下来源加载类的字节码文件: 1.从本地文件系统加载class文件。 2.从JAR包、WAR包加载class文件 3.通过网络加载class文件

 

2.类的链接(Linking)

当加载完类后,系统会生成一个对应的Class文件。生成后,会进入链接阶段;将二进制数据合并到JRE中。

类链接三个阶段:

1.验证:校验加载的类是否结构正确;

2.准备:为类变量分配内存空间,并设置初始值;

3.解析:将类的二进制数据中的符号引用替换成直接引用;

 

3.类的初始化(Initialization)

JVM负责对类进行初始化。初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码,初始化是为类的静态变量赋予正确的初始值。

JVM初始化一个类包含如下几个步骤 1,如果还没加载该类,则程序先加载并连接该类 2,如果该类的直接父类还没有被初始化,则先初始化其直接父类 3,如果该类中有初始化语句,则系统依次执行这些初始化语句

 

双亲委派机制

Java中提供下四种类型的加载器,每一种加载器都有指定的加载对象,具体如下:

Bootstrap ClassLoader(启动类加载器) :主要负责加载Java核心类库,%JRE_HOME%\lib下的.jar包和class文件。

Extention ClassLoader(扩展类加载器):主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。

ApplicationClassLoader(应用程序类加载器) :主要负责加载当前应用的classpath下的所有类

User ClassLoader(用户自定义类加载器) : 用户自定义的类加载器,可加载指定路径的class文件

加载流程:从下往上委派。(流程表只涉及到类加载委派流程。)

双亲委派机制作用:

1.通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。

2.通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer。这个类是不会被随意替换的,除非有人破坏本机的JDK。这样就可以避免有人自定义一个有缺陷的java.lang.Integer被加载。可以有效的防止核心Java API被篡改。

 

ClassLoader类介绍

在java运行过程中,并不会一次性加载所有class文件进入内存。 而是通过Java的类加载机制(ClassLoader)进行动态地加载相关地类,从而转换成Class类的一个实例。

classloader类是一个抽象类,主要的功能是通过指定地类的名称,找到或生成对应地字节码文件。

ClassLoader类中和加载类相关方法

方法说明
getParent()返回该类加载器地父类加载器
loadClass(String name)加载名称为name的类,返回的结果为java.lang.Class类的实例
findClass(String name)查找名称为name的类,返回的结果是java.lang.Class类的实例
findLoadedClass(String name)查找名称为name的已被加载过的类。返回结果是java.lang.Class类的实例
resolveClass(Class<?> c)链接指定为Java类

  

loadClass()类加载流程

loadClass()加载类后,会返回一个java.lang.Class类实例。以下为loadClass()方法源码,在调用加载类时,会先基于findLoadedClass()方法判断是否已被加载。若未加载则会去调用父类加载器去进行加载,若没有则委托给BootstrapClassLoader进行加载 。若也不存在,则自己进行调用findClass()进行加载 。 也就是可以对findClass()方法重写 ,来自定义类加载流程。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
        // 1、查看该类是否加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 2、如果未加载,委托给父类加载器加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    //3、没有父类加载器,委托给BootstrapClassLoader
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }

            if (c == null) {
                // 父类加载器没有加载到,则自己加载
                long t1 = System.nanoTime();
                c = findClass(name);
                // 记录该类加载的状态Stat. 
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        // resolve :true,需要对类进行链接(链接阶段包括:准备,解析,初始化类)
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

 

loadClass()方法与Class.forName()的区别

loadClass()方法只对类进行加载,不会对类进行初始化。Class.forName()会默认对类进行初始化,在进行初始化时,静态代码块中的代码会进行执行 。

 

(5)反射关键类介绍

含义作用
java.lang.Class代表整个字节码。代表一个类型,代表整个类反射的核心类,可以用来获取类的属性、方法等信息。
java.lang.reflect.Method代表字节码中的方法字节码。代表类中的方法。可以用来获取类中方法信息或执行方法。
java.lang.reflect.Constructor代表字节码中的构造方法字节码。代表类中的构造方法。可以用来获取类中的构造方法。
java.lang.reflect.Field代表字节码中的属性字节码。代表类中的成员变量。(静态变量+实例变量)可以用来获取和设置对象的属性值。

 

Class类介绍


CLass本身就是一个类,只是在定义类名时,比较抽象。它可以理解为 对象在照镜子后在镜子中得到的信息:类中的属性、方法和构造器。 也就是,通过Class可以完整地得到一个类中的所有被加载的结构;为Reflection的根源,针对任何想要去动态加载运行时的类,只有先获得相应的Class对象。

 

(1)获取类对象Class

首先,程序执行javac.exe命令后,会生成一个或多个字节码文件(文件类型为.class)。

然后程序执行java.exe命令对某个class文件进行解析时,相当于讲某个字节码文件加载到内存中,此过程我们称之为类的加载过程,加载到内存中的类我们称之为运行时类,此运行时类就作为Class的一个实例。

 

获取类对象Class实例的4种方式

  • 1.使用forName()方法,只需要类名称即可,使用较为方便,拓展性更强。
  • 2.直接获取,任何类都具备静态的属性,可以使用.class,直接获取其对应的Class对象。
  • 3.使用getClass()方法 。可以通过Object类中的getClass()方法来获取字节码对象。 需要先实例化对象。
  • 4.使用类加载器:getSystemClassLoader().loadClass()方法

类加载器和forName()类似,只要有类名即可 。但是与class.forName()中有一些区别,forName的静态方法JVM会装载类,且会执行static代码块中的代码 ; 而getSystemClassLoader().loadClass()不会执行static代码。

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println(Class.forName("Reflection.domain.person")); //class.forname调用
        System.out.println(person.class); //直接调用获取
        System.out.println(new person().getClass()); //使用getClass获取
        System.out.println(ClassLoader.getSystemClassLoader().loadClass("Reflection.domain.person"));
    }
}

执行结果如下,Class.forName直接执行了静态方法,其他则是输出了类路径:

 

(2)获取方法Method

获取方法类

1.getDeclaredMethods()

getDeclaredMethods方法返回类或接口声明的所有方法 ,不包括继承方法。

2.getMethods()

getMethods方法,返回某个类的所有public方法,包括其继承类的public方法。

3.getMethod()

getMethod方法只能返回一个特定的方法。返回的第一个参数为方法名称,后面的参数为方法的参数对应的Class对象 。

4.getDeclaredMethod()

getDeclaredMethod方法和getMethod类似,都是只去返回一个特点的方法。

获取方法流程

得到返回类型 ➡得到方法名称➡得到参数类型➡遍历输出

利用getDeclaredMethods()获取遍历如下,可直接获取所有方法:

public static void main(String[] args) throws Exception {
        Class<?> aClass = Class.forName("Reflection.domain.person");//class.forname调用
        Method[] methods = aClass.getDeclaredMethods();
        for (Method m:methods) {
            System.out.println(m);
        }

 

(3)获取构造器Constructor

获取构造器步骤

1、获取字节码信息

2、工作字节获取构造器

3、循环遍历
 public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> aClass = Class.forName("Reflection.domain.person");//class.forname调用
        Constructor<?>[] constructors = aClass.getConstructors();
        for (Constructor m:constructors) {
            System.out.println(m);
        }

 

(4)获取类成员变量 Fileld

获取类属性方法:

    getDeclaredFields() :能获得类的成员变量数组,包括public、private、和proteced 。但不包括父类声明的字段。

    getDeclaredField():只能获得类的单个成员变量 。

    getFields():能够获得某个类的所有的public字段,包括父类中的字段。

    getField():只能获得某个类其中一个的public字段 。

获取类属性步骤

获取字节码信息➡获取类属性➡循环遍历
public static void main(String[] args) throws Exception {
        Class<?> aClass = Class.forName("Reflection.domain.person");//class.forname调用
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field m:declaredFields ) {
            System.out.println(m);
        }
    }