手写一个mini版Spring:自动注册 + 依赖注入

手写一个mini版Spring:自动注册 + 依赖注入 写本系列文章的初衷在 AI 的时代浪潮下我们要时刻保持自己的思考让 AI 为我们赋能而不是替代我们。所以我打算抽空学习关于 Spring 框架的一些知识就是为了保持专注和探索 AI 的出现和发展会让任何行业的从业者都有可能成为程序员到那个时候我们就仅仅是个程序员了。本篇文章是作者的笔记仅供参考。上一篇文章我们实现了《手写一个mini版Spring》的第一个小节——手动注册 BeanDefinition 和获取单例Bean手动注册 BeanDefinition 和获取单例Bean在实际的项目开发中很少有人会使用手动来注册Bean一般都是通过注解来实现所以我们这期的内容就是Component 注解的实现Autowired 注解的实现字段注入的实现包扫描的实现也是就让容器从手动注册 BeanDefinition 变成自动扫描类 - 注册 BeanDefinition - 创建 Bean 时自动注入依赖我们最终的效果就是ComponentpublicclassOrderRepository{publicStringfindName(){return我是 OrderRepository;}}ComponentpublicclassOrderService{AutowiredprivateOrderRepositoryorderRepository;publicStringfindName(){returnorderRepository.findName();}publicOrderRepositorygetOrderRepository(){returnorderRepository;}}ComponentScan(com.example.step2)publicclassTestApplication{}classSimpleApplicationContextTest{TestvoidscansComponentsAndAutowiresDependencies(){SimpleApplicationContextcontextnewSimpleApplicationContext(TestApplication.class);OrderServiceorderServicecontext.getBean(OrderService.class);System.out.println(获取到的 name : orderService.findName());assertEquals(我是 OrderRepository !,orderService.findName());}}我把重点分了两个大类扫描找到 Component 类注册为 BeanDefinition注入创建 Bean 的时候扫描字段上的 Autowired 注解把依赖对象塞进去1. 定义注解Component 注解标记这个类要交给容器管理标记此注解后会自动注册 Bean 定义。// 作用标记这个类要交给容器管理Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)DocumentedpublicinterfaceComponent{/** * 组件名称 如果不写值就默认类名首字母小写 */Stringvalue()default;}Autowired 注解标记此注解的字段支持自动注入注入到引用这个字段的类的属性中// 当前这一版暂时只支持字段注入、按类型注入Target(ElementType.FIELD)Retention(RetentionPolicy.RUNTIME)DocumentedpublicinterfaceAutowired{}ComponentScan标记配置类要扫描哪些包// 标记配置类要扫码哪些包Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)DocumentedpublicinterfaceComponentScan{/** * 组件名称 如果提供默认为bean的名称 */Stringvalue()default;}2. 改造 SimpleBeanFactory 类在第一小节的时候已经写了一个 SimpleBeanFactory 类主要用于组装bean的定义、注册bean的定义、管理bean的缓存等所以接下来我们要来改造一下这个类。手动注册 BeanDefinition 和获取单例Bean2.1 实现通过按类型获取Bean接口这一步其实是为了 Autowired 提供服务Autowired 的本质就是通过字段类型去容器中查找 Bean。BeanFactory 定义接口// ...// 目前只贴了这个新加的接口TTgetBean(ClassTrequiredType);SimpleBeanFactory 实现接口// ...// 目前只贴了新增加的代码// 遍历Bean定义的Map找到相同类型的Bean并返回// 目前这一版先一个类型对应一个实现类后面在处理负责的如多实现类的情况OverridepublicTTgetBean(ClassTrequiredType){for(StringbeanName:beanDefinitionMap.keySet()){BeanDefinitionbeanDefinitionbeanDefinitionMap.get(beanName);Class?beanClassbeanDefinition.getBeanClass();if(requiredType.isAssignableFrom(beanClass)){returngetBean(beanName,requiredType);}}thrownewRuntimeException(No bean of type requiredType.getName() found.);}2.2 优化 createBean() 方法第一版 createBean() 方法是这样的privateObjectcreateBean(StringbeanName,BeanDefinitionbeanDefinition){try{// 1. 创建bean实例Class?beanClassbeanDefinition.getBeanClass();Constructor?constructorbeanClass.getDeclaredConstructor();constructor.setAccessible(true);Objectbeanconstructor.newInstance();// 2. 将bean实例注册到单例池中registerSingleton(beanName,bean);returnbean;}catch(Exceptione){thrownewRuntimeException(e);}}我们接下来要做的就是在对象创建完之后我们要扫描这个对象里的字段把带有 Autowired 的依赖注入进去举个例子// 1. OrderService 中注入了 UserService 这个类型的字段// 2. 在创建 OrderService 实例的时候就要通过 Autowired 扫描到 UserService 然后把他也初始化到 OrderService 这个对象里ComponentpublicclassOrderService{AutowiredprivateUserServiceuserService;publicvoidtest(){System.out.println(userService);}}开始改造 createBean() 方法增加 populateBean(bean) 方法privateObjectcreateBean(StringbeanName,BeanDefinitionbeanDefinition){try{// 1. 创建bean实例Class?beanClassbeanDefinition.getBeanClass();Constructor?constructorbeanClass.getDeclaredConstructor();constructor.setAccessible(true);Objectbeanconstructor.newInstance();// 2. 依赖注入populateBean(bean);// 3. 将bean实例注册到单例池中registerSingleton(beanName,bean);returnbean;}catch(Exceptione){thrownewRuntimeException(e);}}populateBean(bean) 方法实现/** * 依赖注入 * param bean */privatevoidpopulateBean(Objectbean){// 1. 获取并遍历当前bean的所有字段Field[]fieldsbean.getClass().getDeclaredFields();for(Fieldfield:fields){// 2. 如果字段上没有Autowired注解就跳过if(!field.isAnnotationPresent(Autowired.class)){continue;}// 3. 否则获取这个字段的类型通过类型获取这个字段的bean实例Class?fieldTypefield.getType();ObjectdependencygetBean(fieldType);try{// 4. 通过反射 将这个字段的bean实例注入到当前bean中field.setAccessible(true);// 绕过 Java 的访问控制检查如果字段是私有的是不能在类外访问和修改的field.set(bean,dependency);}catch(IllegalAccessExceptione){thrownewRuntimeException(Failed to autowire field: field.getName(),e);}}}3. 实现自动扫包、自动注册 Bean上一步我们实现了依赖注入但是他的前提是要注册 Bean 的定义第一小节我们实现了通过手动的方式来注册 Bean 的定义 这一节就学习如何自动注册。我们要做的就是通过包名扫描这个包下面所有的 class 如果包含我们定义的 Component 注解就把这个类注册到 BeanDefinition。3.1 创建一个扫描器来实现这个需求重点看 registerIfComponent(String className) 方法带有 Component 注解的类会被注册为 BeanDefinition在 createBean 的时候会用这个定义来实例化 Bean。/** * classpath 下的 BeanDefinition 扫描器。 * * p负责从指定包开始递归查找 class 文件把标记了 Component * 的类转换成 {link BeanDefinition} 并注册到容器中。/p * * author : jiagang * date : Created in 2026/6/5 14:14 */publicclassClassPathBeanDefinitionScanner{// BeanDefinition 注册入口扫描器只负责发现候选类真正的保存工作交给 registry。privatefinalBeanDefinitionRegistryregistry;publicClassPathBeanDefinitionScanner(BeanDefinitionRegistryregistry){this.registryregistry;}/** * 扫描一个或多个基础包。 * * param basePackages 需要扫描的包名例如 com.mdx.demo.service */publicvoidscan(String...basePackages){for(StringbasePackage:basePackages){scanPackage(basePackage);}}/** * 将 Java 包名转换成 classpath 路径并定位对应目录。 */privatevoidscanPackage(StringbasePackage){// com.mdx.demo - com/mdx/demo用于从 ClassLoader 中查找资源目录。StringpathbasePackage.replace(.,/);ClassLoaderclassLoaderThread.currentThread().getContextClassLoader();URLresourceclassLoader.getResource(path);if(resourcenull){return;}// 当前实现只处理文件系统中的 class 目录适合本项目的本地编译输出场景。FilebaseDirnewFile(resource.getFile());scanDirectory(basePackage,baseDir);}/** * 递归扫描目录中的 class 文件。 * * param basePackage 当前目录对应的 Java 包名 * param directory 当前要扫描的目录 */privatevoidscanDirectory(StringbasePackage,Filedirectory){File[]filesdirectory.listFiles();if(filesnull){return;}for(Filefile:files){if(file.isDirectory()){// 子目录对应子包继续向下扫描。scanDirectory(basePackage.file.getName(),file);continue;}// 只处理编译后的 class 文件忽略源码、资源文件等其他内容。if(!file.getName().endsWith(.class)){continue;}// 根据当前包名和文件名还原全限定类名再判断是否需要注册。StringclassNamebasePackage.file.getName().replace(.class,);registerIfComponent(className);}}/** * 加载类并判断是否带有 Component符合条件时注册为 BeanDefinition。 */privatevoidregisterIfComponent(StringclassName){try{Class?clazzClass.forName(className);if(!clazz.isAnnotationPresent(Component.class)){return;}// 读取 Component 的 value作为显式指定的 beanName。Componentcomponentclazz.getAnnotation(Component.class);StringbeanNamecomponent.value();if(beanNamenull||beanName.isBlank()){// 没有显式命名时沿用 Spring 风格类名首字母小写作为 beanName。beanNamelowerFirst(clazz.getSimpleName());}// 将类信息包装成 BeanDefinition后续创建 Bean 实例时会使用它。registry.registerBeanDefinition(beanName,newBeanDefinition(clazz));}catch(ClassNotFoundExceptione){thrownewRuntimeException(Failed to load class: className,e);}}/** * 将类名首字母小写用于生成默认 beanName。 */privateStringlowerFirst(Stringname){if(namenull||name.isEmpty()){returnname;}returnCharacter.toLowerCase(name.charAt(0))name.substring(1);}}3.2 获取 ComponentScan 注解的包名扫描器实现完了现在需要添加一个入口跟扫描器关联起来就是要获取到 ComponentScan 注解定义的包名然后传给扫描器处理。/** * author : jiagang * date : Created in 2026/6/5 17:54 */publicclassSimpleApplicationContext{privatefinalSimpleBeanFactorybeanFactorynewSimpleBeanFactory();publicSimpleApplicationContext(Class?configClass){scan(configClass);}// 获取 ComponentScan 注解上的包名privatevoidscan(Class?configClass){if(!configClass.isAnnotationPresent(ComponentScan.class)){thrownewRuntimeException(No ComponentScan found on config class: configClass.getName());}ComponentScancomponentScanconfigClass.getAnnotation(ComponentScan.class);String[]basePackagesnewString[]{componentScan.value()};ClassPathBeanDefinitionScannerscannernewClassPathBeanDefinitionScanner(beanFactory);scanner.scan(basePackages);}publicObjectgetBean(StringbeanName){returnbeanFactory.getBean(beanName);}publicTTgetBean(StringbeanName,ClassTrequiredType){returnbeanFactory.getBean(beanName,requiredType);}publicTTgetBean(ClassTrequiredType){returnbeanFactory.getBean(requiredType);}}至此所有实现就完成了。4. 测试首先实现以下测试类ComponentpublicclassOrderRepository{publicStringfindName(){return我是 OrderRepository;}}ComponentpublicclassOrderService{AutowiredprivateOrderRepositoryorderRepository;publicStringfindName(){returnorderRepository.findName();}publicOrderRepositorygetOrderRepository(){returnorderRepository;}}ComponentScan(com.example.step2)publicclassTestApplication{}classSimpleApplicationContextTest{// 并没有通过 SimpleBeanFactory 去注册类TestvoidscansComponentsAndAutowiresDependencies(){SimpleApplicationContextcontextnewSimpleApplicationContext(TestApplication.class);OrderServiceorderServicecontext.getBean(OrderService.class);System.out.println(获取到的 name : orderService.findName());assertEquals(我是 OrderRepository !,orderService.findName());}}运行 scansComponentsAndAutowiresDependencies() 测试方法成功获取到 OrderRepository 类中的 findName。最近看到一个很扎心的现象企业越来越关注开发效率而 AI 正在成为新的生产力工具。同样的需求会使用 AI 的工程师往往能够更快完成设计、编码和测试工作。与其担心被 AI 替代不如尽早学会驾驭 AI。最近我不仅在学习 Java 底层还在学习一些人工智能的知识发现了一个不错的 AI 学习网站内容通俗易懂比较适合程序员快速上手感兴趣的话也可以看看人工智能学习网One more thing要么努力到出类拔萃要么就懒得乐知天命。最怕你见识打开了可努力又跟不上骨子里清高至极性格上又软弱无比。