java反射机制简析

最近在看Spring框架,其中的IOC中最基本技术就是利用java的反射机制。反射机制通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。我们只需要在配置文件中给出定义即可,目的就是提高灵活性和可维护性。

从一个栗子说起

package com.yixingu;

public class LiZi {
    private float price;
    private String variety;

    public float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }
    public String getVariety() {
        return variety;
    }
    public void setVariety(String variety) {
        this.variety = variety;
    }
    public LiZi(float price, String variety) {
        super();
        this.price = price;
        this.variety = variety;
    }

    public LiZi(){}

    public void produce(){
        System.out.println("variety: " + variety + " price: " + price);
    }

}

使用:

package com.yixingu;

public class Main {

    public static void main(String[] args) {
        /*
        //传统实例化
        LiZi lizi = new LiZi();
        lizi.setPrice(30);
        lizi.setVariety("哈哈哈");
        */

        //构造器初始化
        LiZi lizi = new LiZi(30, "哈哈哈");
        lizi.produce();
    }

}

这两种方法都采用传统方式直接调用目标类的方法,下面我们通过反射机制以一种间接的方式操控目标类:

package com.yixingu;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {

    public static void main(String[] args) {
        /*
         * //传统实例化 LiZi lizi = new LiZi(); lizi.setPrice(30);
         * lizi.setVariety("哈哈哈");
         */

        // 构造器初始化
        // LiZi lizi = new LiZi(30, "哈哈哈");

        // 通过类加载器获取LiZi类对象
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        try {
            Class clazz = loader.loadClass("com.yixingu.LiZi");
            // 获取类的默认构造器对象并通过它实例化对象
            Constructor<LiZi> cons = clazz.getDeclaredConstructor((Class[]) null);
            LiZi lizi = (LiZi) cons.newInstance();
            // 通过反射方法设置属性
            Method setPrice = clazz.getMethod("setPrice", float.class);
            setPrice.invoke(lizi, 30);
            Method setVariety = clazz.getMethod("setVariety", String.class);
            setVariety.invoke(lizi, "哈哈哈");
            //对象调用
            lizi.produce();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这与通过直接调用类功能的效果是一致的,只不过前者是间接调用,后者是直接调用的。其中反射中主要用到的是类装载器ClassLoader。

类装载器ClassLoader

类装载器就是寻找类的节码文件并构造出类在JVM内部表示对象的组件。在java中,类装载器把一个类装入JVM中,需要经过以下步骤:
1.装载:查找和导入Class文件。
2.链接:执行校检、准备和解析步骤,其中解析步骤是可选的。
校检:检查载入class文件数据的正确性。
准备:给类的静态变量分配存储空间(只是初始化为默认值)。
解析:将符号引用转换为直接引用。
3.初始化:对类的静态变量、静态代码块执行初始化工作(初始化为正确值)。

初始化的场景

1) 创建类的实例
2) 访问某个类的静态变量,或者对该静态变量赋值(如果访问静态编译时常量(即编译时可以确定值的常量,编译时常量必须满足3个条件:static的,final的,常量。)不会导致类的初始化)
3) 调用类的静态方法
4) 反射(Class.forName(xxx.xxx.xxx))
5) 初始化一个类的子类(相当于对父类的主动使用),不过直接通过子类引用父类元素,不会引起子类的初始化
6) Java虚拟机被标明为启动类的类(包含main方法的)

JVM在运行的时候回产生3个ClassLoader:根装载器、ExtClassLoader(扩展类装载器)和APPClassLoader(应用类装载器)。其中根装载器不是classLoader的子类,它使用c++语言编写,因而在java中看不到它,根装载器负责装载JRE的核心类库,如JRE目标下的rt.jar、charsets.jar等。ExtClassLoader和APPClassLoader都是classLoader的子类,其中ExtClassLoader负责JRE扩展目录ext中的JAR类包,APPClassLoader负责装载classpath路径下的类包。

这三个装载器之间存在父子层级关系,根装载器是ExtClassLoader的父装载器,ExtClassLoader是APPClassLoader的父装载器。

一个栗子:

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        System.out.println("current loader: " + classLoader);
        System.out.println("parent loader: " + classLoader.getParent());
        System.out.println("grandparent loader: " + classLoader.getParent().getParent());

JVM装载类时使用“全盘负责委托机制”。“全盘负责”是指当一个ClassLoader装载类时,除非显示地使用另一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader装入;“委托机制”是指先委托父装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点也是出于安全的考虑,试想,如果有人编写了一个恶意的基础类(java.lang.String)并装载JVM中,这将引起多可怕的后果!但由于有了“全盘负责委托机制”,java.lang.String永远由根装载器来装载的,这样就避免了上述安全隐患的发生。

猜你喜欢

转载自blog.csdn.net/lks1139230294/article/details/74011155