Java的反射机制 —— 类的镜子

Java的反射机制 —— 类的镜子

一、反射机制的原理

  • 什么是反射
  • 反射机制的实现
  • 获取类的Class对象的途径

1.1、什么是反射?

定义

在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。这种动态获取类的信息以及动态调用对象的方法的功能来自于 Java 语言的反射(Reflection)机制。反射(Reflection)是 Java 语言的特性之一,能够让 Java 程序在运行时动态地执行类的方法、构造方法等。

Java语言

可以说有了反射,不管什么类在我们面前都毫无隐私可言,什么 privateprotecteddefault 修改符都好像失去了作用一样,在反射面前只能俯首称臣,任人宰割。

顺便一提,反射也是Java自省机制的核心。JavaBean 具有的自省机制可以在不知道 JavaBean 都有哪些属性的情况下,设置它们的值,JavaBean 的自省机制主要由 Introspector 实现, 该接口中提供了关键的方法

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

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

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

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

  • 在运行时调用任意一个对象的方法

Reflection 也是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现的interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods

一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,PerlPythonRuby是动态语言,而C++JavaC# 不是动态语言。这时候可能会有人打我,你上面不是说Java是动态语言(或准动态)吗?怎么这里的动态语言定义排除了,别着急,我话还没说完呢

尽管在这样的定义与分类下 Java 不算是动态语言,但是它却有着一个非常突出的动态相关机制:Reflection。这个单词的意思是 “ 反射、映象或倒影 ” ,用在 Java 身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java 程序可以加载一个运行时才得知名称的 class,获悉其完整构造(但不包括 methods 定义),并生成其对象实体、或对其 fields 设值、或唤起其 methods。这种 “ 看透 class ” 的能力(the ability of the program to examine itself)被称为 introspection(内省、内观、反省)。

1.2、反射机制的实现

Java Reflection API 简介

在JDK中,主要由以下类来实现Java的反射机制,这些类都位于java.lang.reflect包中

  • Class类:代表一个类。

  • Field 类:代表类的成员变量(成员变量也称为类的属性)。

  • Method类:代表类的方法。

  • Constructor 类:代表类的构造方法。

  • Array类:提供了动态创建数组,以及访问数组的元素的静态方法

镜像

众所周知Java有个 Object class,是所有 Java classes 的继承根源,其内声明了数个应该在所有 Java class 中被改写的 methods:hashCode()、equals()、clone()、toString()、getClass() 等。其中getClass()返回一个Class object。因此对于任意一个 Java 对象,都可以通过此方法获得对象的类型。Class 类是 Reflection API 中的核心类,它有以下方法

  • getName():获得 class 的全类名。

  • getFields():获得类的 public 类型的属性

  • getDeclaredFields():获得类的所有属性。

  • getMethods():获得类的 public 类型的方法。

  • getDeclaredMethods():获得类的所有方法。

  • getMethod(String name, Class[] parameterTypes):获得类的特定方法,name 参数指定方法的名字,parameterTypes 参数指定方法的参数类型。

  • getConstructors():获得类的public类型的构造方法。

  • getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。

  • newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

package com.bbc.exercise.reflect;


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

/**
 * @author wl
 * @version V1.0
 * @Description 学习Java中的反射
 * @date 2022/7/17 1:48
 */
public class ReflectionStudy {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        // 通过getClass()方法获取Person的类型
        Person person = new Person();
        Class<?> personClass = person.getClass();
        // 1.输出类名,这个类名一般是类的全路径,也就是包含包名在内的
        System.out.println("Person的全类名:");
        System.out.println("personClass.getName() = " + personClass.getName());
        // 2.获取类的公开字段名称
        System.out.println("Person类的公开字段:");
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            System.out.println("field.getName() = " + field.getName());
        }
        // 3.获取类的所有属性
        System.out.println("Person类的所有字段:");
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("declaredField.getName() = " + declaredField.getName());
        }
        // 4.获取类的所有公开的方法
        System.out.println("Person类的公开方法:");
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println("method.getName() = " + method.getName() + "()");
        }
        // 5.获取类的所有公开构造器
        System.out.println("Person类的公开构造器:");
        Constructor<?>[] constructors = personClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("constructor.getName() = " + constructor.getName());
        }
        // 6.通过newInstance()方法构造类的实例
        System.out.println("newInstance()方法构造类的实例:");
        Object instance = personClass.newInstance();
    }
}

/**
 * 人员类
 */
class Person {

    /**
     * 姓名
     */
    public String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 身高
     */
    private Double height;

    /**
     * 体重
     */
    private Double weight;

    public Person() {
        System.out.println("我是人员类的公开构造器方法");
    }

    private Person(String msg) {
        System.out.println(msg + ": 我是人员类的私有构造器方法");
    }

    public String getName() {
        return name;
    }

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

    public void publicMethod() {
        System.out.println("我是人员类的公开方法");
    }

    private void privateMethod() {
        System.out.println("我是人员类的私有方法");
    }
}

输出结果如下图

反射输出结果01

还可以通过默认构造方法创建一个新对象:

Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});

以上代码先调用 Class 类的 getConstructor() 方法获得一个 Constructor 对象,它代表默认的构造方法,然后调用Constructor 对象的 newInstance() 方法构造一个实例

Class lass

Class class十分特殊。它和一般classes一样继承自Object,其实体用以表达 Java 程序运行时的 classes 和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。

当一个class被加载,或当类加载器(ClassLoader)的 defineClass()方法被JVM调用,JVM 便自动产生一个 Class object。如果您想借由 “ 修改Java标准库源码 ” 来观察 Class object 的实际生成时机(例如在 Class 的constructor 内添加一个 println()),不行!因为Class并没有 public constructor

Class 是 Reflection 起源。针对任何您想探勘的 class,唯有先为它产生一个 Class object,接下来才能经由后者唤起为数十多个的Reflection APIs

Notes:

  • 反射机制的底层是基于解析类的字节码文件来实现的,可以获取一个类内部的所有信息,包括私有或公开的属性/方法和构造器。
  • 反射机制的效率较低
  • 有人说,反射机制破坏了Java的封装特性,不应该存在,也有人说,很多框架的底层用的就是反射,比如Spring框架,不应该移除,而应当小心使用。我想说的是,工具没有好坏之分,重要的是在于使用的人

1.3、获取类的Class对象的途径

前言

除了上面提到的通过 Object 类的 getClass() 来获取类的 Class 对象(Class object)之外,Java 还允许我们从其他途径为一个class生成对应的Class object

下面的表格展示了几种获取获取 Class object 的方式

Class object 获取方式示例
Object的getClass()方法,每个类都有此方法String str = "Hello,world"; Class c1 = str.getClass();
Class类的getSuperClass()方法Button button = new Button; Class c1 = button.getClass(); Class c2 = getSuperclass();
Class类的静态方法forName()Class<?> aClass = Class.forName("java.lang.String");
类的.class语法String str = String.class;
基本数据类型的包装类的type语法Class<Integer> type = Integer.TYPE; Class<Void> type = Void.TYPE;

Notes:

  • 所有的类在类加载的过程中,都会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。这个生成的Class对象其实就代表了这个类本身

1.4、运行时动态调用

  • 运行时动态生成instances
  • 运行时动态调用methods
  • 运行时变更fields内容

1.4.1、运行时动态生成instances

前面的篇幅提到了通过反射获取类的 Class 对象的几个途径,接下就是实战了,我们应该如何通过反射来动态生成某个类的实例呢?

动态生成对象实例

如果我们想生成某个对象实体,在 Reflection 动态机制中有两种作法,一个针对 “ 不带自变量(无参)的Constructor ”,一个针对 “ 带参数的Constructor ”。

  1. 不带自变量(无参)的Constructor
package com.bbc.exercise.reflect;


/**
 * @author wl
 * @version V1.0
 * @Description 学习Java中的反射
 * @date 2022/7/17 1:48
 */
public class ReflectionStudy {

    public static void main(String[] args) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException {
        Class<?> aClass = Class.forName("com.bbc.exercise.reflect.Person");
        // 不带参数
        Object obj = aClass.newInstance();
        System.out.println("obj = " + obj);
    }
}

/**
 * 人员类
 */
class Person {

    /**
     * 姓名
     */
    public String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 身高
     */
    private Double height;

    /**
     * 体重
     */
    private Double weight;

    public Person() {
        System.out.println("我是人员类的公开构造器方法");
    }

    private Person(String msg) {
        System.out.println(msg + ": 我是人员类的私有构造器方法");
    }

    public String getName() {
        return name;
    }

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

    public void publicMethod() {
        System.out.println("我是人员类的公开方法");
    }

    private void privateMethod() {
        System.out.println("我是人员类的私有方法");
    }
}

输出结果如下图

反射输出结果02

  1. 带参数的Constructor

如果想调用的是 “ 带参数的 Constructor “ 就比较麻烦些,不能调用 Class 的 newInstance(),而是调用Constructor 的newInstance()方法。首先准备一个Class[]做为ctor的参数类型(本例指定为一个String),然后以此为自变量调用getConstructor(),获得一个专属 Constructor。接下来再准备一个Object[] 对象数组 作为 Constructor 实参值(本例指定"echo:"),调用上述专属 Constructor 的 newInstance()

package com.bbc.exercise.reflect;


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

/**
 * @author wl
 * @version V1.0
 * @Description 学习Java中的反射
 * @date 2022/7/17 1:48
 */
public class ReflectionStudy {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> aClass = Class.forName("com.bbc.exercise.reflect.Person");
        // 带构造器参数
        Class[] classes = {String.class};
        // 指定构造参数List,便可以获取特定的Constructor
        Constructor<?> constructor = aClass.getDeclaredConstructor(classes);
        // 这里不设置true可能会报IllegalAccessException
        constructor.setAccessible(true);
        // 设置构造参数的值
        Object[] params = {"echo:"};
        Object obj = constructor.newInstance(params);
        System.out.println("obj = " + obj);
    }
}

/**
 * 人员类
 */
class Person {

    /**
     * 姓名
     */
    public String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 身高
     */
    private Double height;

    /**
     * 体重
     */
    private Double weight;

    public Person() {
        System.out.println("我是人员类的公开构造器方法");
    }

    private Person(String msg) {
        System.out.println(msg + "我是人员类的私有带参构造器方法");
    }

    public String getName() {
        return name;
    }

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

    public void publicMethod() {
        System.out.println("我是人员类的公开方法");
    }

    private void privateMethod() {
        System.out.println("我是人员类的私有方法");
    }
}

输出结果如下图

反射输出结果03

1.4.2、运行时动态调用methods

前言

这个动作和上述调用 “ 带参数的Constructor ” 类似。首先准备一个 Class[] 类数组 作为 参数类型(本例指定其中一个是 String,另一个是 Map),然后以此为自变量调用 getMethod(),获得特定的 Method object。接下来准备一个 Object[] 放置自变量,然后调用上述所得特定 Method objectinvoke()。为什么获得 Method object 时不需指定回返类型?

因为 method overloading 机制要求 signature 必须唯一,而回返类型并非 signature 的组件。换句话说,只要指定了 method 名称和参数列表,就一定指出了一个独一无二的 method

package com.bbc.exercise.reflect;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author wl
 * @version V1.0
 * @Description 学习Java中的反射
 * @date 2022/7/17 1:48
 */
public class ReflectionStudy {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> aClass = Class.forName("com.bbc.exercise.reflect.Person");
        // 指定调用的方法参数类型
        Class[] classes = new Class[2];
        classes[0] = Class.forName("java.lang.String");
        classes[1] = Class.forName("java.util.Map");
        Method function = aClass.getMethod("function", classes);
        Person person = new Person();
        // 指定调用方法的参数值
        Object[] params = new Object[2];
        params[0] = "Jack say";
        params[1] = new HashMap<String, Object>(2);
        // 调用方法
        Object invoke = function.invoke(person, params);
        String msg = invoke.toString();
        System.out.println("方法的返回值:" + msg);
    }
}

/**
 * 人员类
 */
class Person {

    /**
     * 姓名
     */
    public String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 身高
     */
    private Double height;

    /**
     * 体重
     */
    private Double weight;

    public Person() {
        System.out.println("我是人员类的公开构造器方法");
    }

    private Person(String msg) {
        System.out.println(msg + "我是人员类的私有带参构造器方法");
    }

    public String getName() {
        return name;
    }

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

    public void publicMethod() {
        System.out.println("我是人员类的公开方法");
    }

    private void privateMethod() {
        System.out.println("我是人员类的私有方法");
    }

    public String function(String str, Map<String, Object> map) {
        System.out.println("function invoked");
        return str;
    }
}

输出结果如下图

在这里插入图片描述

1.4.3、运行时变更fields内容

与先前两个动作相比,“ 变更 field 内容 ” 轻松多了,因为它不需要参数和自变量。首先调用 Class 的 getField()并指定 field 名称。获得特定的 Field object 之后便可直接调用 Fieldget()set() 方法

package com.bbc.exercise.reflect;


import java.lang.reflect.Field;
import java.util.Map;

/**
 * @author wl
 * @version V1.0
 * @Description 学习Java中的反射
 * @date 2022/7/17 1:48
 */
public class ReflectionStudy {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class<?> aClass = Class.forName("com.bbc.exercise.reflect.Person");
        // 获取指定的字段
        Field age = aClass.getDeclaredField("age");
        // 不设置可能会报IllegalAccessException
        age.setAccessible(true);
        Person person = new Person();
        System.out.println("age = " + age.get(person));
        age.set(person, 123);
        // 设置person的age字段的值
        System.out.println("age = " + age.get(person));
    }
}

/**
 * 人员类
 */
class Person {

    /**
     * 姓名
     */
    public String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 身高
     */
    private Double height;

    /**
     * 体重
     */
    private Double weight;

    public Person() {
        System.out.println("我是人员类的公开构造器方法");
    }

    private Person(String msg) {
        System.out.println(msg + "我是人员类的私有带参构造器方法");
    }

    public String getName() {
        return name;
    }

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

    private void setAge(Integer age) {
        this.age = age;
    }

    private Integer getAge() {
        return age;
    }

    public void publicMethod() {
        System.out.println("我是人员类的公开方法");
    }

    private void privateMethod() {
        System.out.println("我是人员类的私有方法");
    }

    public String function(String str, Map<String, Object> map) {
        System.out.println("function invoked");
        return str;
    }
}

输出结果如下图

在这里插入图片描述

二、反射机制的应用

  • Spring的AOP

2.1、Spring的AOP

众所周知,AOP的实现有两种方法,一种是静态代理(代码里面写死),另一种是动态代理,后者比前者应用的多,而 JDK 动态代理 其实底层就沿用到了 Java 的反射机制

动态代理

主要有两种:JDK 动态代理CGlib(Code Generation Library:代码生成库),如果目标类为接口,使用JDKProxy 来实现,否则使用 CGlib。

  • JDK动态代理主要通过Java语言内部的反射机制来实现的,它的核心是 InvacationHandler 接口和 Proxy 类,在生成类的过程中比较高效

  • CGlib主要以继承的方式动态生成目标类的代理,借助 ASM 实现,生成类之后执行类的过程中比较高效(但也可以通过将 ASM 生成的类进行缓存,解决 ASM 生成类低效的问题)

Spring里的代理模式实现

在代理模式下, 接口 + 实现类 + 代理类 = AOP实现类和代理类都需要实现接口,在运行时代理类会替换掉实现类的实现以此达到AOP的实现

  • 真实实现类的逻辑包含在了 getBean() 方法里

  • getBean() 返回的实际上是 Proxy 的实例

  • Proxy 实例是 Spring 采用 JDK Proxy 或 CGlib 动态生成

Notes:

  • ASM 是对 Java 的字节码进行操纵的一个优秀的框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
  • Java的类信息被存储在严格格式定义的字节码文件 中,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。 ASM字节码增强技术主要是用来反射的时候提升性能的,如果单纯用 JDK 的反射调用,性能是非常低下的,而使用字节码增强技术后反射调用的时间已经基本可以与直接调用相当了
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值