前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java-Java反射

Java-Java反射

作者头像
小小工匠
发布2021-08-16 16:05:52
3.7K1
发布2021-08-16 16:05:52
举报
文章被收录于专栏:小工匠聊架构

Java反射概述

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

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

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

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

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


示例

Code

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

代码语言:javascript
复制
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);
    }

}

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

代码语言:javascript
复制
    Car car = new Car("BMW","Black",180);
    car.introduceCar();

输出: Car : BMW , Black , 180

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

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

来看代码

代码语言:javascript
复制
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来装载应用程序的类。

验证下:

代码语言:javascript
复制
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());
    }

}

输出:

代码语言:javascript
复制
current loader:sun.misc.Launcher$AppClassLoader@8391b0c
parent laoder:sun.misc.Launcher$ExtClassLoader@5d1eb50b
grandparent laoder:null

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

全盘负责委托机制

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

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

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

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

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

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


重要方法

loadClass(String name)

代码语言:javascript
复制
 public Class loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
代码语言:javascript
复制
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)

代码语言:javascript
复制
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()

代码语言:javascript
复制
  @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的安全机制允许,也可以通过反射调用。比如:

代码语言:javascript
复制
package com.xgj.master.ioc.reflect;

public class PrivateCar {

    private String brand;

    protected void introduce() {
        System.out.println("brand:" + brand);
    }

}
代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017/07/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java反射概述
  • 示例
    • Code
      • 分析
      • 类装载器ClassLoader
        • 工作机制
          • ClassLoader分类
            • 全盘负责委托机制
              • 重要方法
                • loadClass(String name)
                • defineClass(String name, byte[] b, int off, int len)
                • findSystemClass(String name)
                • findLoadedClass(String name)
                • getParent()
              • 总结
              • Java反射机制
                • 三个主要的反射类
                  • Constructor
                  • Method
                  • Field
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档