当前位置: 首页
AI教程
如何从零开始实现一个简易低配版Spring BeanFactory完整教程

如何从零开始实现一个简易低配版Spring BeanFactory完整教程

热心网友 时间:2026-07-01
转载

如何自己动手实现一个简易版 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(面向切面编程),从而构建更强大的企业级应用。

更多相关资源

在这里插入图片描述

在这里插入图片描述

来源:https://developer.aliyun.com/article/704942

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
内网RPA离线部署从依赖打包到7×24无人值守踩坑与避坑方案

内网RPA离线部署从依赖打包到7×24无人值守踩坑与避坑方案

这三年,内网RPA项目接了不下二十个。每次开局都像闯关——断网、缺依赖、多机同步、定时执行、批量分发、源码保护、AI离线化,八个坑一个比一个深。今天把这些实战经验整理出来,希望能帮正在内网搞自动化的兄弟们少踩点雷。 一、内网无网络环境怎么部署RPA流程:先搞清楚什么叫“真离线” 很多工具宣传“支持本

时间:2026-07-02 12:28
水利工程师用WorkBuddy写洪水报告效率提升3倍

水利工程师用WorkBuddy写洪水报告效率提升3倍

WorkBuddy开发者分享季 水利工程师AI提效实战:用WorkBuddy撰写洪水影响评价报告,效率提升3倍 WorkBuddy 效率 人工智能 开发工具 一、我是谁,为什么需要AI 先介绍一下自己——我是一名水利工程师,在湖南长沙的一家小型水利设计公司任职。当前行业环境不太

时间:2026-07-02 12:27
日志服务数据加工规则洞察仪表盘使用指南

日志服务数据加工规则洞察仪表盘使用指南

数据加工诊断仪表盘 想实时掌握日志服务加工功能的运行状态?直接从加工列表页点击那个“规则洞察”按钮,仪表盘就会立刻呈现出来。入口就在那儿,不绕弯子。 跳转后,你可以按作业名称、实例ID或源LogStore来筛选任务状态。比如下边这张图,展示的是当前实例ID(90c9d47714dbb807d47c1

时间:2026-07-02 12:27
基于RFID的固定资产管理系统技术架构与工程实践

基于RFID的固定资产管理系统技术架构与工程实践

固定资产管理难题是众多企事业单位的普遍困扰,资产数量动辄数千件,且广泛分布于不同部门、楼层乃至园区。传统人工盘点方式在工程维度上始终面临三大关键瓶颈:采集效率低下、数据闭环中断、状态同步滞后。使用条码枪逐一扫描标签,识别距离通常不超过30厘米,操作人员需逐个寻找并扫描,盘点效率完全受限于人力。面对5

时间:2026-07-02 12:27
WorkBuddy实战用AI搭建A股智能盯盘助手省心高效

WorkBuddy实战用AI搭建A股智能盯盘助手省心高效

炒股的朋友们想必都深有体会——每天重复盯盘、查行情、分析板块轮动,这一整套流程下来耗费大量精力。手动翻查数据不仅身心俱疲,还很容易错过关键买卖节点。今天我们就来聊聊如何打造一款趁手的盯盘工具,借助AI替你分担这些重复性工作。 背景:盯盘的核心痛点 股民都有同感——每天不只要查询单只股票的实时行情,还

时间:2026-07-02 12:27
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜