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

2次阅读

共计 7552 个字符,预计需要花费 19 分钟才能阅读完成。

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/arti…。

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。
我是 Guide 哥,一 Java 后端开发,会一点前端,自在的少年。咱们下期再见!微信搜“JavaGuide”回复“面试突击”支付我整顿的 4 本原创 PDF

正文完
 0