【 手撕Java源码专栏 】Spirng篇之手撕SpringBean:(包含Bean扫描、注册、实例化、获取)

代码链接:github代码地址

简介:本文先演示原生 Spring Bean 扫描与创建,剖析其扫描、注册、实例化、获取流程;再详述手写 Spring 框架的实现思路与核心代码,对比两者差异,助力理解 Spring 容器核心原理与反射等技术应用。

文章目录

1.原生Spring实现Bean扫描和创建

这是一个使用标准Spring框架的演示项目,用于对比和参考:

  • 使用标准Spring注解
    • @Configuration:标记配置类
    • @ComponentScan:指定扫描路径
    • @Component/@Service:标记Bean类
  • 标准Spring容器使用 :展示如何使用Spring的AnnotationConfigApplicationContext
  • Bean生命周期演示:展示Bean的初始化过程

核心类说明

  • SpringConfig:Spring配置类
  • UserServicePersonService:演示服务类
  • SpringBeanTest:测试类,展示如何获取和使用Spring容器中的Bean

1.1依赖导入

xml 复制代码
 <dependencies>
        <!-- 1. Spring 容器核心:扫描、注册 Bean -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.31</version> <!-- 所有模块版本必须一致 -->
        </dependency>

        <!-- 2. Bean 实例化、生命周期管理 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.31</version>
        </dependency>

        <!-- 3. 核心工具类(必须) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.31</version>
        </dependency>

        <!-- 4. SpEL 表达式(支持 @Value,可选但建议包含) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.3.31</version>
        </dependency>
    </dependencies>

1.2定义Bean

1.2.1UserService

java 复制代码
package cn.varin.springdemo.service;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Component(value = "UserService")
public class UserService {
    public  UserService() {
        System.out.println("UserService init");
    }
}

1.2.2PersonService

java 复制代码
package cn.varin.springdemo.service;

import org.springframework.stereotype.Service;

@Service
public class PersonService {
    public PersonService() {
        System.out.println("PersionService init");
    }
}

1.3定义配置类(扫描包)

SpringConfig

java 复制代码
package cn.varin.springdemo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration // 定义配置类
@ComponentScan(value = "cn.varin.springdemo.service") // 扫描路径
public class SpringConfig {
}

1.4启动容器并创建容器

SpringBeanTest

java 复制代码
package cn.varin.springdemo.test;

import cn.varin.springdemo.config.SpringConfig;
import cn.varin.springdemo.service.PersonService;
import cn.varin.springdemo.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringBeanTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        // 根据类型获取Bean
        UserService userService = context.getBean(UserService.class);
        // 根据名称获取Bean
        PersonService persionService = (PersonService) context.getBean("personService");
        context.close();

    }
}

2.Spring扫描和创建Bean流程

  1. 扫描:遍历指定包下的类,识别需要被容器管理的类(通过注解 / XML 标记);

  2. 注册:将识别到的类信息(BeanDefinition)存入容器

  3. 实例化:根据类信息创建 Bean 实例(默认单例);

  4. 获取:提供接口从容器中获取 Bean 实例。

3.手写Spring框架

手写Spring框架的工作流程

  1. 初始化阶段
    • 创建AnnotationConfigApplicationContext实例,传入配置类
    • 从配置类中获取@ComponentScan注解的扫描路径
    • 根据扫描路径查找并加载符合条件的类
  2. Bean注册阶段
    • 遍历扫描到的类,检查是否带有@Component注解
    • 如果有,创建DefinitionBean对象,记录Bean的元信息
    • 检查是否带有@ClassType注解,确定Bean的作用域类型
    • 将Bean定义注册到definitionBeanConcurrentHashMap
  3. 单例Bean实例化阶段
    • 对于单例Bean,在注册后立即通过反射创建实例
    • 将创建好的实例存储到singletonBeanHashMap
  4. Bean获取阶段
    • 调用getBean方法时,根据Bean名称查找对应的Bean定义
    • 对于单例Bean,直接从singletonBeanHashMap中获取已创建的实例
    • 对于原型Bean,每次都通过反射创建新的实例并返回

3.1项目结构

本项目包含两个主要模块,分别是手写的Spring框架实现和标准Spring框架的演示项目:

plain 复制代码
├── handwriting-springbean/   # 手写Spring框架实现模块
│   ├── src/main/java/cn/varin/  
│   │   ├── config/              # 配置类
│   │   ├── handwriting/         # 核心实现
│   │   ├── service/             # 测试服务类
│   │   └── test/                # 测试类
├── spring-demo/              # 标准Spring框架演示模块
│   ├── src/main/java/cn/varin/springdemo/
│   │   ├── config/              # Spring配置类
│   │   ├── service/             # 演示服务类
│   │   └── test/                # 测试类
└── pom.xml                   # 父项目Maven配置

3.2模块说明

1. handwriting-springbean模块

这是一个手写的Spring容器框架核心实现,主要功能包括:

  • 注解扫描与Bean注册 :通过自定义的@ComponentScan注解指定扫描路径,自动发现并注册带有@Component注解的类
  • 单例与原型Bean管理:支持单例(Singleton)和原型(Prototype)两种Bean作用域
  • Bean生命周期管理:实现了基本的Bean实例化和获取功能
  • 自定义注解实现
    • @Component:标记需要被容器管理的类
    • @ComponentScan:指定要扫描的包路径
    • @ClassType:指定Bean的作用域类型

核心类说明

  • AnnotationConfigApplicationContext:手写的Spring容器实现,负责Bean的扫描、注册和管理
  • DefinitionBean:Bean定义类,存储Bean的相关信息
  • BeanConfig:配置类,通过注解指定扫描路径

使用示例

java 复制代码
// 初始化容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
// 获取Bean实例
UserService userService = (UserService) context.getBean("UserService");
handwriting包详细代码分析

handwriting-springbean/src/main/java/cn/varin/handwriting/annotation/ 包中包含了手写Spring框架的核心实现代码,下面对每个文件进行详细分析:

1. AnnotationConfigApplicationContext.java

这是整个手写Spring框架的核心类,实现了类似Spring的AnnotationConfigApplicationContext的功能,主要负责:

java 复制代码
public class AnnotationConfigApplicationContext {
    // 用于存储bean定义信息的容器
    private ConcurrentHashMap<String, DefinitionBean> definitionBeanConcurrentHashMap = new ConcurrentHashMap<>();
    // 用于存储单例bean实例的容器
    private ConcurrentHashMap<String, Object> singletonBeanHashMap = new ConcurrentHashMap<>();
    private String scanPackage;
    private Class clazz;
    
    // 构造方法,接收配置类
    public AnnotationConfigApplicationContext(Class clazz) {
        this.clazz = clazz;
        // 扫描并注册Bean
        scan(clazz);
    }
    
    // 根据bean名称获取bean实例
    public Object getBean(String beanName) {
        // 检查Bean是否存在
        boolean containsKey = definitionBeanConcurrentHashMap.containsKey(beanName);
        if (!containsKey) {
            throw new RuntimeException("Bean not found: " + beanName);
        }
        
        // 判断是单例还是原型
        boolean singletonKey = singletonBeanHashMap.containsKey(beanName);
        if (singletonKey) {
            // 单例模式直接返回已存在的实例
            return singletonBeanHashMap.get(beanName);
        } else {
            // 原型模式每次创建新实例
            DefinitionBean definitionBean = definitionBeanConcurrentHashMap.get(beanName);
            String className = scanPackage.replace("/",".") + "." + definitionBean.getClassName();
            try {
                return getObject(clazz.forName(className), false);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    // 扫描指定包路径下的类并注册Bean
    public void scan(Class clazz) {
        // 获取配置类上的ComponentScan注解
        ComponentScan componentScan = (ComponentScan) clazz.getAnnotation(ComponentScan.class);
        if (componentScan == null) {
            return;
        }
        
        // 获取扫描路径
        String scanValue = componentScan.value();
        String scanValuePath = scanValue.replace(".", "/");
        this.scanPackage = scanValuePath;
        
        // 通过类加载器获取资源
        ClassLoader classLoader = componentScan.getClass().getClassLoader();
        URL resource = classLoader.getResource(scanValuePath);
        File folder = new File(resource.getFile());
        
        // 遍历文件夹中的所有文件
        if (folder.isDirectory()) {
            File[] files = folder.listFiles();
            for (File file : files) {
                try {
                    // 加载类并检查注解
                    Class<?> aClass = Class.forName(scanValue + "." + file.getName().replace(".class", ""));
                    Component component = aClass.getAnnotation(Component.class);
                    ClassType classType = aClass.getAnnotation(ClassType.class);
                    
                    // 如果是需要管理的Bean
                    if (aClass != null && component != null) {
                        // 创建Bean定义
                        DefinitionBean definitionBean = new DefinitionBean(aClass, file.getName().replace(".class", ""));
                        String createType = "";
                        
                        // 判断作用域类型
                        if (classType != null && classType.value().equals("prototype")) {
                            createType = "prototype";
                            // 原型模式不需要存储Class引用
                            definitionBean.setClazz(null);
                        }
                        definitionBean.setClassType(createType.equals("prototype") ? createType : "singleton");
                        
                        // 注册Bean定义
                        definitionBeanConcurrentHashMap.put(definitionBean.getClassName(), definitionBean);
                        
                        // 单例模式立即创建实例
                        if (definitionBean.getClassType().equals("singleton")) {
                            singletonBeanHashMap.put(file.getName().replace(".class", ""), getObject(aClass, true));
                        }
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    // 创建对象实例的方法
    public Object getObject(Class clazz, Boolean b) throws Exception {
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(b);
        return declaredConstructor.newInstance();
    }
}

核心设计思路

  • 使用两个ConcurrentHashMap分别存储Bean定义和单例Bean实例,确保线程安全
  • 通过反射机制实现类的加载、实例化和注解解析
  • 实现了单例和原型两种Bean作用域的管理逻辑
  • 提供了getBean方法用于获取Bean实例
2. DefinitionBean.java

Bean定义类,用于存储Bean的元数据信息:

java 复制代码
public class DefinitionBean {
    // Bean的Class对象引用
    private Class clazz;
    // Bean的作用域类型(singleton或prototype)
    private String classType;
    // Bean的名称
    private String className;
    
    // 构造方法
    public DefinitionBean(Class clazz, String classType, String className) {
        this.clazz = clazz;
        this.classType = classType;
        this.className = className;
    }
    
    public DefinitionBean(Class clazz, String className) {
        this.clazz = clazz;
        this.className = className;
    }
    
    // 无参构造方法
    public DefinitionBean() {}
    
    // Getter和Setter方法
    public Class getClazz() { return clazz; }
    public void setClazz(Class clazz) { this.clazz = clazz; }
    public String getClassType() { return classType; }
    public void setClassType(String classType) { this.classType = classType; }
    public String getClassName() { return className; }
    public void setClassName(String className) { this.className = className; }
    
    // toString方法,用于调试
    @Override
    public String toString() {
        return "DefinitionBean{" +
                "clazz=" + clazz +
                ", classType='" + classType + '\'' +
                ", className='" + className + '\'' +
                '}';
    }
}

设计思路

  • 作为Bean的元数据容器,存储Bean的关键信息
  • 为不同场景提供多种构造方法
  • 实现了标准的Java Bean风格,包含getter和setter方法
3. Component.java

自定义的@Component注解,用于标记需要被Spring容器管理的类:

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}

注解参数说明

  • value():Bean的名称,默认为空字符串
  • @Target({ElementType.TYPE}):表示该注解只能应用于类、接口或枚举类型
  • @Retention(RetentionPolicy.RUNTIME):表示该注解在运行时仍然可见,可以通过反射获取
4. ComponentScan.java

自定义的@ComponentScan注解,用于指定Spring容器需要扫描的包路径:

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    String value();
}

注解参数说明

  • value():需要扫描的包路径
  • 与@Component类似,也是应用于类型且运行时可见
5. ClassType.java

自定义的@ClassType注解,用于指定Bean的作用域类型:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ClassType {
    String value() default "";
}

注解参数说明

  • value():指定Bean的作用域类型,如"singleton"或"prototype",默认为空字符串
  • 同样是应用于类型且运行时可见的注解
5. SpringBeanTest.java

测试类,用于测试手写AnnotationConfigApplicationContext

java 复制代码
package cn.varin.test;

import cn.varin.config.BeanConfig;
import cn.varin.handwriting.annotation.AnnotationConfigApplicationContext;
import cn.varin.service.PersonService;
import cn.varin.service.UserService;

import java.lang.reflect.Constructor;

public class SpringBeanTest {
    public static void main(String[] args) {

        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        UserService userService1 = (UserService) annotationConfigApplicationContext.getBean("UserService");
        UserService userService2 = (UserService) annotationConfigApplicationContext.getBean("UserService");

        System.out.println(userService1);
        System.out.println(userService2);

        PersonService personService1 = (PersonService) annotationConfigApplicationContext.getBean("PersonService");
        PersonService personService2 = (PersonService) annotationConfigApplicationContext.getBean("PersonService");
        System.out.println(personService1);
        System.out.println(personService2);
        personService1.test();
        personService2.test();



    }
}

运行效果图:

代码优化建议
  1. 异常处理优化 :目前代码中大量使用throw new RuntimeException(e),可以考虑定义更具体的异常类型
  2. 资源关闭:在处理文件和资源时,应确保适当关闭,避免资源泄漏
  3. 并发安全:虽然使用了ConcurrentHashMap,但在某些复合操作中可能仍需额外的同步措施
  4. 包扫描递归:当前实现只扫描了一级目录,应增加递归扫描子目录的功能
  5. 依赖注入:可以扩展实现属性注入和构造函数注入功能
  6. Bean生命周期回调:可以添加初始化和销毁回调方法的支持

通过这个手写实现,可以清晰地理解Spring容器的核心工作原理,包括Bean的扫描、注册、实例化和管理过程。

4.对比分析

通过对比两个模块,可以清晰地理解Spring框架的核心原理:

  1. 实现方式对比
    • 手写版本:直接使用反射API实现Bean的扫描、实例化和管理
    • 标准Spring:使用完整的Spring框架,提供更丰富的功能和更完善的错误处理
  2. 功能对比
    • 手写版本:实现了基本的Bean容器功能,支持单例/原型模式
    • 标准Spring:提供完整的依赖注入、AOP、事件机制等企业级功能

5.运行说明

  1. 确保JDK 17及以上版本
  2. 使用Maven构建项目
  3. 可以分别运行两个模块中的测试类来验证功能

6.学习价值

本项目通过手写Spring框架的核心功能,帮助理解:

  • Spring容器的工作原理
  • 注解的定义和使用
  • 反射机制在框架开发中的应用
  • Bean的生命周期管理
  • 依赖注入的基本思想
相关推荐
吴佳浩1 小时前
Python入门指南(六) - 搭建你的第一个YOLO检测API
人工智能·后端·python
踏浪无痕2 小时前
JobFlow已开源:面向业务中台的轻量级分布式调度引擎 — 支持动态分片与延时队列
后端·架构·开源
Pitayafruit2 小时前
Spring AI 进阶之路05:集成 MCP 协议实现工具调用
spring boot·后端·llm
韩立学长2 小时前
【开题答辩实录分享】以《自助游网站的设计与实现》为例进行选题答辩实录分享
java·mysql·spring
ss2732 小时前
线程池:任务队列、工作线程与生命周期管理
java·后端
不像程序员的程序媛2 小时前
Spring的cacheEvict
java·后端·spring
SAP小崔说事儿3 小时前
在数据库中将字符串拆分成表单(SQL和HANA版本)
java·数据库·sql·sap·hana·字符串拆分·无锡sap
凌云若寒3 小时前
半导体代加工企业标签模板痛点的全景式解决方案
java
踏浪无痕3 小时前
JobFlow 实战:无锁调度是怎么做到的
后端·面试·架构
shoubepatien3 小时前
JAVA -- 11
java·后端·intellij-idea