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

JavaGuide 2020-11-13 10:05:17
http 框架 一个 两个 手写


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资源免费分享给大家,大部分内容都是我的原创,少部分来自朋友。

image-20201012105608336

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

版权声明
本文为[JavaGuide]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_34337272/article/details/109066990

  1. [front end -- JavaScript] knowledge point (IV) -- memory leakage in the project (I)
  2. This mechanism in JS
  3. Vue 3.0 source code learning 1 --- rendering process of components
  4. Learning the realization of canvas and simple drawing
  5. gin里获取http请求过来的参数
  6. vue3的新特性
  7. Get the parameters from HTTP request in gin
  8. New features of vue3
  9. vue-cli 引入腾讯地图(最新 api,rocketmq原理面试
  10. Vue 学习笔记(3,免费Java高级工程师学习资源
  11. Vue 学习笔记(2,Java编程视频教程
  12. Vue cli introduces Tencent maps (the latest API, rocketmq)
  13. Vue learning notes (3, free Java senior engineer learning resources)
  14. Vue learning notes (2, Java programming video tutorial)
  15. 【Vue】—props属性
  16. 【Vue】—创建组件
  17. [Vue] - props attribute
  18. [Vue] - create component
  19. 浅谈vue响应式原理及发布订阅模式和观察者模式
  20. On Vue responsive principle, publish subscribe mode and observer mode
  21. 浅谈vue响应式原理及发布订阅模式和观察者模式
  22. On Vue responsive principle, publish subscribe mode and observer mode
  23. Xiaobai can understand it. It only takes 4 steps to solve the problem of Vue keep alive cache component
  24. Publish, subscribe and observer of design patterns
  25. Summary of common content added in ES6 + (II)
  26. No.8 Vue element admin learning (III) vuex learning and login method analysis
  27. Write a mini webpack project construction tool
  28. Shopping cart (front-end static page preparation)
  29. Introduction to the fluent platform
  30. Webpack5 cache
  31. The difference between drop-down box select option and datalist
  32. CSS review (III)
  33. Node.js学习笔记【七】
  34. Node.js learning notes [VII]
  35. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  36. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  37. 【JQuery框架,Java编程教程视频下载
  38. [jQuery framework, Java programming tutorial video download
  39. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  40. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  41. 【Vue,阿里P8大佬亲自教你
  42. 【Vue基础知识总结 5,字节跳动算法工程师面试经验
  43. [Vue, Ali P8 teaches you personally
  44. [Vue basic knowledge summary 5. Interview experience of byte beating Algorithm Engineer
  45. 【问题记录】- 谷歌浏览器 Html生成PDF
  46. [problem record] - PDF generated by Google browser HTML
  47. 【问题记录】- 谷歌浏览器 Html生成PDF
  48. [problem record] - PDF generated by Google browser HTML
  49. 【JavaScript】查漏补缺 —数组中reduce()方法
  50. [JavaScript] leak checking and defect filling - reduce() method in array
  51. 【重识 HTML (3),350道Java面试真题分享
  52. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  53. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  54. [re recognize HTML (3) and share 350 real Java interview questions
  55. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  56. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  57. 【重识 HTML ,nginx面试题阿里
  58. 【重识 HTML (4),ELK原来这么简单
  59. [re recognize HTML, nginx interview questions]
  60. [re recognize HTML (4). Elk is so simple