如何从零开始实现一个简易低配版Spring BeanFactory完整教程
如何自己动手实现一个简易版 Spring BeanFactory?
许多 Java 开发者刚开始接触 Spring 框架时,都会被 IOC 容器和 BeanFactory 的设计理念所吸引。其实,抛开复杂的源码细节,亲手编写一个简易版 BeanFactory 能让你更直观、更深刻地理解 Spring 的核心工作原理。接下来,我们将一步步拆解实现过程。
准备工作
首先,我们需要准备几个基础组件:一个用于扫描指定包下所有 class 文件的工具类、两个自定义注解(分别用来标记需要由容器管理的 Bean 以及需要从配置文件中读取属性的配置类),以及一个用于存放所有 Bean 实例的单例工厂。
包扫描工具类定义
这个工具的主要职责是扫描指定包路径下的所有 class 文件,无论是目录结构还是 JAR 包中的类,都能正确识别。
package com.example.swagger.common.component;
import lombok.extern.slf4j.Slf4j;
import ja va.io.File;
import ja va.io.FileInputStream;
import ja va.io.IOException;
import ja va.net.URL;
import ja va.util.ArrayList;
import ja va.util.Arrays;
import ja va.util.List;
import ja va.util.jar.JarEntry;
import ja va.util.jar.JarInputStream;
@Slf4j
public class ClasspathPackageScanner {
private String basePackage;
private ClassLoader cl;
public ClasspathPackageScanner(String basePackage) {
this.basePackage = basePackage;
this.cl = getClass().getClassLoader();
}
public ClasspathPackageScanner(String basePackage, ClassLoader cl) {
this.basePackage = basePackage;
this.cl = cl;
}
public List getFullyQualifiedClassNameList() throws IOException {
log.info("Start Scan...", basePackage);
return doScan(basePackage, new ArrayList<>());
}
private List doScan(String basePackage, List nameList) throws IOException {
String splashPath = StringUtil.dotToSplash(basePackage);
URL url = cl.getResource(splashPath);
String filePath = StringUtil.getRootPath(url);
List names = null;
if (isJarFile(filePath)) {
if (log.isDebugEnabled()) {
log.debug("{} find a jar file", filePath);
}
names = readFromJarFile(filePath, splashPath);
} else {
if (log.isDebugEnabled()) {
log.debug("{} find a directory", filePath);
}
names = readFromDirectory(filePath);
}
for (String name : names) {
if (isClassFile(name)) {
nameList.add(toFullyQualifiedName(name, basePackage));
} else {
doScan(basePackage + "." + name, nameList);
}
}
if (log.isDebugEnabled()) {
for (String n : nameList) {
log.debug("load {}", n);
}
}
return nameList;
}
private String toFullyQualifiedName(String shortName, String basePackage) {
StringBuilder sb = new StringBuilder(basePackage);
sb.append('.');
sb.append(StringUtil.trimExtension(shortName));
return sb.toString();
}
private List readFromJarFile(String jarPath, String splashedPackageName) throws IOException {
if (log.isDebugEnabled()) {
log.debug("从JAR包中读取类: {}", jarPath);
}
JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
JarEntry entry = jarIn.getNextJarEntry();
List nameList = new ArrayList<>();
while (null != entry) {
String name = entry.getName();
if (name.startsWith(splashedPackageName) && isClassFile(name)) {
nameList.add(name);
}
entry = jarIn.getNextJarEntry();
}
return nameList;
}
private List readFromDirectory(String path) {
File file = new File(path);
String[] names = file.list();
if (null == names) {
return null;
}
return Arrays.asList(names);
}
private boolean isClassFile(String name) {
return name.endsWith(".class");
}
private boolean isJarFile(String name) {
return name.endsWith(".jar");
}
}
class StringUtil {
private StringUtil() {}
public static String getRootPath(URL url) {
String fileUrl = url.getFile();
int pos = fileUrl.indexOf('!');
if (-1 == pos) {
return fileUrl;
}
return fileUrl.substring(5, pos);
}
public static String dotToSplash(String name) {
return name.replaceAll(".", "/");
}
public static String trimExtension(String name) {
int pos = name.indexOf('.');
if (-1 != pos) {
return name.substring(0, pos);
}
return name;
}
public static String trimURI(String uri) {
String trimmed = uri.substring(1);
int splashIndex = trimmed.indexOf('/');
return trimmed.substring(splashIndex);
}
}
自动注入注解定义 AutoRegister
该注解的作用与 Spring 的 @Component 类似,用于标识一个类需要被 IoC 容器所管理。
package com.example.swagger.api;
import ja va.lang.annotation.ElementType;
import ja va.lang.annotation.Retention;
import ja va.lang.annotation.RetentionPolicy;
import ja va.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface AutoRegister {
String name() default "";
}
配置类属性填充注解定义 PropNameSpace
这个注解用于标注一个配置类,并指定从 Properties 或 YAML 配置文件中读取属性的前缀,从而实现自动化属性注入。
package com.example.swagger.api;
import ja va.lang.annotation.ElementType;
import ja va.lang.annotation.Retention;
import ja va.lang.annotation.RetentionPolicy;
import ja va.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface PropNameSpace {
String prefix() default "";
}
实例工厂定义
一个简单的单例工厂,用于统一存储和管理所有已注册的 Bean 实例。
package com.example.swagger.common.context;
import ja va.util.HashMap;
import ja va.util.Map;
public class AppBeanContext {
private static class SingletonHolder {
private static final AppBeanContext INSTANCE = new AppBeanContext();
}
public static final AppBeanContext getInstance() {
return SingletonHolder.INSTANCE;
}
private static final Map HOLDER = new HashMap<>();
public void registerBean(String name, Object bean) {
if (HOLDER.containsKey(name)) {
throw new IllegalStateException("bean repetition register!");
}
HOLDER.putIfAbsent(name, bean);
}
public T getBean(String name, Class clazz) {
if (!HOLDER.containsKey(name)) {
throw new IllegalArgumentException("bean not found!");
}
return clazz.cast(HOLDER.get(name));
}
}
整体设计思路
整个流程非常清晰:首先扫描指定基础包下的所有 class 文件,然后逐一检查每个类上的注解。如果发现类上标注了 @AutoRegister,则通过反射机制创建该类的实例并将其注册到工厂中;如果该类同时还标注了 @PropNameSpace,则自动读取配置文件中对应前缀的属性值,并填充到实例的相应字段中。
核心源码实现
启动初始化类
这个类是整个自制 IoC 容器的启动入口,它在类加载阶段执行包扫描、实例化以及属性注入等关键逻辑。
package com.example.swagger.common.component;
import com.example.swagger.api.AutoRegister;
import com.example.swagger.api.PropNameSpace;
import com.example.swagger.common.configuration.ApplicationYamlLoader;
import com.example.swagger.common.context.AppBeanContext;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import ja va.lang.reflect.Field;
import ja va.util.List;
@Slf4j
public class AppStarterInitial {
static {
log.info("start init...");
beforeStart();
log.info("end init...");
}
private static void beforeStart() {
try {
ClasspathPackageScanner scan = new ClasspathPackageScanner("com.example.swagger");
List classNames = scan.getFullyQualifiedClassNameList();
for (String clazz : classNames) {
Class> c = Thread.currentThread().getContextClassLoader().loadClass(clazz);
if (c.getAnnotations().length > 0) {
if (c.getAnnotationsByType(AutoRegister.class).length > 0) {
Object bean = c.newInstance();
String prefixKey = "";
if (c.getAnnotationsByType(PropNameSpace.class).length > 0) {
PropNameSpace[] annotationsByType = c.getAnnotationsByType(PropNameSpace.class);
prefixKey = annotationsByType[0].prefix();
}
Field[] declaredFields = c.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(Boolean.TRUE);
String filedKey = field.getAnnotationsByType(JsonProperty.class).length > 0
? field.getAnnotationsByType(JsonProperty.class)[0].value()
: field.getName();
String propKey = prefixKey + "." + filedKey;
field.set(bean, ApplicationYamlLoader.getPropsByKey(propKey, field.getType()));
}
String beanName = c.getAnnotationsByType(AutoRegister.class)[0].name();
AppBeanContext.getInstance().registerBean(
StringUtils.isNotBlank(beanName) ? beanName :
StringUtils.lowerCase(c.getSimpleName()).substring(0, 1) + c.getSimpleName().substring(1),
bean);
}
}
}
} catch (Throwable e) {
log.error("init error", e);
}
}
}
这个初始化类的主要职责正是完成前面提到的“扫描→实例化→属性注入”全流程。项目的启动类只需继承 AppStarterInitial,在实例化任何 Bean 之前,上述逻辑就会自动执行,所有符合条件的 Bean 都会被注册到单例工厂中随时取用。
package com.example.swagger;
import com.example.swagger.common.component.AppStarterInitial;
public class SwaggerTransformApplication extends AppStarterInitial {
public static void main(String[] args){
// 启动入口
}
}
运行效果验证
这样一来,我们亲手打造的简易 BeanFactory 就能够正常工作了。下面的截图展示了实际运行效果:配置类 SwaggerEnhanceConfig 被自动注册到容器中,并且它的字段成功从 YAML 配置文件中读取到了对应的值。

截图中打印的配置类代码如下:
package com.example.swagger.common.configuration;
import com.example.swagger.api.AutoRegister;
import com.example.swagger.api.PropNameSpace;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@AutoRegister
@PropNameSpace(prefix = "enhance.config")
public class SwaggerEnhanceConfig {
@JsonProperty("api-docs-url")
private String apiDocsUrl;
}
对应的 YAML 配置文件内容:
enhance:
config:
api-docs-url: http://localhost:9000/swagger-resources/v2/api-docs?group=UI
从控制台输出可以看出,配置类中的 apiDocsUrl 字段已经被成功赋值为 YAML 中指定的 URL 地址。
总结
本文通过自定义注解与反射机制,模拟了 Spring BeanFactory 的一个轻量级实现,展示了如何实现简单的 Bean 动态注册与属性注入功能。感兴趣的朋友可以进一步深入 Spring 框架的三大核心思想:IOC(控制反转)、DI(依赖注入)和 AOP(面向切面编程),从而构建更强大的企业级应用。
更多相关资源


游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
内网RPA离线部署从依赖打包到7×24无人值守踩坑与避坑方案
这三年,内网RPA项目接了不下二十个。每次开局都像闯关——断网、缺依赖、多机同步、定时执行、批量分发、源码保护、AI离线化,八个坑一个比一个深。今天把这些实战经验整理出来,希望能帮正在内网搞自动化的兄弟们少踩点雷。 一、内网无网络环境怎么部署RPA流程:先搞清楚什么叫“真离线” 很多工具宣传“支持本
水利工程师用WorkBuddy写洪水报告效率提升3倍
WorkBuddy开发者分享季 水利工程师AI提效实战:用WorkBuddy撰写洪水影响评价报告,效率提升3倍 WorkBuddy 效率 人工智能 开发工具 一、我是谁,为什么需要AI 先介绍一下自己——我是一名水利工程师,在湖南长沙的一家小型水利设计公司任职。当前行业环境不太
日志服务数据加工规则洞察仪表盘使用指南
数据加工诊断仪表盘 想实时掌握日志服务加工功能的运行状态?直接从加工列表页点击那个“规则洞察”按钮,仪表盘就会立刻呈现出来。入口就在那儿,不绕弯子。 跳转后,你可以按作业名称、实例ID或源LogStore来筛选任务状态。比如下边这张图,展示的是当前实例ID(90c9d47714dbb807d47c1
基于RFID的固定资产管理系统技术架构与工程实践
固定资产管理难题是众多企事业单位的普遍困扰,资产数量动辄数千件,且广泛分布于不同部门、楼层乃至园区。传统人工盘点方式在工程维度上始终面临三大关键瓶颈:采集效率低下、数据闭环中断、状态同步滞后。使用条码枪逐一扫描标签,识别距离通常不超过30厘米,操作人员需逐个寻找并扫描,盘点效率完全受限于人力。面对5
WorkBuddy实战用AI搭建A股智能盯盘助手省心高效
炒股的朋友们想必都深有体会——每天重复盯盘、查行情、分析板块轮动,这一整套流程下来耗费大量精力。手动翻查数据不仅身心俱疲,还很容易错过关键买卖节点。今天我们就来聊聊如何打造一款趁手的盯盘工具,借助AI替你分担这些重复性工作。 背景:盯盘的核心痛点 股民都有同感——每天不只要查询单只股票的实时行情,还
- 日榜
- 周榜
- 月榜
相关攻略
2026-07-02 12:28
2026-07-02 12:27
2026-07-02 12:27
2026-07-02 12:27
2026-07-02 12:27
2026-07-02 12:27
2026-07-02 12:26
2026-07-02 12:26
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

