mini-ssm Lab3 - IOC 容器

30 阅读3分钟

mini-ssm Lab3 - IOC 容器

mini-ssm github

在这个 Lab 中,我们将实现一个简单的 IOC 容器,它的核心是通过 BeanDefinition 集合创建和管理 Bean 实例。

1. 项目结构

在开始实现前,首先看一下 Lab3 的项目结构:

image.png

IOC 容器的底层实现其实就是一个 Map,其中:

  • Key:Bean 名称
  • Value:Bean 实例

2. BeanFactory 接口

首先,我们定义了 BeanFactory 接口,用于生成和管理 Bean。BeanFactory 主要包含以下几个方法:

/**
 * 获取指定名称的 Bean 实例
 * @param name Bean 的名称
 * @return Bean 实例
 * @throws Exception 异常
 */
Object getBean(String name) throws Exception;

/**
 * 获取指定类型的 Bean 实例
 * @param requiredType Bean 的类型
 * @return Bean 实例
 * @param <T> Bean 的类型
 * @throws Exception 异常
 */
<T> T getBean(Class<T> requiredType) throws Exception;

/**
 * 获取 IOC 容器中的所有 Bean
 * @return 所有 Bean 的列表
 */
List<Object> getAllBeans();

3. DefaultBeanFactory 实现

DefaultBeanFactoryBeanFactory 的默认实现,负责将 BeanDefinition 注册到 IOC 容器中,并实例化管理这些 Bean。以下是关键实现:

@Slf4j
public class DefaultBeanFactory implements BeanFactory {
    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private final Map<String, Object> factoryBeanInstanceCache = new ConcurrentHashMap<>();

    protected void doRegisterBeanDefinition(List<BeanDefinition> beanDefinitions) throws Exception {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            if (beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("The "" + beanDefinition.getFactoryBeanName() + "" already exists!");
            }
            beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
    }

    @Override
    public Object getBean(String beanName) throws InvocationTargetException, InstantiationException, IllegalAccessException {
        Object instance = factoryBeanInstanceCache.get(beanName);
        if (instance != null) {
            return instance;
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        Assert.notNull(beanDefinition, "Bean not found: " + beanName);
        Object o = instantiateBean(beanDefinition);
        factoryBeanInstanceCache.put(beanName, o);
        return o;
    }

    protected void doCreateBean() {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            if (!entry.getValue().isLazyInit()) {
                try {
                    getBean(beanName);
                } catch (Exception e) {
                    log.error("Bean creation failed", e);
                }
            }
        }
    }

    private Object instantiateBean(BeanDefinition beanDefinition) throws InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> clz = beanDefinition.getBeanClass();
        Constructor<?>[] constructors = clz.getConstructors();
        Assert.isTrue(constructors.length == 1, "Multiple constructors found in " + beanDefinition.getBeanClass().getName());
        Constructor<?> constructor = constructors[0];
        Class<?>[] parameterTypes = constructor.getParameterTypes();
        Object[] args = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            args[i] = getBean(parameterTypes[i]);
        }
        return constructor.newInstance(args);
    }

    @Override
    public <T> T getBean(Class<T> requiredType) throws InvocationTargetException, InstantiationException, IllegalAccessException {
        String beanName = StringUtil.toLowerFirstCase(requiredType.getSimpleName());
        return requiredType.cast(getBean(beanName));
    }

    @Override
    public List<Object> getAllBeans() {
        return new ArrayList<>(factoryBeanInstanceCache.values());
    }
}

代码解析

  1. doRegisterBeanDefinition:注册 BeanDefinition 对象,将每个 Bean 的定义(名称和类信息)存储到 beanDefinitionMap 中。若 Bean 名称重复则抛出异常。

  2. getBean:从容器中获取指定名称的 Bean。如果 Bean 已在缓存中,则直接返回,否则调用 instantiateBean 方法创建 Bean 实例并缓存。

  3. instantiateBean:通过反射获取构造函数并实例化 Bean。如果构造函数参数有循环依赖,可能会导致 StackOverflowException

  4. doCreateBean:实例化非懒加载的单例 Bean,通过调用 getBean 完成对象的预创建。

注意事项

  • 循环依赖:目前的实现如果构造函数中存在循环依赖,会导致 StackOverflowException
  • 多构造函数检查:如果类中存在多个构造函数,则抛出异常。

4. ApplicationContext - 封装流程

ApplicationContext 类封装了 BeanFactory 的流程管理,包括获取包扫描路径、生成 BeanDefinition、以及创建单例对象等步骤。

@Slf4j
public class ApplicationContext extends DefaultBeanFactory {
    @SneakyThrows
    public static ApplicationContext run(Class<?> mainClass) {
        ApplicationContext context = new ApplicationContext();
        SpringBootApplication springBootApplication = mainClass.getAnnotation(SpringBootApplication.class);
        Assert.notNull(springBootApplication, "Cannot find @SpringBootApplication annotation");
        String scanPackage = springBootApplication.scanPackage();
        if (StringUtil.isEmpty(scanPackage)) {
            scanPackage = mainClass.getPackage().getName();
        }
        context.refresh(scanPackage);
        return context;
    }

    public void refresh(String scanPackage) throws Exception {
        BeanDefinitionReader reader = new BeanDefinitionReader(scanPackage);
        List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
        doRegisterBeanDefinition(beanDefinitions);
        doCreateBean();
        log.info("Refresh complete");
    }
}

代码解析

  • run 方法:主入口方法,用于启动应用程序。根据 @SpringBootApplication 注解获取包扫描路径,然后调用 refresh 方法。

  • refresh 方法:调用 BeanDefinitionReader 进行包扫描,加载所有 BeanDefinition 并注册到容器,随后执行 Bean 的创建。

小结

本 Lab 实现了一个基础的 IOC 容器,包含 Bean 的注册和实例化流程。下一步可以考虑在此基础上实现 DI(依赖注入)功能,使得 IOC 容器更加完善。