游乐游手机版
首页/AI教程/文章详情

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

时间:2026-07-01 17:33
通过包扫描工具扫描指定包下所有类,利用@AutoRegister标记需容器管理的Bean,@PropNameSpace标注配置类并自动注入属性,最后反射创建实例存入单例工厂,实现简易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: https://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
上一篇腾讯混元生图从开通到API集成实战指南 下一篇生产车间系统等不起困境迎来技术解决方案
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
OpenClaw 的 sessions_send 机制
AI教程 · 2026-07-03

OpenClaw 的 sessions_send 机制

OpenClaw 中,Agent 之间( Agent to Agent,A2A )的精准通信主要通过的 sessions_* 工具集来实现。目标是让分布在不同工作区或通讯平台的智能体能够协同工作,而无需用户手动干预。sessions_send 是工具集中的核心工具,允许一个会话向另一个指定的活跃会话

Agent、Copilot、Advisor
AI教程 · 2026-07-03

Agent、Copilot、Advisor

按照自动化程度,对现在流行的几款产品进行排序:Manus > OpenClaw ≈ MiroFish > Claude Code > Codex第一档:真 AgentManus 是员工,唯一接近全自动化的产品,任务一旦开始,人可以消失。第二档:Agent 雏形OpenClaw 是实习生。能跑但不稳。

OpenClaw最佳实践:部署在圈组的AI团队
AI教程 · 2026-07-03

OpenClaw最佳实践:部署在圈组的AI团队

大模型爆发以来,几乎每家企业的技术周会上都出现过这个议题:“我们怎么把AI Agent用起来?”最近爆火的OpenClaw让这个答案逐渐清晰。真正的企业级 AI 应用,需要的是一群能够各司其职、相互配合、持续在线的数字员工,这是一套Multi-Agent系统的工程命题,OpenClaw提供了高性能的

OpenClaw 为什么会火?因为它开始接近“操作系统”了
AI教程 · 2026-07-03

OpenClaw 为什么会火?因为它开始接近“操作系统”了

最近几个月,一个非常明显的趋势正在 AI 圈发生大量 AI Agent 项目开始迅速“操作系统化”。它们已经不再满足于:代码语言:javascript复制Prompt → 回复而是在快速演化为:代码语言:javascript复制任务理解 → 规划 → 记忆 → 工具调用 → 状态管理 → 执行控制

2026企业级Agent产品推荐,三大维度硬核测评与主流产品评测
AI教程 · 2026-07-03

2026企业级Agent产品推荐,三大维度硬核测评与主流产品评测

2026年,企业级AI智能体已跨越“概念验证”的门槛,正式驶入规模化落地的快车道。在市场规模预计突破449亿元、Gartner预测40%的企业软件将嵌入自主执行智能体的时代背景下,企业面临的不再是“要不要用AI”的问题,而是“如何选对能真正解决业务痛点的Agent”。面对国内300 服务商的供给红海