一起来写个SpringBoot[5] — — 实现一个简单的IOC

项目地址:https://github.com/xiaogou446/jsonboot
使用branch:implementIOC
命令行:git checkout implementIOC

构建IOC容器

书接上节,我们大致完成了SpringMVC的相关功能,通过外部的请求进入到处理完请求的链路已经打通,但是还是有着不少的问题。比如在最后通过反射执行请求时,每执行一次,都需要newInstance创建一个对象,那足以造成资源的浪费,以及如果需要在一个类中引用另一个类的方法呢,再创建一次引用类对象?显然不合时宜,所以我们迫切的需要一个管理所需对象的容器,每次调用方法时直接在容器中取出对象,而不是重新创建。本节,就来创建一个简单的IOC容器。

先定义 @Component注解,被该注解标注的类,代表可以被加载到IOC的容器中。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Component {

    String value() default "";

}

加载类,进行范围的类扫描,将标有 @Component注解和 @RestController注解的类在特定的包路径下都扫描出来。

    /**
     * 加载包路径下的类
     *
     * @param packageName 需要加载类的包路径
     */
    public static void loadClass(String packageName){
        AnnotatedClassScanner annotatedScanner = new AnnotatedClassScanner();
        Set<Class<?>> restControllerClasses = annotatedScanner.scan(packageName, RestController.class);
        Set<Class<?>> componentClasses = annotatedScanner.scan(packageName, Component.class);
        ApplicationContext.CLASSES.put(RestController.class, restControllerClasses);
        ApplicationContext.CLASSES.put(Component.class, componentClasses);
    }

定义BEANS用来存储bean,以后需要获取对象的实例直接在BEANS这个map中取,而不是自己实例化。

   /**
     * 设置存放Bean的map
     */
    public static final Map<String, Object> BEANS = new ConcurrentHashMap<>(128);

对扫描到的类进行实例化,实例化后的未完全体收录到BEANS中。当前BEANS存放的是没有进行依赖注入的bean,其中的方法如果在这个时候进行调用都会出现大问题。

    /**
     * 加载bean 根据类生成对应的bean存入BEANS中
     */
    public static void loadBeans(){
        Map<Class<? extends Annotation>, Set<Class<?>>> classes = ApplicationContext.CLASSES;
        classes.forEach((key, value) -> {
            if (key == Component.class){
                for (Class<?> aClass : value){
                    Component component = aClass.getAnnotation(Component.class);
                    //获取beanName,如果Component上有设置则使用Component的value,不然使用默认的Class名称。
                    String beanName = StringUtils.isBlank(component.value()) ? aClass.getName() : component.value();
                    Object instance = ReflectionUtil.newInstance(aClass);
                    BEANS.put(beanName, instance);
                }
            }

            if (key == RestController.class){
                for (Class<?> aClass : value){
                    Object instance = ReflectionUtil.newInstance(aClass);
                    BEANS.put(aClass.getName(), instance);
                }
            }
        });
    }

依赖注入

当我们将实例化后的bean存放到BEANS中,其中的每个bean都需要进行依赖注入才能称作是完整的bean。

定义依赖注入的注解 @Autowired,当注入的字段被标注 @Autowired时,代表会从IOC容器中取出这个bean,执行注入。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface Autowired {
}

依赖注入的过程其实很简单,遍历BEANS中的每个元素bean,再遍历元素bean中注入的依赖,找到依赖上是否有标注 @Autowired注解,如果有标注,且不是接口,则通过类名去BEANS中找到对应的bean注入,如果是接口,那么需要扫描范围内的实现类,获取实现类的名称从BEANS取得进行注入。

    /**
     * 执行依赖注入 @Autowired
     */
    public static void loadDependency(String packageName){
        Map<String, Object> beanMap = BeanFactory.BEANS;
        //遍历每个bean
        beanMap.forEach((beanName, bean) -> {
            for (Field field : bean.getClass().getDeclaredFields()){
                if (field.isAnnotationPresent(Autowired.class)){
                    String className;
                    Class<?> fieldTypeClass = field.getType();
                    //判断是否是接口 如果是接口那存着的就是接口信息,需要顺延找到实现类
                    if (fieldTypeClass.isInterface()){
                        Set<Class<?>> subClass = getSubClass(packageName, fieldTypeClass);
                        //实现类的名字肯定在ioc容器中,目前只考虑单个实现累的情况,如果是多个需要用@Qulifer
                        className = subClass.iterator().next().getName();
                    }else{
                        className = fieldTypeClass.getName();
                    }
                    //根据类型的类名获取
                    Object targetBean = beanMap.get(className);
                    if (targetBean != null){
                        ReflectionUtil.setReflectionField(bean, field, targetBean);
                    }
                }
            }
        });
    }
    /**
     * 获取接口的对应实现类
     *
     * @param packageName 包名
     * @param interfaceClass 接口
     * @return 接口的实现类
     */
    public static Set<Class<?>> getSubClass(String packageName, Class<?> interfaceClass) {
        Reflections reflections = new Reflections(packageName);
        return reflections.getSubTypesOf((Class<Object>) interfaceClass);
    }

在SpringMVC方法的执行时,也优先从BEANS中取对象。

   public static Object executeMethod(Method method, Object... args) {
        Object result = null;
        try {
            String beanName = null;
            Object targetObject;
            //先判断是否已经生成该对象了 直接在ioc的容器中取出来
            Class<?> targetClass = method.getDeclaringClass();
            if (targetClass.isAnnotationPresent(RestController.class)){
                beanName = targetClass.getName();
            }
            if (targetClass.isAnnotationPresent(Component.class)){
                Component component = targetClass.getAnnotation(Component.class);
                beanName = StringUtils.isBlank(component.value()) ? targetClass.getName() : component.value();
            }
            if (StringUtils.isNotEmpty(beanName)){
                targetObject = BeanFactory.BEANS.get(beanName);
            }else{
                targetObject = method.getDeclaringClass().newInstance();
            }
            // 调用对象的方法
            result = method.invoke(targetObject, args);
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
        return result;
    }

以上就完成了基础IOC容器的实现,最后BEANS中存储的都是初始化完成的bean,可以进行直接的使用。


使用branch:feature/impQualifier
命令行:git checkout feature/impQualifier

实现@Qualifier

在进行bean的依赖注入时,如果注入的是接口,且有多个不同的实现类,这样如果不通过 @Qualifier指定对应注入名称,那么就会出现异常。这时通过 @Qualifier指定对应的名称,即会获取到目标实现类,进行注入。

定义 @Qualifier注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface Qualifier {

    String value() default "";

}

作用在依赖注入的地方,如果注入的依赖是接口,获取他的实现类个数,如果有大于两个,则通过@Qualifier注解进行获取定义的bean的名称。进行依赖注入。

    /**
     * 执行依赖注入 @Autowired
     */
    public static void loadDependency(String packageName){
        Map<String, Object> beanMap = BeanFactory.BEANS;
        //遍历每个bean
        beanMap.forEach((beanName, bean) -> {
            for (Field field : bean.getClass().getDeclaredFields()){
                if (field.isAnnotationPresent(Autowired.class)){
                    Class<?> fieldTypeClass = field.getType();
                    String className = fieldTypeClass.getName();
                    //判断是否是接口 如果是接口那存着的就是接口信息,需要顺延找到实现类
                    if (fieldTypeClass.isInterface()){
                        Set<Class<?>> subClass = ReflectionUtil.getSubClass(packageName, fieldTypeClass);
                        if (subClass.size() == 0){
                            throw new InterfaceNotExistsImplementException("接口的实现类不存在");
                        }else if (subClass.size() == 1){
                            //实现类的名字肯定在ioc容器中,目前只考虑单个实现累的情况,如果是多个需要用@Qulifer
                            className = subClass.iterator().next().getName();
                        }else{
                        	//获取注解@Qualifier上的值
                            Qualifier qualifier = field.getDeclaredAnnotation(Qualifier.class);
                            className = StringUtils.isBlank(qualifier.value()) ? beanName : qualifier.value();
                        }
                    }
                    //根据类型的类名获取
                    Object targetBean = beanMap.get(className);
                    if (targetBean == null){
                        throw new NotFountTargetBeanException("该依赖没有找到目标类:" + field.toString());
                    }
                    ReflectionUtil.setReflectionField(bean, field, targetBean);
                }
            }
        });
    }

测试

在这里插入图片描述

在这里插入图片描述
以上实现了一个简单的IOC容器,个人觉得问题还是比较多的,在下一节中进行修改。

下一节:一起来写个SpringBoot[6] — — 拓展IOC的实现


版权声明:本文为qq_41762594原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。