项目地址:https://github.com/xiaogou446/jsonboot
使用branch:feature/expandIOC
命令行:git checkout feature/expandIOC
拓展IOC
在上一节中,我们简单的实现了IOC容器。我们通过遍历扫描到类文件,生成未完成的bean,后遍历未完成的bean进行依赖注入。这种做法也有许多的问题。比如在将所有的class类文件都实例化后,再进行依赖注入,如果一个依赖注入的过程出现了问题,导致后续的依赖注入无法进行,那么最终使用的则都是未完成的bean,在真正的使用中会出问题。其次我们从始至终使用的容器都是BEANS一个容器,不管是未完成的bean还是真正初始化完成bean,都是存储在这个容器中。如果某个bean在初始化期间出现了异常,使未初始化完成的bean存在了最终的容器中,如果在运行过程中出现异常,那排查的难度可想而知。也正是使用一个BEANS容器,使得不同阶段的bean没有区分性,无法确定bean执行的状态和运行阶段。针对以上问题,本节对IOC容器进行拓展。
我们定义三级缓存来存储beans,三级缓存,也就是最基础的,仅仅是对类对象进行实例化,还没有进行依赖注入以及进行aop校验的未完成对象。二级缓存,用来进行对bean是否需要进行aop代理进行判断校验,如果需要进行aop,则将bean进行aop代理,生成代理对象存入二级缓存中,也是用于如果发生循环依赖,且循环依赖的对象需要aop代理时,那注入的对象需要是一个代理对象,会在注入时进行aop的代理,存入二级缓存。一级缓存,最终bean所储备的地方,是真正能够直接使用的对象。我们额外定义一个列表CURRENT_IN_CREATION,用来表示当前正在实例化的对象。
/**
* 设置存放Bean的map 一级缓存
*/
public static final Map<String, Object> BEANS = new ConcurrentHashMap<>(128);
/**
* 存放暂时的aop对象 二级缓存
*/
public static final Map<String, Object> EARLY_BEANS = new HashMap<>(16);
/**
* 存放实例化并未初始化的基础对象 三级缓存
*/
public static final Map<String, Object> BASIC_OBJECTS = new HashMap<>(16);
/**
* 记录当前正在实例化的对象名
*/
public static final List<String> CURRENT_IN_CREATION = new LinkedList<>();
初始开始还是遍历已经收录到CLASSES的类,进行实例化,beanName代表该类定义的名称,如果有 @Component注解,就使用注解上的名称,如果没有注解就使用默认的类名称。
public static void loadBeans(){
Map<Class<? extends Annotation>, Set<Class<?>>> classes = ApplicationContext.CLASSES;
classes.forEach((key, value) -> {
for (Class<?> aClass : value){
String beanName = aClass.getName();
if (key == Component.class){
Component component = aClass.getAnnotation(Component.class);
beanName = StringUtils.isBlank(component.value()) ? beanName : component.value();
}
buildBeans(beanName, aClass);
}
});
}
具体就在于buildBeans(beanName, aClass); 这个方法这是实例化开始的第一步。代码上也标记了比较详细的注释。我们仅对实现类进行实例化,如果是接口,就报出异常。优先从一级缓存中取出完备的bean,如果能够取到,代表这个类在别的时刻已经被实例化并初始化完成了,那么直接取出来使用。如果取不到,那么代表这个bean还没有实例化或还没有初始化完成。接着是从二级缓存和三级缓存中取,优先是从二级缓存中取,如果在二级缓存或三级缓存中取到(区别在于是否已经进行过aop的生成校验-具体下一节完成),并且当前对象正在实例化的流程当中(CURRENT_IN_CREATION),那么将这个未完成的bean返回,解决循环依赖(如果有aop还需要判断aop,下一节aop中会重点提到)。如果在二级缓存和三级缓存中对没有找到对象,代表该对象还没有进行实例化过,那么通过反射对该类进行实例化,并把实例化后的未完成对象加入三级缓存,将beanName加入创建列表CURRENT_IN_CREATION。生成未完成对象后进行属性填充,也就是依赖注入。注入完成后再进行aop代理的判断,看看二级缓存中是否有代理对象了,如果有就返回代理对象。之后再进行初始化步骤,完成后得到的就是一个完整可用的bean了,存入一级缓存,并删除二级缓存和三级缓存中的未完成bean,一级将该beanName移除CURRENT_IN_CREATION。
public static Object buildBeans(String beanName, Class<?> aClass){
if (aClass.isInterface()){
throw new NotFountTargetBeanException("该依赖没有找到目标类:" + beanName);
}
//先从一级缓存中获取bean
Object bean = BEANS.get(beanName);
if (bean != null){
return bean;
}
//再从二三级缓存中获取bean 解决循环依赖
if ( (bean = EARLY_BEANS.get(beanName)) == null && (bean = BASIC_OBJECTS.get(beanName)) == null){
bean = ReflectionUtil.newInstance(aClass);
//
BASIC_OBJECTS.put(beanName, bean);
//将bean的名称存在正在创建的列表中
CURRENT_IN_CREATION.add(beanName);
}else if (CURRENT_IN_CREATION.contains(beanName)){
//判断二三级缓存中的对象是否已经是代理对象
//判断是否可以生成代理对象
//如果是代理生成代理 返回未完成的bean 解决循环依赖
return bean;
}
//如果在二三级缓存中存在 却没有在加载流程中,则继续完成加载。
//进行依赖注入
DependencyInjection.injectionDependency(bean);
//进行aop 判断是否需要aop 判断二级缓存中是否已经有代理对象
//存入一级缓存,删除二三级缓存
BEANS.put(beanName, bean);
EARLY_BEANS.remove(beanName);
BASIC_OBJECTS.remove(beanName);
CURRENT_IN_CREATION.remove(beanName);
return bean;
}
以上介绍了对象实例化的流程,其中也涉及到了依赖注入这个关键的步骤,现在来看看依赖注入是如何执行的。DependencyInjection.injectionDependency(bean);
依赖注入和之前定义的流程相似,本次通过fieldTypeClass(类型)和beanName(名称)贯穿其中,也是遍历该对象中注入的每个依赖,如果是接口就找到实现类,并通过beanName找到对应的fieldTypeClass,最后调用BeanFactory.buildBeans(beanName, fieldTypeClass)对需要的类进行实例化,也就是返回上面的流程。
/**
* 对目标bean进行依赖注入
*
* @param bean 进行依赖注入的bean
*/
public static void injectionDependency(Object bean){
if (bean == null){
return;
}
//遍历该类中每个注入的依赖
for (Field field : bean.getClass().getDeclaredFields()){
//判断参数上是否有@Autowired注解
if (field.isAnnotationPresent(Autowired.class)){
Class<?> fieldTypeClass = field.getType();
String beanName = fieldTypeClass.getName();
beanName = ReflectionUtil.getComponentValue(fieldTypeClass, Component.class, beanName);
//判断是否是接口 如果是接口那存着的就是接口信息,需要顺延找到实现类
if (fieldTypeClass.isInterface()){
Set<Class<?>> subClass = ReflectionUtil.getSubClass(ApplicationContext.getInstance().packageName, fieldTypeClass);
if (subClass.size() == 0){
throw new InterfaceNotExistsImplementException("接口的实现类不存在");
}else if (subClass.size() == 1){
Class<?> aClass = subClass.iterator().next();
fieldTypeClass = aClass;
//实现类的名字肯定在ioc容器中,目前只考虑单个实现累的情况,如果是多个需要用@Qulifer
beanName = ReflectionUtil.getComponentValue(aClass, Component.class, aClass.getName());
}else{
//有两个实现类以上,通过qualifier进行判断指定目标类
Qualifier qualifier = field.getDeclaredAnnotation(Qualifier.class);
if (qualifier == null || StringUtils.isBlank(qualifier.value())){
throw new NotFountTargetBeanException("该依赖没有找到目标类:" + field.toString());
}
for (Class<?> aClass : subClass){
beanName = ReflectionUtil.getComponentValue(aClass, Component.class, aClass.getName());
if (beanName.equals(qualifier.value())){
fieldTypeClass = aClass;
beanName = qualifier.value();
break;
}
}
}
}
//根据类型的类名获取
Object targetBean = BeanFactory.buildBeans(beanName, fieldTypeClass);
if (targetBean == null){
throw new NotFountTargetBeanException("该依赖没有找到目标类:" + field.toString());
}
ReflectionUtil.setReflectionField(bean, field, targetBean);
}
}
}
通过以上对IOC容器的拓展,让一个bean从最初的类到生成一个完整的bean的各个阶段都有进行记录,解决循环依赖-aop等阶段的问题。
上述的代码写的可能还是比较绕,我特意去他人的博客找了(抄)了一张流程图来…可是说是非常清晰明了了。
测试
不测了…改变了一下实现方式,具体的调用没有什么区别。下一节实现aop并整合到实例化这个流程中。