
为什么 Class.forName() 会抛 ClassNotFoundException 而不是直接用 ClassLoader.loadClass()
反射加载类失败,八成卡在这一步:容器尝试根据字符串类名实例化 Bean,但类还没被加载进 JVM。用 Class.forName() 是常见写法,但它默认会触发类的静态初始化(static {} 块),如果类里有依赖未就绪或静态代码抛异常,就会连带失败;而 ClassLoader.loadClass() 只加载不初始化,更安全。
- 生产环境建议统一走
Thread.currentThread().getContextClassLoader().loadClass(className),避免因类加载器隔离导致找不到类 - Spring 的
ClassUtils.forName()就是封装了这层逻辑,加了空值和异常兜底 - 注意:如果目标类含
static字段初始化强依赖其他 Bean,那延迟初始化反而会让后续newInstance()失败——这时得手动控制初始化时机
怎么安全调用无参构造器?getDeclaredConstructor() 比 getConstructor() 更靠谱
getConstructor() 只查 public 构造器,一旦你写的 Bean 构造器是 package-private 或 private(比如用 Lombok 的 @RequiredArgsConstructor 且字段非 public),它直接返回 null,接着 newInstance() 就 NPE。必须用 getDeclaredConstructor(),再显式 setAccessible(true)。
- 别漏掉
setAccessible(true),JDK 12+ 默认禁止反射访问非 public 成员,否则报InaccessibleObjectException - 如果类没有无参构造器,得提前扫描所有构造器,按参数类型匹配已有 Bean 实例来调用——这就是“构造器注入”的起点
- 调用前建议检查
constructor.getParameterCount() == 0,避免对有参构造器误执行newInstance()
字段注入时,setAccessible(true) 后仍报 IllegalAccessException 怎么办?
不是权限没开,而是字段所在类本身被模块系统(Java 9+ Module System)限制了访问。比如你的容器在 com.example.ioc 模块,而目标 Bean 在 com.example.service 模块,且后者没在 module-info.java 中声明 opens 包给前者,反射就会被拦截。
- 开发阶段最快解法:启动 JVM 加参数
--add-opens java.base/java.lang=ALL-UNNAMED(替换为你自己的模块名) - 真实项目中,应让业务模块主动
opens com.example.service to com.example.ioc,而不是靠 JVM 参数硬开 - 字段注入不如构造器注入稳定,尤其涉及 final 字段或 record 类时,根本无法反射赋值——这点容易被忽略
循环依赖能靠三级缓存解决?手写时别直接抄 Spring 的 Map 结构
Spring 的三级缓存(singletonObjects / earlySingletonObjects / singletonFactories)本质是为了解决 A 依赖 B、B 又依赖 A 的场景,但手写时若照搬,很容易在 ObjectFactory 回调里重复创建实例,或者因并发导致缓存状态错乱。
立即学习“Java免费学习笔记(深入)”;
- 简单容器用两级就够了:一个
ConcurrentHashMap<string object></string>存已完全初始化的 Bean;一个ConcurrentHashMap<string objectfactory>></string>存正在创建中的工厂,靠computeIfAbsent原子性控制 - 关键点在于:只有当 Bean 还没放入一级缓存,且当前线程正创建它时,才允许从二级缓存取“半成品”——否则就该报循环依赖异常
- 别忘了加
synchronized或用ReentrantLock锁住整个创建流程,否则多线程下 getBean("a") 和 getBean("b") 可能同时触发构造,互相等待
真正难的不是缓存层级,而是判断“什么时候算正在创建中”。很多手写实现把标记位设在 Map 里,结果忘了清掉,下次 getBean 就永远卡住。










