mini-ssm Lab3 - IOC 容器
在这个 Lab 中,我们将实现一个简单的 IOC 容器,它的核心是通过 BeanDefinition
集合创建和管理 Bean 实例。
1. 项目结构
在开始实现前,首先看一下 Lab3 的项目结构:
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 实现
DefaultBeanFactory
是 BeanFactory
的默认实现,负责将 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());
}
}
代码解析
-
doRegisterBeanDefinition:注册
BeanDefinition
对象,将每个 Bean 的定义(名称和类信息)存储到beanDefinitionMap
中。若 Bean 名称重复则抛出异常。 -
getBean:从容器中获取指定名称的 Bean。如果 Bean 已在缓存中,则直接返回,否则调用
instantiateBean
方法创建 Bean 实例并缓存。 -
instantiateBean:通过反射获取构造函数并实例化 Bean。如果构造函数参数有循环依赖,可能会导致
StackOverflowException
。 -
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 容器更加完善。