手写一个HTTP框架:两个类实现基本的IoC功能

jsoncat: 仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架

国庆节的时候,我就已经把 jsoncat 的 IoC 功能给写了,具体可以看这篇文章《手写“SpringBoot”近况:IoC模块已经完成》 。

今天这篇文章就来简单分享一下自己写 IoC 的思路与具体的代码实现。

在这里插入图片描述

IoC (Inverse of Control:控制反转)AOP(Aspect-Oriented Programming:面向切面编程) 可以说是 Spring 框架提供的最核心的两个功能。但凡是了解过 Spring 的小伙伴,那肯定对这个两个概念非常非常了解。不了解的小伙伴,可以查看《面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?》这篇通俗易懂的文章。

考虑到这篇文章要手写 Spring 框架的 IoC 功能,所以,我这里还是简单介绍一下 IoC 。如果你不太清楚 IoC 这个概念,一定要搞懂之后再看后面具体的代码实现环节。

IoC 介绍

IoC(Inverse of Control:控制反转)是一种设计思想,也就是 将原本在程序中手动创建对象的控制权交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。

IoC 容器

IoC 容器是用来实现 IoC 的载体,被管理的对象就被存放在IoC容器中。IoC 容器在 Spring 中实际上就是个Map(key,value),Map 中存放了各种被管理的对象。

IoC 解决了什么问题

将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

IoC 和 DI 别再傻傻分不清楚

IoC(Inverse of Control:控制反转)是一种设计思想 或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种被管理的对象。

IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。

并且,老马(Martin Fowler)在一篇文章中提到将 IoC 改名为 DI,原文如下,原文地址:https://martinfowler.com/articles/injection.html

IoC实现思路

📝注意 :以下思路未涉及解决循环依赖的问题!

开始代码实现之前,我们先简单聊聊实现 IoC 的思路,搞清楚了思路之后,实现起来就非常简单了。

  1. 扫描指定包下的特定注解比如@Component标记的类,并将这些类保存起来。
  2. 遍历所有被特定注解比如@Component标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象。
  3. 再一次遍历所有被特定注解比如@Component标记的类,并获取类中所有的字段,如果类被 @Autowired 注解标记的话,就进行第 4 步。
  4. 通过字段名 key,从bean容器中获取对应的对象 value。
  5. 判断获取到的对象是否为接口。如果是接口的话,需要获取接口对应的实现类,然后再将指定的实现类的实例化对象通过反射赋值给指定对象。如果不是接口的话,就直接将获取到的对象通过反射赋值给指定对象。

IoC 实现核心代码

核心注解

@Autowired :注解对象

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

}

@Component :声明对象被IoC容器管理


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
    String name() default "";
}

@Qualifier: 指定注入的bean(当接口有多个实现类的时候需要使用)

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
    String value() default "";
}

工具类

简单封装一个反射工具类。工具类包含3个后面会用到的方法:

  1. scanAnnotatedClass() :扫描指定包下的被指定注解标记的类(使用Reflections这个反射框架一行代码即可解决扫描获取指定注解的类)。
  2. newInstance() : 传入 Class 即可返回 Class 对应的对象。
  3. setField() :为对象的指定字段赋值。
@Slf4j
public class ReflectionUtil {
    /**
     * scan the classes marked by the specified annotation in the specified package
     *
     * @param packageName specified package name
     * @param annotation  specified annotation
     * @return the classes marked by the specified annotation in the specified package
     */
    public static Set<Class<?>> scanAnnotatedClass(String packageName, Class<? extends Annotation> annotation) {
        Reflections reflections = new Reflections(packageName, new TypeAnnotationsScanner());
        Set<Class<?>> annotatedClass = reflections.getTypesAnnotatedWith(annotation, true);
        log.info("The number of class Annotated with  @RestController :[{}]", annotatedClass.size());
        return annotatedClass;
    }

    /**
     * create object instance through class
     *
     * @param cls target class
     * @return object created by the target class
     */
    public static Object newInstance(Class<?> cls) {
        Object instance = null;
        try {
            instance = cls.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            log.error("new instance failed", e);
        }
        return instance;
    }

    /**
     * set the value of a field in the object
     *
     * @param obj   target object
     * @param field target field
     * @param value the value assigned to the field
     */
    public static void setField(Object obj, Field field, Object value) {

        field.setAccessible(true);
        try {
            field.set(obj, value);
        } catch (IllegalAccessException e) {
            log.error("set field failed", e);
            e.printStackTrace();
        }

    }
  
}

根据实现思路写代码

📝注意 :以下代码未涉及解决循环依赖的问题!以下是 IoC 实现的核心代码,完整代码地址:https://github.com/Snailclimb/jsoncat

1.扫描指定包下的特定注解比如@Component标记的类,并将这些类保存起来。

扫描指定注解@RestController@Component并保存起来:

public class ClassFactory {
    public static final Map<Class<? extends Annotation>, Set<Class<?>>> CLASSES = new ConcurrentHashMap<>();
    //1.扫描指定包下的特定注解比如`@Component`标记的类,并将这些类保存起来
    public static void loadClass(String packageName) {
        Set<Class<?>> restControllerSets = ReflectionUtil.scanAnnotatedClass(packageName, RestController.class);
        Set<Class<?>> componentSets = ReflectionUtil.scanAnnotatedClass(packageName, Component.class);
        CLASSES.put(RestController.class, restControllerSets);
        CLASSES.put(Component.class, componentSets);
    }
}

2.遍历所有被特定注解比如@Component标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象。

public final class BeanFactory {
    public static final Map<String, Object> BEANS = new ConcurrentHashMap<>(128);

    public static void loadBeans() {

        // 2.遍历所有被特定注解比如 @Component 标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象
        ClassFactory.CLASSES.forEach((annotation, classes) -> {
            if (annotation == Component.class) {
                //将bean实例化, 并放入bean容器中
                for (Class<?> aClass : classes) {
                    Component component = aClass.getAnnotation(Component.class);
                    String beanName = "".equals(component.name()) ? aClass.getName() : component.name();
                    Object obj = ReflectionUtil.newInstance(aClass);
                    BEANS.put(beanName, obj);
                }
            }

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

3.再一次遍历所有被特定注解比如@Component标记的类,并获取类中所有的字段,如果类被 @Autowired 注解标记的话,就进行第 4 步。

public class DependencyInjection {

    public static void dependencyInjection(String packageName) {
        Map<String, Object> beans = BeanFactory.BEANS;
        if (beans.size() == 0) return;
        //3.再一次遍历所有被特定注解比如 @Component 标记的类,并获取类中所有的字段,如果类被 `@Autowired` 注解标记的话,就进行第 4 步。
        // 3.1.遍历bean容器中的所有对象
        beans.values().forEach(bean -> {
            // 3.2.获取对象所属的类声明的所有字段/属性
            Field[] beanFields = bean.getClass().getDeclaredFields();
            if (beanFields.length == 0) return;
            //3.3.遍历对象所属的类声明的所有字段/属性
            for (Field beanField : beanFields) {
              //3.4.判断字段是否被 @Autowired 注解标记
                if (beanField.isAnnotationPresent(Autowired.class)) {
                    //4.通过字段名 key,从bean容器中获取对应的对象 value。
                    //4.1.字段对应的类型
                    Class<?> beanFieldClass = beanField.getType();
                    //4.2.字段对应的类名
                    String beanName = beanFieldClass.getName();
                    if (beanFieldClass.isAnnotationPresent(Component.class)) {
                        Component component = beanFieldClass.getAnnotation(Component.class);
                        beanName = "".equals(component.name()) ? beanFieldClass.getName() : component.name();
                    }
                    //4.3.从bean容器中获取对应的对象
                    Object beanFieldInstance = beans.get(beanName);
                    //5.判断获取到的对象是否为接口。如果是接口的话,需要获取接口对应的实现类,然后再将指定的实现类的实例化对象通过反射赋值给指定对象。如果不是接口的话,就直接将获取到的对象通过反射赋值给指定对象。
                    if (beanFieldClass.isInterface()) {
                        //如果是接口,获取接口对应的实现类
                        Set<Class<?>> subClasses = getSubClass(packageName, beanFieldClass);
                        //没有实现类的话就抛出异常
                        if (subClasses.size() == 0) {
                            throw new InterfaceNotHaveImplementedClassException("interface does not have implemented class exception");
                        }
                        //实现类只有一个话,直接获取
                        if (subClasses.size() == 1) {
                            Class<?> aClass = subClasses.iterator().next();
                            beanFieldInstance = ReflectionUtil.newInstance(aClass);
                        }
                        //实现类多与一个的话,根据 Qualifier 注解的值获取
                        if (subClasses.size() > 1) {
                            Class<?> aClass = subClasses.iterator().next();
                            Qualifier qualifier = beanField.getDeclaredAnnotation(Qualifier.class);
                            beanName = qualifier == null ? aClass.getName() : qualifier.value();
                            beanFieldInstance = beans.get(beanName);
                        }

                    }
                    // 如果最后获取到的字段对象为null,就抛出异常
                    if (beanFieldInstance == null) {
                        throw new CanNotDetermineTargetBeanException("can not determine target bean");
                    }
                    //通过反射设置指定对象中的指定字段的值
                    ReflectionUtil.setField(bean, beanField, beanFieldInstance);
                }
            }
        });


    }

    /**
     * 获取接口对应的实现类
     */
    @SuppressWarnings("unchecked")
    public static Set<Class<?>> getSubClass(String packageName, Class<?> interfaceClass) {
        Reflections reflections = new Reflections(packageName);
        return reflections.getSubTypesOf((Class<Object>) interfaceClass);
    }
}

我整理了一份优质原创PDF资源免费分享给大家,大部分内容都是我的原创,少部分来自朋友。

<img src="https://cdn.jsdelivr.net/gh/javaguide-tech/blog-images/2020-10/image-20201012105544846.png" style="zoom:50%;" />

<img src="https://cdn.jsdelivr.net/gh/javaguide-tech/blog-images/2020-10/image-20201012105608336.png" alt="image-20201012105608336" style="zoom:50%;" />

下载地址:https://cowtransfer.com/s/fbed14f0c22a4d

作者介绍: Github 70k Star 项目 JavaGuide(公众号同名) 作者。每周都会在公众号更新一些自己原创干货。公众hao后台回复“1”领取Java工程师必备学习资料+面试突击pdf。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352