1. 程式人生 > >手寫IOC實現過程

手寫IOC實現過程

##### 一.手寫ioc前基礎知識 **1.什麼是IOC(Inversion of Control 控制反轉)?** IoC不是一種技術,只是**一種思想**,一個重要的面向物件程式設計的法則,它能指導我們如何設計出**鬆耦合、更優良**的程式。傳統應用程式都是由我們在類內部主動建立依賴物件,從而導致類與類之間高耦合,難於測試;有了IoC容器後,**把建立和查詢依賴物件的控制權交給了容器**,**由容器進行注入組合物件**,所以物件與物件之間是鬆散耦合,這樣也方便測試,利於功能複用,更重要的是使得程式的整個體系結構變得非常靈活。 其實IoC對程式設計帶來的最大改變不是從程式碼上,而是從思想上,發生了“主從換位”的變化。應用程式原本是老大,要獲取什麼資源都是主動出擊,但是在IoC/DI思想中,應用程式就變成被動的了,被動的等待IoC容器來建立並注入它所需要的資源了。 IoC很好的體現了面向物件設計法則之一—— 好萊塢法則:“**別找我們,我們找你**”;即由**IoC容器幫物件找相應的依賴物件並注入,而不是由物件主動去找。** **2.什麼是DI(Dependency Injection 依賴注入)?** DI—Dependency Injection,即“依賴注入”:是元件之間依賴關係由容器在執行期決定,形象的說,即由容器動態的將某個依賴關係注入到元件之中。依賴注入的目的並非為軟體系統帶來更多功能,而是為了提升元件重用的頻率,併為系統搭建一個靈活、可擴充套件的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何程式碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。 **3.IOC和DI什麼關係?** IoC和DI由什麼關係呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制物件這一個層面,很難讓人想到誰來維護物件關係),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,依賴注入”明確描述了“被注入物件依賴IoC容器配置依賴物件”。 **4.什麼是依賴?** 傳統應用程式設計中所說的依賴一般指“類之間的關係”,那先讓我們複習一下類之間的關係: **泛化:**表示類與類之間的繼承關係、介面與介面之間的繼承關係; **實現:**表示類對介面的實現; **依賴:**當類與類之間有使用關係時就屬於依賴關係,不同於關聯關係,依賴不具有“擁有關係”,而是一種“相識關係”,只在某個特定地方(比如某個方法體內)才有關係。 **關聯:**表示類與類或類與介面之間的依賴關係,表現為“擁有關係”;具體到程式碼可以用例項變數來表示; **聚合:**屬於是關聯的特殊情況,體現部分-整體關係,是一種弱擁有關係;整體和部分可以有不一樣的生命週期;是一種弱關聯; **組合:**屬於是關聯的特殊情況,也體現了體現部分-整體關係,是一種強“擁有關係”;整體與部分有相同的生命週期,是一種強關聯; Spring IoC容器的依賴有兩層含義:Bean依賴容器和容器注入Bean的依賴資源: **Bean依賴容器:**也就是說Bean要依賴於容器,這裡的依賴是指容器負責建立Bean並管理Bean的生命週期,正是由於由容器來控制建立Bean並注入依賴,也就是控制權被反轉了,這也正是IoC名字的由來,此處的有依賴是指Bean和容器之間的依賴關係。 **容器注入Bean的依賴資源**:容器負責注入Bean的依賴資源,依賴資源可以是Bean、外部檔案、常量資料等,在Java中都反映為物件,並且由容器負責組裝Bean之間的依賴關係,此處的依賴是指Bean之間的依賴關係,可以認為是傳統類與類之間的“關聯”、“聚合”、“組合”關係。 **5.依賴注入的好處?** **動態替換Bean依賴物件,程式更靈活**:替換Bean依賴物件,無需修改原始檔:應用依賴注入後,由於可以採用配置檔案方式實現,從而能隨時動態的替換Bean的依賴物件,無需修改java原始檔; **更好實踐面向介面程式設計,程式碼更清晰:**在Bean中只需指定依賴物件的介面,介面定義依賴物件完成的功能,通過容器注入依賴實現; **更好實踐優先使用物件組合,而不是類繼承:**因為IoC容器採用注入依賴,也就是組合物件,從而更好的實踐物件組合。 - 採用物件組合,Bean的功能可能由幾個依賴Bean的功能組合而成,其Bean本身可能只提供少許功能或根本無任何功能,全部委託給依賴Bean,物件組合具有動態性,能更方便的替換掉依賴Bean,從而改變Bean功能; - 而如果採用類繼承,Bean沒有依賴Bean,而是採用繼承方式新增新功能,,而且功能是在編譯時就確定了,不具有動態性,而且採用類繼承導致Bean與子Bean之間高度耦合,難以複用。 **增加Bean可複用性**:依賴於物件組合,Bean更可複用且複用更簡單; **降低Bean之間耦合**:由於我們完全採用面向介面程式設計,在程式碼中沒有直接引用Bean依賴實現,全部引用介面,而且不會出現顯示的建立依賴物件程式碼,而且這些依賴是由容器來注入,很容易替換依賴實現類,從而降低Bean與依賴之間耦合; **程式碼結構更清晰:**要應用依賴注入,程式碼結構要按照規約方式進行書寫,從而更好的應用一些最佳實踐,因此程式碼結構更清晰。 從以上我們可以看出,其實依賴注入只是一種裝配物件的手段,設計的類結構才是基礎,如果設計的類結構不支援依賴注入,Spring IoC容器也注入不了任何東西,從而從根本上說**“如何設計好類結構才是關鍵,依賴注入只是一種裝配物件手段”。** ##### **二. 手寫IOC** 標記配置分為**集中式管理和分散式管理**,即xml檔案方式和註解方式配置元資料資訊,手寫ioc我採用註解配置元資料方式來實現ioc的大致執行過程。 **1.定義元資料配置資訊** @Component/@Controller/@Repository@Service 掃描什麼樣的class裝載到ioc容器中進行管理。 @Autowired 什麼樣的物件進行依賴注入。 ```java  /** * @Classname Component * @Description * @Date 2020/8/6 14:54 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { } ``` ```java /** * @Classname Controller * @Description TODO * @Date 2020/8/6 14:56 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { } ``` ```java /** * @Classname Repository * @Description TODO * @Date 2020/8/6 14:58 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Repository { } ``` ```java /** * @Classname Service * @Description TODO * @Date 2020/8/6 14:57 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Service { } ``` ```java /** * @Classname Autowired * @Description 自動注入註解 * @Date 2020/8/6 14:28 * @Created by zhangtianci */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { String value() default ""; } ``` **2.實現ioc容器** 核心功能:載入被配置標記的class檔案,交給ioc容器管理。 ```java package org.simplespring.core; import lombok.extern.slf4j.Slf4j; import org.simplespring.core.annotation.Component; import org.simplespring.core.annotation.Controller; import org.simplespring.core.annotation.Repository; import org.simplespring.core.annotation.Service; import org.simplespring.util.ClassUtil; import org.simplespring.util.ValidationUtil; import java.lang.annotation.Annotation; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @Classname BeanContainer * @Description bean容器 *

* BeanContainer應該是單例的 採用內部列舉的方式實現。 * * 應該擁有的例項方法: * 1.boolean isLoad() 是否載入 * 2.int getSize() 獲取bean個數 * 3.loadBeans(String packageName) 根據包名載入所有被配置標記的class檔案/ * 4.獲取/刪除/增加bean及提供一些便利的方法 * * @Date 2020/8/6 14:41 * @Created by zhangtianci */ @Slf4j public class BeanContainer { /** * 存放所有被配置標記(xml/註解)的class,並new出一個例項物件bean * 存放在一個map中 */ private final Map, Object> beanMap = new ConcurrentHashMap<>(); /** * 載入bean的註解列表 */ private static final List> BEAN_ANNOTATION = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class); /** * 是否已經載入過bean */ private boolean loaded = false; /** * 獲取bean容器 * * @return */ public static BeanContainer getInstance() { return ContainerHolder.HOLDER.instance; } private enum ContainerHolder { HOLDER; private BeanContainer instance; ContainerHolder() { instance = new BeanContainer(); } } /** * 是否已經載入過bean */ public boolean isLoad(){ return loaded; } /** * 獲取容器中bean的個數 */ public int getSize(){ return beanMap.size(); } /** * 載入指定路徑下的所有class檔案 * 並將所有被配置標記(xml/註解)的class物件和new出一個例項物件放入容器 */ public synchronized void loadBeans(String packageName){ //判斷容器是否已經載入過 if (loaded){ log.warn("Container has loaded!"); return; } // 匯出所有的class物件 Set> classSet = ClassUtil.extractPackageClass(packageName); //為空直接return if (ValidationUtil.isEmpty(classSet)) { log.warn("extract nothing from packageName" + packageName); return; } classSet.stream().forEach(clazz -> { for (Class annotationClazz : BEAN_ANNOTATION) { if (clazz.isAnnotationPresent(annotationClazz)){ //將目標類本身作為鍵,目標類的例項作為值,放入到beanMap中 beanMap.put(clazz, ClassUtil.newInstance(clazz, true)); } } }); loaded = true; } /** * 新增一個class物件及其Bean例項 * * @param clazz Class物件 * @param bean Bean例項 * @return 原有的Bean例項, 沒有則返回null */ public Object addBean(Class clazz, Object bean) { return beanMap.put(clazz, bean); } /** * 移除一個IOC容器管理的物件 * * @param clazz Class物件 * @return 刪除的Bean例項, 沒有則返回null */ public Object removeBean(Class clazz) { return beanMap.remove(clazz); } /** * 根據Class物件獲取Bean例項 * * @param clazz Class物件 * @return Bean例項 */ public Object getBean(Class clazz) { return beanMap.get(clazz); } /** * 獲取容器管理的所有Class物件集合 * * @return Class集合 */ public Set> getClasses(){ return beanMap.keySet(); } /** * 獲取所有Bean集合 * * @return Bean集合 */ public Set getBeans(){ return new HashSet<>( beanMap.values()); } /** * 根據註解篩選出Bean的Class集合 * * @param annotation 註解 * @return Class集合 */ public Set> getClassesByAnnotation(Class annotation){ //1.獲取beanMap的所有class物件 Set> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ log.warn("nothing in beanMap"); return null; } //2.通過註解篩選被註解標記的class物件,並新增到classSet裡 Set> classSet = new HashSet<>(); for(Class clazz : keySet){ //類是否有相關的註解標記 if(clazz.isAnnotationPresent(annotation)){ classSet.add(clazz); } } return classSet.size() > 0? classSet: null; } /** * 通過介面或者父類獲取實現類或者子類的Class集合,不包括其本身 * * @param interfaceOrClass 介面Class或者父類Class * @return Class集合 */ public Set> getClassesBySuper(Class interfaceOrClass){ //1.獲取beanMap的所有class物件 Set> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ log.warn("nothing in beanMap"); return null; } //2.判斷keySet裡的元素是否是傳入的介面或者類的子類,如果是,就將其新增到classSet裡 Set> classSet = new HashSet<>(); for(Class clazz : keySet){ //判斷keySet裡的元素是否是傳入的介面或者類的子類 if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){ classSet.add(clazz); } } return classSet.size() > 0? classSet: null; } } ``` **3.定義依賴注入器** 核心功能:掃描被管理的bean物件,進行依賴注入。 ```java package org.simplespring.inject; import lombok.extern.slf4j.Slf4j; import org.simplespring.core.BeanContainer; import org.simplespring.inject.annotation.Autowired; import org.simplespring.util.ClassUtil; import org.simplespring.util.ValidationUtil; import java.lang.reflect.Field; import java.util.Set; /** * @Classname DependencyInjector * @Description 依賴注入器 * @Date 2020/8/6 14:38 * @Created by zhangtianci */ @Slf4j public class DependencyInjector { /** * 擁有一個Bean容器 */ private BeanContainer beanContainer; public DependencyInjector(){ beanContainer = BeanContainer.getInstance(); } /** * 執行依賴注入 * * 1.遍歷Bean容器中所有的Class物件 * 2.遍歷Class物件的所有成員變數 * 3.找出被Autowired標記的成員變數 * 4.獲取這些成員變數的型別 * 5.獲取這些成員變數的型別在容器裡對應的例項 * 6.通過反射將對應的成員變數例項注入到成員變數所在類的例項裡 */ public void doIoc(){ if(ValidationUtil.isEmpty(beanContainer.getClasses())){ log.warn("empty classset in BeanContainer"); return; } //1.遍歷Bean容器中所有的Class物件 for(Class clazz : beanContainer.getClasses()){ //2.遍歷Class物件的所有成員變數 Field[] fields = clazz.getDeclaredFields(); if (ValidationUtil.isEmpty(fields)){ continue; } for(Field field : fields){ //3.找出被Autowired標記的成員變數 if(field.isAnnotationPresent(Autowired.class)){ Autowired autowired = field.getAnnotation(Autowired.class); String autowiredValue = autowired.value(); //4.獲取這些成員變數的型別 Class fieldClass = field.getType(); //5.獲取這些成員變數的型別在容器裡對應的例項 Object fieldValue = getFieldInstance(fieldClass, autowiredValue); if(fieldValue == null){ throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClass.getName() + " autowiredValue is : " + autowiredValue); } else { //6.通過反射將對應的成員變數例項注入到成員變數所在類的例項裡 Object targetBean = beanContainer.getBean(clazz); ClassUtil.setField(field, targetBean, fieldValue, true); } } } } } /** * 根據Class在beanContainer裡獲取其例項或者實現類 */ private Object getFieldInstance(Class fieldClass, String autowiredValue) { Object fieldValue = beanContainer.getBean(fieldClass); if (fieldValue != null){ return fieldValue; } else { Class implementedClass = getImplementedClass(fieldClass, autowiredValue); if(implementedClass != null){ return beanContainer.getBean(implementedClass); } else { return null; } } } /** * 獲取介面的實現類 */ private Class getImplementedClass(Class fieldClass, String autowiredValue) { Set> classSet = beanContainer.getClassesBySuper(fieldClass); if(!ValidationUtil.isEmpty(classSet)){ if(ValidationUtil.isEmpty(autowiredValue)){ if(classSet.size() == 1){ return classSet.iterator().next(); } else { //如果多於兩個實現類且使用者未指定其中一個實現類,則丟擲異常 throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + " please set @Autowired's value to pick one"); } } else { for(Class clazz : classSet){//別名採用clazz.getSimpleName()簡單實現 if(autowiredValue.equals(clazz.getSimpleName())){ return clazz; } } } } return null; } } ``` **4.工具包** 作為框架 類載入/反射/校驗等功能。 ```java package org.simplespring.util; import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.FileFilter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * @Classname ClassUtil * @Description * @Date 2020/8/8 15:20 * @Created by zhangtianci */ @Slf4j public class ClassUtil { public static final String FILE_PROTOCOL = "file"; /** * 獲取類載入器 * 因為專案部署在tomcat等一些web容器中 * 這些web容器載入部署在裡面的apps 所以需要自定義classLoader去載入class檔案 * 當我們寫框架需要去通過類載入器去載入class檔案時 就需要通過Thread.currentThread().getContextClassLoader() * 拿到tomcat的自定義的classLoader去載入專案裡面的class檔案 * 詳情 參考我寫的classLoader載入相關文章 * @return */ public static ClassLoader getClassLoader(){ return Thread.currentThread().getContextClassLoader(); } /** * 通過包名載入class * * 點進去Class.forName()方法進去看看 * * @CallerSensitive * public static Class forName(String className) * throws ClassNotFoundException { * Class caller = Reflection.getCallerClass(); * return forName0(className, true, ClassLoader.getClassLoader(caller), caller); * } * * 獲取到呼叫這個方法的class物件 然後用這個class物件的classLoader去載入這個class檔案 * 所以歸根結底是 拿到tomcat的自定義的classLoader去載入的class檔案 * 所以沒問題 * @param className class全名=package + 類名 * @return */ public static Class loadClass(String className){ try { return Class.forName(className); } catch (ClassNotFoundException e) { log.error("loadClass failed!",e); throw new RuntimeException(e); } } /** * 通過一個class物件例項化一個例項物件(通過預設構造器) * @param clazz * @param accessible * @param * @return */ public static T newInstance(Class clazz, boolean accessible){ try { //clazz.getDeclaredConstructor() 獲取指定引數類表的構造器 //這裡獲取預設構造器 Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(accessible); return (T)constructor.newInstance(); } catch (Exception e) { log.error("new instance failed!"); throw new RuntimeException(e); } } /** * 設定類的屬性值 * * @param field 成員變數 * @param target 類例項 * @param value 成員變數的值 * @param accessible 是否允許設定私有屬性 */ public static void setField(Field field, Object target, Object value, boolean accessible){ field.setAccessible(accessible); try { field.set(target, value); } catch (IllegalAccessException e) { log.error("setField error", e); throw new RuntimeException(e); } } /** * 載入包路徑下的所有的class檔案 * 將class物件放進set集合中 * * 1.獲取classLoader載入器 * 2.遞迴載入路徑下的所有class檔案 * @param packageName * @return */ public static Set> extractPackageClass(String packageName){ //1.獲取classLoader載入器 ClassLoader classLoader = getClassLoader(); //2.獲取資原始檔的的url URL url = classLoader.getResource(packageName.replace(".","/")); if (url == null){ log.warn("unable to retrieve anything from package: " + packageName); return null; } //3.依據不同的資源型別,採用不同的方式獲取資源的集合 Set> classSet = null; //過濾出檔案型別的資源 if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){ classSet = new HashSet>(); File packageDirectory = new File(url.getPath()); extractClassFile(classSet, packageDirectory, packageName); } //TODO 此處可以加入針對其他型別資源的處理 return null; } /** * 遞迴載入包路徑下的class檔案 * @param classSet * @param packageDirectory * @param packageName */ private static void extractClassFile(Set> classSet, File packageDirectory, String packageName) { if(!packageDirectory.isDirectory()){ return; } //如果是一個資料夾,則呼叫其listFiles方法獲取資料夾下的檔案或資料夾 File[] files = packageDirectory.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { if(pathname.isDirectory()){ return true; } else{ //獲取檔案的絕對值路徑 String absoluteFilePath = pathname.getAbsolutePath(); if(absoluteFilePath.endsWith(".class")){ //若是class檔案,則直接載入 addToClassSet(absoluteFilePath); } } return false; } //根據class檔案的絕對值路徑,獲取並生成class物件,並放入classSet中 private void addToClassSet(String absoluteFilePath) { //1.從class檔案的絕對值路徑裡提取出包含了package的類名 //如/Users/zhangtc/springframework/simple-spring/target/classes/com/zhangtianci/entity/dto/MainPageInfoDTO.class //需要弄成com.zhangtianci.entity.dto.MainPageInfoDTO absoluteFilePath = absoluteFilePath.replace(File.separator, "."); String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName)); className = className.substring(0, className.lastIndexOf(".")); //2.通過反射機制獲取對應的Class物件並加入到classSet裡 Class targetClass = loadClass(className); classSet.add(targetClass); } }); if(files == null){ return; } Arrays.stream(files).forEach( file -> { extractClassFile(classSet,file,packageName); }); } } ``` ```java package org.simplespring.util; import java.util.Collection; import java.util.Map; public class ValidationUtil { /** * String是否為null或"" * * @param obj String * @return 是否為空 */ public static boolean isEmpty(String obj) { return (obj == null || "".equals(obj)); } /** * Array是否為null或者size為0 * * @param obj Array * @return 是否為空 */ public static boolean isEmpty(Object[] obj) { return obj == null || obj.length == 0; } /** * Collection是否為null或size為0 * * @param obj Collection * @return 是否為空 */ public static boolean isEmpty(Collection obj){ return obj == null || obj.isEmpty(); } /** * Map是否為null或size為0 * * @param obj Map * @return 是否為空 */ public static boolean isEmpty(Map obj) { return obj == null || obj.isEmpty();