1.什麼是IOC(Inversion of Control 控制反轉)?java
IoC不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導咱們如何設計出鬆耦合、更優良的程序。傳統應用程序都是由咱們在類內部主動建立依賴對象,從而致使類與類之間高耦合,難於測試;有了IoC容器後,把建立和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,因此對象與對象之間是鬆散耦合,這樣也方便測試,利於功能複用,更重要的是使得程序的整個體系結構變得很是靈活。web
其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了「主從換位」的變化。應用程序本來是老大,要獲取什麼資源都是主動出擊,可是在IoC/DI思想中,應用程序就變成被動的了,被動的等待IoC容器來建立並注入它所須要的資源了。spring
IoC很好的體現了面向對象設計法則之一—— 好萊塢法則:「別找咱們,咱們找你」;即由IoC容器幫對象找相應的依賴對象並注入,而不是由對象主動去找。編程
2.什麼是DI(Dependency Injection 依賴注入)?tomcat
DI—Dependency Injection,即「依賴注入」:是組件之間依賴關係由容器在運行期決定,形象的說,即由容器動態的將某個依賴關係注入到組件之中。依賴注入的目的並不是爲軟件系統帶來更多功能,而是爲了提高組件重用的頻率,併爲系統搭建一個靈活、可擴展的平臺。經過依賴注入機制,咱們只須要經過簡單的配置,而無需任何代碼就可指定目標須要的資源,完成自身的業務邏輯,而不須要關心具體的資源來自何處,由誰實現。app
3.IOC和DI什麼關係?框架
IoC和DI由什麼關係呢?其實它們是同一個概念的不一樣角度描述,因爲控制反轉概念比較含糊(可能只是理解爲容器控制對象這一個層面,很難讓人想到誰來維護對象關係),因此2004年大師級人物Martin Fowler又給出了一個新的名字:「依賴注入」,相對IoC 而言,依賴注入」明確描述了「被注入對象依賴IoC容器配置依賴對象」。ide
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容器也注入不了任何東西,從而從根本上說「如何設計好類結構纔是關鍵,依賴注入只是一種裝配對象手段」。
標記配置分爲集中式管理和分散式管理,即xml文件方式和註解方式配置元數據信息,手寫ioc我採用註解配置元數據方式來實現ioc的大體運行過程。
1.定義元數據配置信息
@Component/@Controller/@Repository@Service
掃描什麼樣的class裝載到ioc容器中進行管理。
@Autowired
什麼樣的對象進行依賴注入。
/** * @Classname Component * @Description * @Date 2020/8/6 14:54 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { }
/** * @Classname Controller * @Description TODO * @Date 2020/8/6 14:56 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { }
/** * @Classname Repository * @Description TODO * @Date 2020/8/6 14:58 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Repository { }
/** * @Classname Service * @Description TODO * @Date 2020/8/6 14:57 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Service { }
/** * @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容器管理。
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容器 * <p> * 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<Class<?>, Object> beanMap = new ConcurrentHashMap<>(); /** * 加載bean的註解列表 */ private static final List<Class<? extends Annotation>> 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<Class<?>> classSet = ClassUtil.extractPackageClass(packageName); //爲空直接return if (ValidationUtil.isEmpty(classSet)) { log.warn("extract nothing from packageName" + packageName); return; } classSet.stream().forEach(clazz -> { for (Class<? extends Annotation> 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<Class<?>> getClasses(){ return beanMap.keySet(); } /** * 獲取全部Bean集合 * * @return Bean集合 */ public Set<Object> getBeans(){ return new HashSet<>( beanMap.values()); } /** * 根據註解篩選出Bean的Class集合 * * @param annotation 註解 * @return Class集合 */ public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){ //1.獲取beanMap的全部class對象 Set<Class<?>> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ log.warn("nothing in beanMap"); return null; } //2.經過註解篩選被註解標記的class對象,並添加到classSet裏 Set<Class<?>> 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<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){ //1.獲取beanMap的全部class對象 Set<Class<?>> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ log.warn("nothing in beanMap"); return null; } //2.判斷keySet裏的元素是不是傳入的接口或者類的子類,若是是,就將其添加到classSet裏 Set<Class<?>> 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對象,進行依賴注入。
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<Class<?>> 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.工具包
做爲框架 類加載/反射/校驗等功能。
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 <T> * @return */ public static <T> 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<Class<?>> 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<Class<?>> classSet = null; //過濾出文件類型的資源 if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){ classSet = new HashSet<Class<?>>(); 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<Class<?>> 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); }); } }
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(); } }