带你掌握框架的灵魂——反射技术

简介: 带你掌握框架的灵魂——反射技术

引言

今天是10月24日,行业内的大牛小白都知道, 这是一个中国广大程序员的共同节日,1024是2的十次方,二进制计数的基本单位之一。程序员就像是一个个1024,以最低调、踏实、核心的功能模块搭建起这个科技世界,在此也向IT行业的前辈们致敬。
这也是我第二次过程序员节,但是学习习惯不能丢,今天起了个早,写一篇博客开启这美好的一天吧。

反射概述

Java反射机制指的是在Java程序运行状态中,对于任何一个类,都可以获得这个类的所有属性和方法;对于给定的一个对象,都能够调用它的任意一个属性和方法。这种动态获取类的内容以及动态调用对象的方法称为反射机制。
Java的反射机制允许编程人员在对类未知的情况下,获取类相关信息的方式变得更加多样灵活,调用类中相应方法,是Java增加其灵活性与动态性的一种机制。
反射能动态编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,在很多框架中被大量使用,所以可以说框架的灵魂即是:反射技术。

这些都是很官方的一些解释,通过概述能够知道反射技术的强大,所以接下来,我们细细品味一下反射的用法。

类的加载过程

当程序要使用某个类的时候,如果该类还没有被加载到内存,则系统会通过加载、连接、初始化三个步骤来实现对这个类的初始化。

  • 加载:指将class文件读入内存,并为之创建一个Class对象,任何类被使用时系统都会为其创建Class对象
  • 连接:连接又分为三个步骤(验证、准备、解析)
               验证:验证是否有正确的内部结构,并和其它类协调一致
               准备:负责为类的静态成员分配内存,并设置默认初始化值
               解析:将类的二进制数据中的符号引用替换为直接引用
  • 初始化:初始化会为所有的静态变量赋予正确的值。注意初始化操作和准备操作的区别,准备操作为静态成员分配内存,设置默认初始化值,而初始化操作是设置正确的值。举个例子:static int num = 1024,这样的一个成员变量在准备阶段会为其赋值为0,只有到初始化阶段才会赋值为1024。

类加载器

了解完类的加载过程之后,我们来看看到底是谁完成了类的加载操作,它就是:类加载器。
类加载器负责将.class文件加载到内存中,并为之生成对应的Class对象,类加载器由以下三大加载器组成:

  1. 根类加载器(Bootstrap ClassLoader):根类加载器也被称为引导类加载器,负责Java核心类的加载,例如System、String类等
  2. 扩展类加载器(Extension ClassLoader):扩展类加载器负责JRE的扩展目录中jar包的加载
  3. 系统类加载器(System ClassLoader):系统类加载器负责在Java虚拟机启动时加载来自Java命令的class文件以及classpath变量所指定的jar包和类路径

获取Class对象

有了理论的知识之后,我们就可以开始实践了,先来看看如何获取类的Class对象(有三种方式)。
首先创建一个基本类用于测试:

package com.wwj.reflect;

public class Programmer {
   
    private String name;
    public int age;
    private String address;

    public Programmer() {
   
    }

    private Programmer(String name, int age) {
   
        this.name = name;
        this.age = age;
    }

    public Programmer(String name, int age, String address) {
   
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getName() {
   
        return name;
    }

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

    public int getAge() {
   
        return age;
    }

    public void setAge(int age) {
   
        this.age = age;
    }

    public String getAddress() {
   
        return address;
    }

    public void setAddress(String address) {
   
        this.address = address;
    }

    public void test() {
   
        System.out.println("test---无参无返回值方法");
    }

    public void test2(String str) {
   
        System.out.println("test2---带参无返回值方法");
    }

    public String test3(String str, int num) {
   
        System.out.println("test3--带参带返回值方法");
        return str + "--" + num;
    }

    private void test4() {
   
        System.out.println("test4---私有方法");
    }

    @Override
    public String toString() {
   
        return "Programmer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

那么第一种获取Class对象的方式就是通过Object类的getClass()方法:

Programmer programmer = new Programmer();
Class pClass = programmer.getClass();

第二种方式就是通过静态属性class:

Class pClass = Programmer.class;

第三种方式通过Class类中的静态方法forName():

Class pClass = Class.forName("com.wwj.reflect.Programmer");

获取构造方法

拿到了Class对象后,我们就可以通过该对象获取类的成员并使用,先来看看如何获取类的构造方法。

    public static void main(String[] args) throws ClassNotFoundException {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor[] constructors = pClass.getConstructors();
        for (Constructor constructor : constructors) {
   
            System.out.println(constructor);
        }
    }

运行结果:

public com.wwj.reflect.Programmer(java.lang.String,int,java.lang.String)
public com.wwj.reflect.Programmer()

控制台只打印了两个构造方法,但很显然,Programmer类中有三个构造方法,其中的私有构造方法获取不到。所以Class类中的getConstructors()只能获取到公共的构造方法,要想获取到所有的构造方法,可以使用getDeclaredConstructors()方法:

    public static void main(String[] args) throws ClassNotFoundException {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor[] constructors = pClass.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
   
            System.out.println(constructor);
        }
    }

运行结果:

public com.wwj.reflect.Programmer(java.lang.String,int,java.lang.String)
private com.wwj.reflect.Programmer(java.lang.String,int)
public com.wwj.reflect.Programmer()

通常情况下,我们并不需要这么多的构造方法,往往我们只需要一个构造方法就行了。

1.获取无参构造方法

先说说如何获取类的无参构造方法。

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        System.out.println(object);
    }

我们可以通过Class类的getConstructor()方法获得单个的构造方法,不传参则代表获取无参构造方法,然后通过返回的构造方法对象调用newInstance()方法即可创建Programmer对象,所以运行结果应为Programmer类的信息。

Programmer{name='null', age=0, address='null'}
2.获取带参构造方法

获取带参构造方法的方式同样是通过getConstructor()方法,只不过需要传入参数,返回的是对应参数的构造方法对象,直接看例子:

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor(String.class,int.class,String.class);
        Object object = constructor.newInstance("张三",18,"杭州");
        System.out.println(object);
    }

运行结果:

Programmer{name='张三', age=18, address='杭州'}
3.获取私有构造方法

前面已经说到,getConstructor()方法无法获取到私有构造方法,所以我们改用getDeclaredConstructors()方法即可,用法和getConstructor()相同。

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor declaredConstructor = pClass.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);//取消访问检查
        Object object = declaredConstructor.newInstance("李四", 20);
        System.out.println(object);
    }

运行结果:

Programmer{name='李四', age=20, address='null'}

需要注意的是,虽然getDeclaredConstructor()方法能够获取到私有构造方法,但由于Java语言的访问检查机制,在创建对象的时候会抛出非法访问异常,所以我们需通过setAccessible()方法取消访问检查,参数为true则为取消,取消了访问检查后才能正常创建对象。

获取成员变量

我们再来看看如何通过Class对象获得类的成员变量。

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Field[] fields = pClass.getFields();
        for (Field field : fields) {
   
            System.out.println(field);
        }
    }

运行结果:

public int com.wwj.reflect.Programmer.age

输出结果不难理解,和获取构造方法一样,getFields()方法无法获取类的私有成员变量,可以通过getDeclaredFields()方法获取:

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Field[] fields = pClass.getDeclaredFields();
        for (Field field : fields) {
   
            System.out.println(field);
        }
    }

运行结果:

private java.lang.String com.wwj.reflect.Programmer.name
public int com.wwj.reflect.Programmer.age
private java.lang.String com.wwj.reflect.Programmer.address
1.获取公共成员变量
    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Field ageField = pClass.getField("age");
        ageField.set(object, 20);
        System.out.println(object);
    }

运行结果:

Programmer{name='null', age=20, address='null'}

通过Class对象的getField()方法能够获取指定属性名的成员变量,但若想对属性进行赋值,则首先需要创建出Programmer对象,然后调用成员变量对象的set()方法,传入要赋值的对象和属性值。这个逻辑其实和正常创建对象赋值是刚好相反的,反射是通过成员变量对象调用方法并将类对象和参数值传入。

2.获取私有成员变量

获取私有成员变量的方式和获取私有构造方法相同,通过getDeclaredField()方法获得成员变量对象,并且在赋值之前需要先取消访问检查,直接看示例:

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Field nameField = pClass.getDeclaredField("name");
        nameField.setAccessible(true);//取消访问检查
        nameField.set(object,"李四");
        System.out.println(object);
    }

运行结果:

Programmer{name='李四', age=0, address='null'}

获取成员方法

获取成员方法的方式和前面相同,通过getMethods()方法可以获取到公共的成员方法,通过getDeclaredMethods()方法可以获取到包括私有的所有成员方法,在此不做重复讲解,接下来说一说如何获取单个成员方法。

1.获取无参无返回值成员方法
    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Method method = pClass.getMethod("test");
        method.invoke(object);
    }

运行结果:

test---无参无返回值方法

同样地,通过getMethod()方法可以获取到对应参数名的成员方法,该方法需要传入两个参数:第一个参数为方法名;第二个参数为方法的参数类型。
这里因为是无参方法,所以无需传入第二个参数,获取到成员方法的对象之后,同样是调用该对象的invoke()方法并将需要执行方法的对象传入才能成功执行方法。

2.获取带参无返回值成员方法

获取带参成员方法就很简单了,在getMethod()方法中传入参数类型即可,直接看示例:

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Method method = pClass.getMethod("test2", String.class);
        method.invoke(object, "王五");
    }

运行结果:

test2---带参无返回值方法
3.获取带参带返回值成员方法

获取带参带返回值成员方法同样十分简单,只不过多了一个返回值处理罢了:

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Method method = pClass.getMethod("test3", String.class, int.class);
        Object obj = method.invoke(object, "赵六", 20);
        System.out.println(obj);
    }

运行结果:

test3--带参带返回值方法
赵六--20
4.获取私有成员方法

获取私有成员方法,即通过getDeclaredMethod()方法获取成员方法对象,并取消访问检查,然后执行方法即可:

    public static void main(String[] args) throws Exception {
   
        Class pClass = Class.forName("com.wwj.reflect.Programmer");
        Constructor constructor = pClass.getConstructor();
        Object object = constructor.newInstance();
        Method method = pClass.getDeclaredMethod("test4");
        method.setAccessible(true);
        method.invoke(object);
    }

运行结果:

test4---私有方法

利用反射无视泛型检查

到这里关于反射的基本知识就介绍完了,接下来我们用泛型来解决一个问题:无视掉Java的泛型检查。
我们看这样的一段代码:

    public static void main(String[] args) throws Exception {
   
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        list.add(1024);
    }

因为list集合的泛型被指定为String类型,所以该集合将只能存储字符串,所以我们在放入1024的时候编译器会报错,那有没有可能通过一些手段将其它类型也能够放入该集合呢?办法是有的,那就是通过反射。
因为Java泛型机制其实只在编译阶段有效,在真正运行的时候是不带泛型的,这种现象叫泛型擦除。这是因为这一特点,我们就能通过反射越过编译期的泛型检查,实现将其它类型的数据存放到指定类型的集合中。

    public static void main(String[] args) throws Exception {
   
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        Class listClass = list.getClass();
        Method addMethod = listClass.getMethod("add", Object.class);
        addMethod.invoke(list, 1024);
        System.out.println(list);
    }

运行结果:

[hello, world, 1024]

这样,int类型数据就成功被存放到了集合中。

动态代理

动态代理是反射技术的高级应用,其目的就是为其它对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
在Java中,JDK为我们提供了Proxy类和InvocationHandler接口,通过这个类和接口就可以生成动态代理对象,但需要注意,JDK提供的代理只能针对接口做代理,如果需要对普通类做代理,我们可以使用cglib。
由于篇幅有限,这里不对动态代理做详细介绍,就通过一个案例让大家先了解一下动态代理。
新建一个接口ICat:

interface ICat{
   
    public void run();
}

新建一个类继承该接口:

public class Cat implements ICat{
   

    @Override
    public void run(){
   
        System.out.println("喵喵~一只猫在奔跑");
    }
}

这是一只会奔跑的猫,通常我们会使用动态代理来增强某个类的方法,例如该类中,我们可以增强run()方法,使其还会抓老鼠:

    public static void main(String[] args) throws Exception {
   
        final ICat cat = new Cat();//原对象
        ICat catProxy = (ICat) Proxy.newProxyInstance(cat.getClass().getClassLoader(), cat.getClass().getInterfaces(), new InvocationHandler() {
   

            public Object invoke(Object proxy, Method method, Object[] objs)
                    throws Throwable {
   
                //增强run方法
                if (method.getName().equals("run")) {
   
                    method.invoke(cat, objs);//调用原对象的方法,保留原方法的功能
                    //新增功能
                    System.out.println("抓住一只老鼠");
                }
                return null;
            }
        });
        catProxy.run();
    }

运行结果:

喵喵~一只猫在奔跑
抓住一只老鼠

主要说一说invoke()方法,该方法有三个参数:

  • proxy:在其调用方法的代理实例
  • method:对应于在代理实例上调用的接口方法的Method实例。Method对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口
  • objs:包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为null。基本类型的参数被包装在适当基本包装器类的实例中

返回值即是代理方法的返回值,因为这里run()方法没有返回值,所以返回null即可,然后调用method对象的invoke()方法,并将需要执行方法的对象和参数值objs传入即可执行原方法的逻辑,这在如何获取成员方法中已经说过,然后我们就可以在下面写上需要添加的功能,这样该方法就比原先的方法功能更加丰富了。

最后

本篇文章总体是偏简单的,适合刚入门的学习者,虽然简单,但也写了挺久,从8点多一直写到11点,目的也是希望大家能够快速掌握反射技术,反射技术在后期的框架学习中是至关重要的,理解反射,对于框架的底层实现你就能够更加了解。

最后祝大家节日快乐!

相关文章
|
3月前
|
数据采集 人工智能 机器人
从手动搜货到 AI 全自动选品|OpenClaw item_search + 多 Agent 落地实战,小白也能搭建无人选品机器人
本文介绍基于OpenClaw淘宝`item_search`接口与AI多智能体协同的自动化选品方案:一句话指令即可完成关键词拓展、全页抓取、智能筛选、详情深挖及Excel铺货报表生成,全程无人值守。附可直接运行的Python源码,适配个人卖家、工作室及ERP开发,大幅提升选品效率与准确性。(239字)
|
Prometheus 监控 异构计算
阿里云容器服务GPU监控2.0基础篇3:监控NVIDIA XID错误
本系列相关文章:阿里云容器服务GPU监控2.0基础篇1:基本功能使用阿里云容器服务GPU监控2.0基础篇2:监控NVLINK带宽阿里云容器服务GPU监控2.0基础篇3:监控NVIDIA XID错误阿里云容器服务GPU监控2.0进阶篇1:剖析(Profiling)GPU使用情况必备知识阿里云容器服务GPU监控2.0进阶篇2:学会剖析(Profiling)GPU使用情况本篇文章将向您介绍如何使用GPU
9263 0
阿里云容器服务GPU监控2.0基础篇3:监控NVIDIA XID错误
|
3月前
|
人工智能 自然语言处理 小程序
2026年企业级智能客服系统建设方案:从需求分析到架构落地的三步策略
2026年企业智能客服已升级为驱动增长的核心中枢。瓴羊Quick Service基于阿里20年服务经验,首创“探需-建模-夯基”三步法:需求深潜分层、意图-数据-动作闭环、可演进中台,支持全渠道一致体验与持续运营,AI准确率达93%,助企业6–8周落地,实现服务向增长引擎转型。(239字)
|
3月前
|
人工智能 运维 API
AI智能体的开发费用
AI智能体开发费用从免费到百万元不等,取决于复杂度、开发模式(低代码/定制/深度私有化)及业务集成深度。成本分一次性开发费与持续云资源/运维费。建议企业先用低代码平台做MVP验证ROI,再逐步投入。
|
9月前
|
缓存 边缘计算 监控
《天梯榜三重防护:数据实时校准与反刷榜技术实践指南》
本文聚焦天梯排行榜的数据实时性、准确性保障及反刷榜技术实践,提出构建“感知-校准-拦截”全链路防护体系。核心围绕多级数据流转与轻量化计算架构实现毫秒级排名更新,通过多源交叉验证和动态阈值校准机制过滤异常数据。同时,创新采用行为画像与意图识别模型精准区分正常竞技与刷榜行为,搭配阶梯式拦截策略遏制不正当操作。针对高并发场景,依托读写分离、边缘计算及资源动态调度平衡性能,结合数据驱动与用户反馈的闭环迭代机制,持续优化技术体系,确保天梯榜公平可靠。
570 1
|
3月前
|
人工智能 缓存 自然语言处理
阿里云TokenPlan是什么?百炼Token Plan按Credits计费、ai模型支持及省钱技巧(配置指南)
阿里云百炼Token Plan团队版是面向企业/团队的AI大模型订阅服务,以Credits统一计费,支持qwen3.6-plus、GLM-5、Qwen-Image等20+文本与图像模型,兼容Hermes Agent、OpenClaw等主流工具。提供标准(198元/月)、高级(698元)、尊享(1398元)三档坐席,额度分别为2.5万–25万Credits/月,多租户隔离、数据不用于训练,华北2(北京)地域可用。快速体验:https://t.aliyun.com/U/fPVHqY
575 0
|
6月前
|
人工智能 Linux API
【保姆级教程】OpenClaw 零基础部署与精选Skills安装指南,含阿里云百炼配置与常见问题解答
2026年开源AI Agent领域的核心工具OpenClaw(曾用名Clawdbot)凭借模块化的Skill扩展生态,实现了从单纯对话到落地执行的能力跃迁,成为众多开发者和办公人士提升效率的重要工具。这款支持MacOS、Linux、Windows11本地部署,也可通过阿里云实现一键部署的AI助手,能通过自然语言指令完成文档处理、自动化工作流、跨平台协作等复杂任务,但新手初次接触时,既会面临部署配置的入门难题,也会因海量Skills陷入选择困境。本文将从零基础部署入手,详解阿里云及多系统本地部署流程、阿里云百炼免费大模型API配置方法,同时梳理核心Skills的安装逻辑与清单,并解答部署和使用中
3186 3
|
9月前
|
传感器 数据采集 监控
[开源代码]基于STM32的环境检测与报警系统
本项目成功实现了基于STM32的环境检测与报警系统,能够同时监测水位、温度、湿度和亮度,并对每项参数独立设置安全范围,实现报警提示。
[开源代码]基于STM32的环境检测与报警系统
|
9月前
|
存储 NoSQL 关系型数据库
MongoDB 数据库与集合操作详解
本文介绍MongoDB中数据库与集合的基本操作。数据按“数据库→集合→文档”层级组织。通过`use`创建或切换数据库,`show dbs`查看列表,`db.dropDatabase()`删除数据库。集合无需显式创建,插入文档时自动生,也可用`createCollection`手动创建。使用`show collections`查看集合,`drop()`删除集合。注意命名规范及保留名如admin、local等。
|
人工智能 Python
【02】做一个精美的打飞机小游戏,python开发小游戏-鹰击长空—优雅草央千澈-持续更新-分享源代码和游戏包供游玩-记录完整开发过程-用做好的素材来完善鹰击长空1.0.1版本
【02】做一个精美的打飞机小游戏,python开发小游戏-鹰击长空—优雅草央千澈-持续更新-分享源代码和游戏包供游玩-记录完整开发过程-用做好的素材来完善鹰击长空1.0.1版本
1171 7

热门文章

最新文章