16-Java反射机制:Spring IOC背后的核心技术

Java反射机制:Spring IOC背后的核心技术

开篇:为什么框架能自动创建对象?

你有没有想过:Spring里你只写了一个@Service注解,Spring就能自动创建对象、注入依赖、调用方法。它是怎么做到的?

答案就是反射。反射是Java的"自省"能力,让程序在运行时能查看和操作自己的结构。

本文不讲高深理论,只讲反射的核心用法和实际应用场景。

一、什么是反射

反射(Reflection)是指在运行状态中,程序可以获取任意一个类的内部信息,并且可以操作它的属性、方法、构造器。

正常情况下,我们写代码是"知道类名,直接new对象"。反射则是"只知道类名字符串,也能创建对象并调用方法"。

java 复制代码
// 传统方式:知道类名
User user = new User();

// 反射方式:只知道类名字符串
Class<?> clazz = Class.forName("com.example.User");
Object user = clazz.newInstance();

【面试高频】 反射让Java从"静态语言"具备了一定的动态性。

二、获取Class对象的三种方式

Class对象是反射的入口。获取Class对象有三种方式:

java 复制代码
// 方式一:类名.class
Class<User> clazz1 = User.class;

// 方式二:对象.getClass()
User user = new User();
Class<?> clazz2 = user.getClass();

// 方式三:Class.forName()
Class<?> clazz3 = Class.forName("com.example.User");

一个类在JVM中只有一个Class对象。无论用哪种方式获取,比较结果都是true

java 复制代码
System.out.println(clazz1 == clazz2);  // true
System.out.println(clazz2 == clazz3);  // true

三、反射操作构造器

3.1 获取构造器

java 复制代码
Class<?> clazz = User.class;

// 获取所有public构造器
Constructor<?>[] constructors = clazz.getConstructors();

// 获取指定参数类型的构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);

3.2 创建对象

java 复制代码
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object user = constructor.newInstance("张三", 20);

【面试高频】 Class.newInstance()在JDK9之后被标记为过时,推荐使用Constructor.newInstance()

四、反射操作方法

4.1 获取方法

java 复制代码
Class<?> clazz = User.class;

// 获取所有public方法(包括继承的)
Method[] methods = clazz.getMethods();

// 获取指定方法
Method setName = clazz.getMethod("setName", String.class);

4.2 调用方法

java 复制代码
Object user = clazz.newInstance();
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "李四");

invoke的第一个参数是方法所属的对象,后面的参数是方法实参。

4.3 调用私有方法

java 复制代码
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);  // 暴力破解访问权限
privateMethod.invoke(user);

五、反射操作字段

5.1 获取字段

java 复制代码
Class<?> clazz = User.class;

// 获取所有public字段
Field[] fields = clazz.getFields();

// 获取指定字段
Field nameField = clazz.getDeclaredField("name");

5.2 修改字段值

java 复制代码
Object user = clazz.newInstance();
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(user, "王五");

String name = (String) nameField.get(user);
System.out.println(name);  // 王五

六、反射的典型应用场景

6.1 Spring IOC容器

Spring通过反射读取配置文件或注解,自动创建Bean对象:

java 复制代码
// 配置文件
<bean id="userService" class="com.example.UserService"/>

// Spring底层
Class<?> clazz = Class.forName("com.example.UserService");
Object bean = clazz.newInstance();

6.2 MyBatis结果集映射

MyBatis查询数据库后,通过反射把数据库字段映射到Java对象属性:

java 复制代码
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj, resultSet.getObject(columnName));

6.3 JDBC加载驱动

java 复制代码
Class.forName("com.mysql.cj.jdbc.Driver");

这行代码就是利用反射把数据库驱动类加载到内存。

6.4 序列化与反序列化

JSON框架(如FastJSON、Jackson)通过反射读取对象字段,把对象转成JSON字符串,或者把JSON字符串转成对象。

七、手写简易IOC容器

下面用反射实现一个极简版IOC容器,让你理解Spring的核心原理。

7.1 定义注解

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent {
    String value() default "";
}

7.2 标注Bean

java 复制代码
@MyComponent
public class UserService {
    public void sayHello() {
        System.out.println("Hello from UserService");
    }
}

7.3 IOC容器

java 复制代码
public class MyIocContainer {
    private Map<String, Object> beanMap = new HashMap<>();

    public void scan(String packageName) throws Exception {
        // 简单起见,直接加载指定类
        Class<?> clazz = Class.forName(packageName);

        if (clazz.isAnnotationPresent(MyComponent.class)) {
            Object bean = clazz.newInstance();
            String beanName = clazz.getSimpleName();
            beanName = beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
            beanMap.put(beanName, bean);
        }
    }

    public Object getBean(String name) {
        return beanMap.get(name);
    }
}

7.4 使用容器

java 复制代码
public class IocDemo {
    public static void main(String[] args) throws Exception {
        MyIocContainer container = new MyIocContainer();
        container.scan("com.example.UserService");

        UserService userService = (UserService) container.getBean("userService");
        userService.sayHello();
    }
}

【面试高频】 Spring IOC的核心就是"通过反射创建对象并管理对象生命周期"。

八、反射的优缺点

8.1 优点

  • 可以在运行时动态创建对象
  • 可以操作私有成员
  • 是框架实现的基础

8.2 缺点

  • 性能比直接调用低
  • 破坏了封装性
  • 代码可读性下降

【面试陷阱】 反射性能差不是绝对的。JDK对反射做了很多优化,普通场景下差距不大。但在高并发、大量反射调用的场景要注意。

九、开发踩坑实录

坑 1:Class.forName类名写错

java 复制代码
Class<?> clazz = Class.forName("com.example.user");  // 类名大小写错误

后果ClassNotFoundException

正确做法:类名必须是完整限定名,且大小写一致。

坑 2:调用私有成员没setAccessible

java 复制代码
Field field = clazz.getDeclaredField("name");
field.set(user, "张三");  // 报 IllegalAccessException

正确做法 :先调用field.setAccessible(true)

坑 3:反射创建对象时无参构造器被删除

java 复制代码
public class User {
    public User(String name) {}  // 只有有参构造器
}

User user = User.class.newInstance();  // 报错

正确做法:明确调用对应参数的构造器。

坑 4:invoke参数类型不匹配

java 复制代码
Method method = clazz.getMethod("setAge", int.class);
method.invoke(user, "20");  // 参数类型不匹配

正确做法:传入与声明类型一致的实际参数。

坑 5:反射滥用导致代码难以维护

反射虽然强大,但过度使用会让代码变成"黑魔法"。普通业务代码中应优先使用正常面向对象写法。

十、面试速记卡

10.1 核心API速查

API 作用
Class.forName() 加载类并获取Class对象
clazz.newInstance() 创建对象(JDK9后过时)
getConstructor() 获取构造器
getMethod() 获取方法
getField() 获取public字段
getDeclaredMethod() 获取任意方法(含私有)
getDeclaredField() 获取任意字段(含私有)
setAccessible(true) 暴力破解访问权限
invoke() 调用方法

10.2 高频面试题

  1. 什么是反射?有什么作用?
  2. 获取Class对象的三种方式是什么?
  3. 反射如何调用私有方法?
  4. Spring IOC底层是怎么实现的?
  5. 反射的优缺点是什么?
  6. 为什么反射性能比直接调用低?
  7. JDBC中Class.forName()的作用是什么?

10.3 口诀总结

复制代码
反射入口是Class,三种方式获取它
forName最常用,全限定名别写差
构造器和方法,都能反射拿
私有成员加setAccessible,才能操作它
框架底层爱用它,Spring IOC靠它发
反射虽好别滥用,代码维护会抓瞎

十一、小结

反射是Java最强大的特性之一,也是大多数框架的底层基础。

掌握反射的核心API、理解Spring IOC的实现原理、能手写一个简易容器,面试和工作都够用了。

记住:反射是一把双刃剑,能解决问题,但不要为了用而用。

相关推荐
李永奉1 小时前
C语言-嵌入式开发C语言常用预处理指令合集
c语言·开发语言
Patrick在香港2 小时前
Python Docker镜像从1.2GB到89MB:多阶段构建的完整优化实录
java·python·docker·信息可视化·容器·数据分析·ai编程
上海安当技术2 小时前
敏感数据怎么防拖库?信封加密(DEK+KEK 二层密钥)架构设计与 Java 实战
java·开发语言·python
我是唐青枫2 小时前
Java JCommander 实战详解:用注解解析命令行参数
java·开发语言
大鹏说大话2 小时前
C++ 内存布局详解:类、虚函数、虚表底层原理
java·开发语言
键盘会跳舞2 小时前
C++:容器适配器 queue 的源码级拆解
开发语言·c++·queue
还是鼠鼠3 小时前
Spring AI连接DeepSeek与通义千问:application.yaml配置详解
java·通义千问·spring ai·deepseek
计算机小白一个3 小时前
蓝桥杯 Java B 组之哈希表应用(两数之和、重复元素判断)
java·数据结构·算法·蓝桥杯
Loge编程生活3 小时前
打卡信奥刷题(3449)用C++实现信奥题 P10429 [蓝桥杯 2024 省 B] 拔河
开发语言·数据结构·c++·算法·青少年编程
在世修行4 小时前
从零打造 C# 工业视觉检测系统(八):Modbus RTU 协议解析与 PLC 剔除控制实战
开发语言·c#