mini-spring实现

一、简介

基于bilibili up主“学java的生生”的手写spring教程,实现一个简单的spring框架。mini-spring包含的核心功能有:包扫描,BeanDefinition封装,IOC容器,依赖注入,生命周期管理,按类型/名称获取Bean等功能。后续还会继续基于mini-spring实现mvc。

二、项目框架

mini-spring
├── .idea
├── .mvn
└── src
    └── main
        ├── java
        │   └── tech
        │       └── insight
        │           └── minispring
        │               ├── annotation
        │               │   ├── Autowired
        │               │   ├── Component
        │               │   └── PostConstruct
        │               ├── entity
        │               ├── ApplicationContext
        │               ├── BeanDefinition
        │               ├── BeanPostProcessor
        │               ├── Main
        │               └── MyBeanPostProcessor
        └── resources
            └── application.properties

三、核心代码解析

1.BeanDefinition

package tech.insight.minispring;

import tech.insight.minispring.sub.Autowired;
import tech.insight.minispring.sub.PostConstruct;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class BeanDefinition {
    private final String name;
    private final Constructor<?> constructor;
    private final Method postConstructMethod;
    private final List<Field> autowiredFields;
    private final Class<?> beanType;

    public BeanDefinition(Class<?> type){
        this.beanType = type;
        Component component = type.getDeclaredAnnotation(Component.class);
        this.name = component.name().isEmpty() ? type.getSimpleName() : component.name();
        try {
            this.constructor = type.getConstructor();
            this.postConstructMethod = Arrays.stream(type.getDeclaredMethods())
                    .filter(m -> m.isAnnotationPresent(PostConstruct.class))
                    .findFirst()
                    .orElse(null);
            this.autowiredFields = Arrays.stream(type.getDeclaredFields())
                    .filter(m -> m.isAnnotationPresent(Autowired.class))
                    .toList();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public String getName(){
        return name;
    }

    public Constructor<?> getConstructor(){
        return constructor;
    }

    public Method getPostConstructMethod(){
        return postConstructMethod;
    }
    
    public List<Field> getAutowiredFields(){
        return autowiredFields;
    }
    
    public Class<?> getBeanType(){
        return beanType;
    }
}

为什么要定义BeanDefinition,而不是直接定义Bean然后进行实例化?

BeanDefinition存储的是Bean的元信息,用来描述怎么创建一个Bean。BeanDefinition像是配方,而Bean是具体做出来的菜。

使用BeanDefinition有以下优势:

  1. 延迟实例化(Lazy Init)

如果你直接new,那一启动就得把所有对象都创建出来。而通过BeanDefinition,可以先将Bean的配方存储,在真正需要使用时再创建Bean,这样就能控制实例化的时机。

  1. 生命周期管理

直接使用new,只能拿到对象本身,无法管理对象的生命周期。因为BeanDefinition记录了Bean的元数据,所以可以在合适的时机执行生命周期方法,比如PostConstruct,PreDestory等。

  1. 依赖注入

BeanDefinition中保存了类中带有@Autowired的字段,创建Bean时能够通过反射注入对应的依赖对象。

  1. 代理增强

可以通过BeanDefinition的配置,决定返回一个原始对象还是增强后的代理对象,可以额外实现日志、事务等功能,这也是SpringAOP的基础。

2.ApplicationContext

  1. 包扫描函数
**
 * 包扫描函数
 * @param packageName
 * @return
 */
public List<Class<?>> scanPackage(String packageName) throws Exception {
    List<Class<?>> classList = new ArrayList<>();
    // 需要a.b.c
    URL resource = this.getClass().getClassLoader().getResource(packageName.replace(".", File.separator));
    Path path = Paths.get(resource.toURI());
    Files.walkFileTree(path,new SimpleFileVisitor<>(){
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Path absolutePath = file.toAbsolutePath();
            if (absolutePath.toString().endsWith(".class")) {
                String replaceStr = absolutePath.toString().replace(File.separator, ".");
                int index = replaceStr.indexOf(packageName);
                String className = replaceStr.substring(index,replaceStr.length() - ".class".length());
                try {
                    classList.add(Class.forName(className));
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
            return FileVisitResult.CONTINUE;
        }
    });
    return classList;

resource:URL对象,包所在的文件目录的定位地址

URI:更通用的统一资源标识符,避免URL中的特殊符号产生的起义

Path:NIO引入的新文件系统的抽象,比File更强大,能够把URI转化为操作系统本地文件路径

Files.walkFileTree():递归遍历包目录及其子目录

这一个方法其实是访问者模式的体现:把堆某种结构的访问操作,从结构本身分离出来,交给访问者去完成。在不改变数据结构的前提下,灵活扩展对结构的操作。

在这里文件是被访问者,SimpleFileVisitor子类是访问者,visitFile()是访问者逻辑,Files.walkFileTree是调度器,遍历目录将文件交给visitor处理。

visitFile()的逻辑是将后缀为.class的文件的绝对路径转化为类的全限定类名,然后通过全限定类名使用反射的方式加载对象(并不是实例化对象,只是生成字节码文件,存储对象的元信息),最后加入classList中。classList中存储的是对象的元信息,之后会被封装成BeanDefinition。

  1. wrapper映射函数:将字节码对象封装为BeanDefinition
protected BeanDefinition wrapper(Class<?> type){
    // 1. 用 Class<?> 创建一个 BeanDefinition(即“配方”)
    BeanDefinition beanDefinition = new BeanDefinition(type);

    // 2. 校验:容器里不能有重复的 beanName
    if (beanDefinitionsMap.containsKey(beanDefinition.getName())){
        throw new RuntimeException("Duplicate bean name: " + beanDefinition.getName());
    }

    // 3. 注册:把 BeanDefinition 放进 beanDefinitionsMap(配方容器)
    beanDefinitionsMap.put(beanDefinition.getName(), beanDefinition);

    // 4. 返回 BeanDefinition(有时候可能还要进一步处理)
    return beanDefinition;
}
  1. createBean&&doCreateBean
protected Object createBean(BeanDefinition beanDefinition) {
    String name = beanDefinition.getName();
    if (ioc.containsKey(name)){
        return ioc.get(name);
    }
    if (loadingIoc.containsKey(name)){
        return loadingIoc.get(name);
    }
    return doCreateBean(beanDefinition);
}

先查询单例池IOC中是否有bean,如果有说明已经创建过了,直接返回。再检查loadingIoc中是否有正在创建的bean,如果有,先返回半成品用于解决循环依赖。如果两个容器中都没有,则进行doCreateBean()。

private Object doCreateBean(BeanDefinition beanDefinition) {
    Constructor constructor = beanDefinition.getConstructor();
    Object bean = null;
    try {
        // 1. 调用构造器,实例化对象,得到一个裸对象
        bean = constructor.newInstance();
        
        // 2. 放入临时集合,标记“正在创建”
        loadingIoc.put(beanDefinition.getName(), bean);
        
        // 3. 自动注入依赖(相当于 Spring 的依赖注入阶段)
        autowiredBean(bean, beanDefinition);
        
        // 4. 初始化(调用初始化方法、执行 Aware 接口、BeanPostProcessor 等)
        bean = initializeBean(bean, beanDefinition);
        
        // 5. 创建完成,从 loadingIoc 移除
        loadingIoc.remove(beanDefinition.getName());
        
        // 6. 放入单例池 ioc,表示 Bean 已经创建完成
        ioc.put(beanDefinition.getName(), bean);
    } catch (Exception e){
        throw new RuntimeException(e);
    }
    return bean;

Spring三级缓存解决循环依赖

在Spring的DefaultSingletonBeanRegistry中有三个Map:

1)singletonObjects:相当于ioc单例池,存放已经初始化好的bean。

2)earlySingletonObjects:相当于loadingIoc,存放已实例化但还未初始化完成的bean。

3)singletonFactories:存放ObjectFactory,一个生成bean的工厂对象

Spring三级缓存相较于本文代码的优势是:在循环依赖时,B拿到的A是工厂返回的代理对象,既可以返回原始对象,也能返回增强后的代理对象;而本文的实现是直接返回裸对象(使用BeanDefinition的构造器进行创建对象),在创建对象后无法再进行增强。因此,Spring的三级缓存具有更强大,更灵活的AOP功能。

图论环检测角度解决循环依赖

提供一个另外思考的视角,Spring 解决循环依赖的做法是 【图论环检测】 的经典实践。 依赖关系可以表示为一个有向图,Cat -> Dog -> Cat 的依赖关系可以表示为一个【有向图】 ,Bean是怕【顶点】, 依赖关系是【边】。Spring在createBean时会递归构建依赖图 (深度优先遍历DFS) , 如果发现当前路径中某个Bean正在创建中(即存在“正在创建”的顶点被重复访问),则判定存在环 【环检测】 。简化做法是采用二级缓存来获取 Cat 的引用 【在图中引入一个虚拟顶点】, 将环拆解为链式依赖 ,从而避免了无限递归。当所有依赖注入完成,再将真实顶点替换虚拟顶点。故视频中的loadingIoc缓存是用于存放虚拟顶点进行环检测的集合容器,ioc是存放真实顶点的集合容器。

  1. autowiredBean 自动注入方法
private void autowiredBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {
    for (Field autowiredField : beanDefinition.getAutowiredFields()) {
        autowiredField.setAccessible(true);
        autowiredField.set(bean,getBean(autowiredField.getType()));
    }
}
  1. BeanPostProcessor
private Object initializeBean(Object bean,BeanDefinition beanDefinition) throws InvocationTargetException, IllegalAccessException {
    for (BeanPostProcessor postProcessor : postProcessors) {
        postProcessor.beforeInitializeBean(bean,beanDefinition.getName());
    }
    Method postConstructMethod = beanDefinition.getPostConstructMethod();
    if (postConstructMethod != null){
        postConstructMethod.invoke(bean);
    }
    for (BeanPostProcessor postProcessor : postProcessors) {
        postProcessor.afterInitializeBean(bean,beanDefinition.getName());
    }

    return bean;
}
/**
 * 将实现BeanPostProcessor接口的Bean实例化并放入postProcessor列表
 */
private void initBeanPostProcessor() {
    beanDefinitionsMap.values().stream().
            filter(bd ->BeanPostProcessor.class.isAssignableFrom(bd.getBeanType()))
            .map(this::createBean).map((bean) -> (BeanPostProcessor)bean)
            .forEach(postProcessors::add);
}

@PostConstruct:在依赖注入完成后,Bean可用前的初始化钩子。

PostProcessor:处理器。所有实现BeanPostProcessor接口的Bean都会被注册成为一个处理器。所有postProcessors列表中的处理器都可以参与生命周期的增强。在创建新的bean时调用initializeBean方法,执行处理器中的逻辑:

先执行 MyBeanLogger.beforeInitializeBean()

再执行 @PostConstruct 方法(如果有)

最后执行 MyBeanLogger.afterInitializeBean()

所以postProcessor的作用就是:定义一系列处理器,在自动注入完成后,对半成品bean进行生命周期的增强

  1. getBean
/**
 * 通过名字获取对象
 * @param name
 * @return
 */
public Object getBean(String name) {
    if (name == null) {
        return null;
    }
    Object bean = ioc.get(name);
    //如果bean不为空,直接返回
    if (bean != null){
        return bean;
    }
    //如果map中有但还未初始化,则将其初始化后返回
    //这样做是为了确保map中存在beanDefinition但还未在ioc容器中创建的对象也被创建
    if (beanDefinitionsMap.containsKey(name)){
        return createBean(beanDefinitionsMap.get(name));
    }
    //ioc中确实没有
    return null;
}

/**
 * 通过类型获取对象
 * @param beanType
 * @return
 * @param <T>
 */
public <T> T getBean(Class<T> beanType){
    String beanName = this.beanDefinitionsMap.values().stream()
            .filter(bd -> beanType.isAssignableFrom(bd.getBeanType()))
            .map(BeanDefinition::getName)
            .findFirst()
            .orElse(null);

    return (T)getBean(beanName);
}

/**
 *
 * @param beanType
 * @return
 * @param <T>
 */
public <T> List<T> getBeans(Class<T> beanType){
    return this.beanDefinitionsMap.values().stream()
            .filter(bd -> beanType.isAssignableFrom(bd.getBeanType()))
            .map(BeanDefinition::getName)
            .map(this::getBean)
            .map(bean -> (T)bean)
            .toList();
}

Spring在通过类型获取bean时,为什么需要通过遍历IoC容器获取,而不是通过维护一个Map<Class,Bean>来获取?

因为要支持多态、代理、多实现的场景。如果传入一个接口或一个父类,其具有多个实现,就不能保证Class和Bean的一一映射关系,无法准确获取bean。因此在通过名称获取时依靠beanNname -> Bean的唯一映射,通过Map来获取;而在通过类型获取时,需要遍历ioc容器获取bean,再匹配类型,以支持多态、代理、多实现的场景。

posted @ 2025-10-03 13:30  lumosdong666  阅读(1)  评论(0)    收藏  举报