手寫IOC實現過程

一.手寫ioc前基礎知識

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容器也注入不了任何東西,從而從根本上說如何設計好類結構纔是關鍵,依賴注入只是一種裝配對象手段」。

二. 手寫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();
    }
}
相關文章
相關標籤/搜索