手写一个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 的思路,搞清楚了思路之后,实现起来就非常简单了。
- 扫描指定包下的特定注解比如
@Component
标记的类,并将这些类保存起来。 - 遍历所有被特定注解比如
@Component
标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象。 - 再一次遍历所有被特定注解比如
@Component
标记的类,并获取类中所有的字段,如果类被@Autowired
注解标记的话,就进行第 4 步。 - 通过字段名 key,从bean容器中获取对应的对象 value。
- 判断获取到的对象是否为接口。如果是接口的话,需要获取接口对应的实现类,然后再将指定的实现类的实例化对象通过反射赋值给指定对象。如果不是接口的话,就直接将获取到的对象通过反射赋值给指定对象。
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个后面会用到的方法:
scanAnnotatedClass()
:扫描指定包下的被指定注解标记的类(使用Reflections这个反射框架一行代码即可解决扫描获取指定注解的类)。newInstance()
: 传入 Class 即可返回 Class 对应的对象。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

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java 8 有多牛逼?打破一切你对接口的认知!
前段时间面试了一个 39 岁的程序员,结果不是很理想,没看过的点击这里阅读。 最近也面试一些 Java 程序员,不乏工作 4、5 年经验的,当我问他一些 Java 8 的新特性时,大多却答不上来。 比如下面这道题: 栈长:接口里面可以写方法吗? 小A:当然可以啊,默认就是抽象方法。 栈长:那接口里面可以写实现方法吗? 小A:不可以,所有方法必须是抽象的。 栈长:你确定吗? 小A:确定…… 小A看起来对我的问题有点怀疑人生,心里肯定估摸着,我不会在给他埋了什么坑吧。然后他还是仔细再想了一下,最后还是斩钉截铁的告诉我:接口里面只能写抽象方法,不能写实现方法。 栈长:接口里面是可以写实现方法的,Java 8 开始就可以了,你用过 Java 8 吗? 小A:好吧,看来是我学艺不精,Java 8 有了解一点,比如那个 Lambda 表达式,但实际项目中也没怎么用。 通过和小A的交流,我也看到了许多开发者的问题,虽然开发版本用的是 Java 8,但实际用的还是 Java 8 之前的最基础的语法,对 Java 8 新增的特性一无所知。 Java 8 至 2014 年发布至今,已经过了 6 个年头了,...
- 下一篇
NumPy 广播机制与 C 语言扩展
前两篇主要针对 NumPy 中的基本概念,即高维数组 ndarray 的数据结构以及关键方法作了介绍。本篇重点介绍广播机制以及针对高维数组的轴操作,最后对 NumPy 的 C 语言扩展作了介绍。 广播机制 转置等轴操作 通用函数 ufunc NumPy 之 C 语言扩展 1广播 NumPy 运算通常是在两个数组的元素级别上进行的。最简单情况就是,两个具有完全相同 shape 的数组运算,如下面例子所示, a=np.array([1.0,2.0,3.0])b=np.array([2.0,2.0,2.0])a*b numpy 的广播机制是指在执行算术运算时处理不同 shape 的数组的方式。在一定规则下,较小的数组在较大的数组上广播,从而使得数组具有兼容的 shape。 a=np.array([1.0,2.0,3.0])b=2.0a*b 发现这两个计算的结果是一样的,但第二个是有广播机制在发挥作用。 广播规则 在两个数组上执行运算时,NumPy 比较它们的形状。它从 shape 的最右边开始往左一一比较。如果所有位子比较下来都是下面两种情况之一, 相同位子上的两个数字相等 或者其中之一是 ...
相关文章
文章评论
共有0条评论来说两句吧...