jsoncat: 仿 Spring Boot 但不一樣於 Spring Boot 的一個輕量級的 HTTP 框架html
國慶節的時候,我就已經把 jsoncat 的 IoC 功能給寫了,具體能夠看這篇文章《手寫「SpringBoot」近況:IoC模塊已經完成》 。前端
今天這篇文章就來簡單分享一下本身寫 IoC 的思路與具體的代碼實現。java
IoC (Inverse of Control:控制反轉) 和 AOP(Aspect-Oriented Programming:面向切面編程) 能夠說是 Spring 框架提供的最核心的兩個功能。但凡是瞭解過 Spring 的小夥伴,那確定對這個兩個概念很是很是瞭解。不瞭解的小夥伴,能夠查看《面試被問了幾百遍的 IoC 和 AOP ,還在傻傻搞不清楚?》這篇通俗易懂的文章。git
考慮到這篇文章要手寫 Spring 框架的 IoC 功能,因此,我這裏仍是簡單介紹一下 IoC 。若是你不太清楚 IoC 這個概念,必定要搞懂以後再看後面具體的代碼實現環節。github
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 的話,你只須要配置好,而後在須要的地方引用就好了,這大大增長了項目的可維護性且下降了開發難度。json
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