java反射机制简析

来源:互联网 发布:麻瓜编程第一本python 编辑:程序博客网 时间:2024/06/05 05:46

最近在看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永远由根装载器来装载的,这样就避免了上述安全隐患的发生。

原创粉丝点击