帶你手寫spring:IOC與DI


前言

依稀記得在2016年剛畢業的時候,在京面試某公司的時候技術總監和我聊到了spring,我比較欣賞一個音樂人Rod Johnson以個人之力承擔了spring的主開發工程。當時的個人水平僅僅是知道spring,會簡單使用。當面試官問到我對源碼的閱讀時,問我大概多久的時間能吃懂spring源碼,我給了一個答案是1-2年吧。然后這個問題沒有下文了,估計他(是她,是個女技術總監)當時想spring這么簡單的東西你都要2年???

工作已經2年+,是時候兌現當年自己說的話了,日常我們在spring最主要的使用地方只是集成第三方產品(如orm框架等),像一種萬能膠一樣。以及自身業務系統中的bean的管理。以及一些aop的使用。在本篇文章中,主要是通過自身對於源碼的解讀后,抓住核心思想,實現一個mini版的spring。幫助大家以及我自身的理解,畢竟分享與幫助他人也是提高自身的一種手段。在本節中,我們主要講解IOC與DI,后幾篇文章中再繼續探討aop與springMVC以及springBoot的問題(當然,mvc與boot以及cloud是由spring衍生出來的產品)。

本文不是概念型講解(只做簡單的概念說明),如果你是新手概念也是模糊的,那么不太適合閱讀此文章。對於內容中有任何的錯誤之處以及不恰當之處,希望您能指出。我會及時改正。

IOC

Inversion of Control中文譯名:控制反轉。

對於這個詞,我的個人理解是:沒有spring的時候,B類需要對象A,我們需要自己去new A(),可能有10個地方都需要A,那我們需要new10次,加重了GC的壓力。

這是,我們想,如果有一個大的池子來幫我們創建好所有的對象(比如B和A類的對象都創建好),對象直接的依賴關系,也由這個池子來幫我們管理,如在創建B類時,發現需要引用A,則注入(DI)一個A對象。

原本對象的控制權在自己手里,而把這個控制權轉變為給這個池子。更像是一種設計模式,如果這種思想被開發的早,可能就列入GoF的23種設計模式中了。。。實現這種模式的主要方式,則是我們后文的DI(Dependency Injection)

ioc實現

對於spring源碼的閱讀,個人閱讀過相關書籍,第一遍看的時候,全臉懵逼,光是源碼中的調用鏈,就有20幾層+(不過源碼中嚴格遵循了方法單一職責,這點很值得我學習,且設計模式運用的惟妙惟肖)。給大家的學習意見是:先看一些知名的博客,梳理一個整體的邏輯圖,再細微的看里面的實現策略。否則是很難讀懂的。當然,時至今日(個人也是略知一二),只是對大致邏輯做了總結后寫出一個mini版,只是講一個思路,代碼的健壯性還請大家不要太較真。可能有的地方實現的並不是最好的實現方式。

bean:是spring中很重要的一個概念,其實就是對象的實例

首先,我們定義一個BeanFactory,對象實例的工廠,這個工廠提供一個統一的方法,getBean

1 public interface HkBeanFactory {
2     
3     Object getBean(String name) throws Exception;
4 
5 }

 

有了工廠后,我們還不行,還需要一個定義bean(BeanDefinition)的接口,用來描述這個bean是如何被定義出來的,在我所寫的代碼中,bean定義只通過類構造器來創建bean,事實上spring源碼中還可通過靜態工廠與成員工廠來創建bean,但因實際中少用這兩中,所以我沒定義。

定義bean,我們需要知道這個bean是哪個類的,是要單例的還是非單例(spring中叫:原型prototype),初始化bean時需要做的工作,以及bean銷毀時的操作(銷毀我的代碼中未實現)

 1 public interface HkBeanDefinition {
 2     
 3     final static String SINGLETION = "singleton";
 4 
 5     final static String PROTOTYPE = "prototype";
 6     
 7     Class<?> getBeanClass();
 8     
 9     String getScope();
10     
11     boolean isSingleton();
12     
13     boolean isPrototype();
14     
15     String getInitMethodName();
16     
17 }

 

有了bean定義的接口,我們還是不能放入工廠中,還得將bean注冊到工廠中,我們再寫一個bean定義注冊(BeanDefinitionRegistry)的接口,此接口要提供注冊的功能,獲取BeanDefinition的功能,另外,防止bean重復定義,需要一個檢測是否已有bean的功能,如下:

1 public interface HkBeanDefinitionRegistry {
2     
3     void registerBeanDefinition (String beanName, HkBeanDefinition hkBeanDefinition) throws Exception;
4     
5     HkBeanDefinition getBeanDefinition(String beanName);
6     
7     boolean containsBeanDefinition(String beanName);
8 
9 }

 

OK,有了以上3個接口,我們可以開始實現具體細節了,首先,我們來實現一個通用的bean定義(GenericBeanDefinition),spring中,默認所有的bean都是單例的。所以我們也給定默認單例

 1 public class GenericBeanDefinition implements HkBeanDefinition {
 2     
 3     private Class<?> beanClass;
 4     
 5     private String scope = HkBeanDefinition.SINGLETION;
 6     
 7     private String initMethodName;
 8 
 9     
10     
11 
12     public void setBeanClass(Class<?> beanClass) {
13         this.beanClass = beanClass;
14     }
15 
16     public void setScope(String scope) {
17         this.scope = scope;
18     }
19 
20     public void setInitMethodName(String initMethodName) {
21         this.initMethodName = initMethodName;
22     }
23 
24 
25     @Override
26     public Class<?> getBeanClass() {
27         return beanClass;
28     }
29 
30     @Override
31     public String getScope() {
32         return scope;
33     }
34 
35     @Override
36     public boolean isSingleton() {
37         return Objects.equals(scope, HkBeanDefinition.SINGLETION);
38     }
39 
40     @Override
41     public boolean isPrototype() {
42         return Objects.equals(scope, HkBeanDefinition.PROTOTYPE);
43     }
44 
45     @Override
46     public String getInitMethodName() {
47         return initMethodName;
48     }
49 
50 
51 }
bean定義實現類,點擊展開

 

在這個類中,我們實現接口中的方法並生成對應的set方法即可!

OK,以上是我們所需要的零件,現在,我們用這些零件來組裝一個bean工廠的實現類,bean工廠中,我們要有注冊bean定義的功能(類似官方spring中,我們在配置文件中組裝bean那一步,指定類,屬性等等。這里我們通過硬編碼實現),

還要有獲取bean的功能getBean(),為了簡化步驟與理解,我們全部用java代碼來實現,更好的幫助大家理解。(即我們這會省去xml解析,省去解析注解諸如@Component@Repository、@Service等)也省掉spring的applicationcontext的門面模式,我們來直接操作工廠。

所以這個類需要實現HkBeanFactory與HkBeanDefinitionRegistry這2個接口。

 

首先,定義倆個集合,存放bean實例與beanDefinition。如下:因可能存在同時注冊,所以采用與官方版本一致的ConcurrentHashMap

public class DefaultBeanFactory implements HkBeanFactory, HkBeanDefinitionRegistry {

    private Map<String, Object> beanMap = new ConcurrentHashMap<>();
    private Map<String, HkBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    }

 

接下來,寫注冊bean的功能,如上文所說,我們此處用代碼來注冊,不再解析xml,方法如下:

    /* 
     * 注冊bean定義,需要給定唯一bean的名稱和bean的定義,放到bean定義集合中
     */
    @Override
    public void registerBeanDefinition(String beanName, HkBeanDefinition hkBeanDefinition) throws Exception {
        Objects.requireNonNull(beanName, "beanName不能為空");
        Objects.requireNonNull(hkBeanDefinition, "beanDefinition不能為空");
        if (beanDefinitionMap.containsKey(beanName)){
            throw new Exception("已存在【"+beanName+ "】的bean定義"+getBeanDefinition(beanName));
        }
        beanDefinitionMap.put(beanName, hkBeanDefinition);

    }

 

OK,bean已經再工廠定義好,接下來我們寫關鍵的獲取bean方法getBean(),在spring的官方版本中,創建bean對象有3種方式,通過調用構造器,還可以通過類的靜態工廠方法與成員工廠方法來創建對象。鑒於后2種我們平常使用較少,此處我們采用構造器來創建對象。代碼如下:

 1     /* 
 2      * 獲得bean的門面方法
 3      */
 4     @Override
 5     public Object getBean(String name) throws Exception {
 6         return doGetBean(name);
 7     }
 8     
 9     /**
10      * getBean的具體邏輯
11      * 
12      * 事實上,在spring的bean定義中,還可以靜態工廠方法和成員工廠方法來創建實例,但在開發中這2種用的較少,所以此處只使用構造器來創建bean
13      */
14     private Object doGetBean(String beanName) throws Exception{
15         Objects.requireNonNull(beanName, "beanName不能為空");
16         Object instance = beanMap.get(beanName);
17         //如果bean已存在,則直接返回
18         if(instance != null){
19             return instance;
20         }
21         HkBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
22         Objects.requireNonNull(beanDefinition, "beanDefinition不能為空");
23         Class<?> class1 = beanDefinition.getBeanClass();
24         Objects.requireNonNull(class1, "bean定義中class類型不能為空");
25         instance = class1.newInstance();
26         
27         //實例已創建好,通過反射執行bean的init方法
28         String initMethodName = beanDefinition.getInitMethodName();
29         if(null!=initMethodName){
30             Method method = class1.getMethod(initMethodName, null);
31             method.invoke(instance, null);
32         }
33         
34         //將單例bean放到map中,下次可直接拿到
35         if(beanDefinition.isSingleton()){
36             beanMap.put(beanName, instance);
37         }
38         return instance;
39     }

 

對以上代碼關鍵部分做出解釋,因實例化bean會在之后多次用到,所以單獨抽取出來,事實上官方版本也是這么做的。,所以我們重點看一下doGetBean方法

16~20行,嘗試從beanMap種獲取,如果是單例bean,已創建過對象,則直接返回對象

21~22行,從beanDefinitionMap種獲取bean定義,如果未定義,則拋出異常。

25行,調用bena定義種的類的構造方法來創建實例。

28~32行,某些bean有初始化方法init,如資源加載等,此處我們通過反射執行一次init方法。

35~37行,將單例bean放到集合種,下次獲取此對象即可直接返回。

下面,附上完整代碼:

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 默認的bean工廠實現類,需實現bean定義注冊
 * @author HK
 *
 */
public class DefaultBeanFactory implements HkBeanFactory, HkBeanDefinitionRegistry {

    private Map<String, Object> beanMap = new ConcurrentHashMap<>();
    private Map<String, HkBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    
    
    /* 
     * 注冊bean定義,需要給定唯一bean的名稱和bean的定義,放到bean定義集合中
     */
    @Override
    public void registerBeanDefinition(String beanName, HkBeanDefinition hkBeanDefinition) throws Exception {
        Objects.requireNonNull(beanName, "beanName不能為空");
        Objects.requireNonNull(hkBeanDefinition, "beanDefinition不能為空");
        if (beanDefinitionMap.containsKey(beanName)){
            throw new Exception("已存在【"+beanName+ "】的bean定義"+getBeanDefinition(beanName));
        }
        beanDefinitionMap.put(beanName, hkBeanDefinition);

    }

    /* 
     * 獲得bean定義
     */
    @Override
    public HkBeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

    /* 
     * 是否存在bean定義
     */
    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    /* 
     * 獲得bean的門面方法
     */
    @Override
    public Object getBean(String name) throws Exception {
        return doGetBean(name);
    }
    
    /**
     * getBean的具體邏輯
     * 
     * 事實上,在spring的bean定義中,還可以靜態工廠方法和成員工廠方法來創建實例,但在開發中這2種用的較少,所以此處只使用構造器來創建bean
     */
    private Object doGetBean(String beanName) throws Exception{
        Objects.requireNonNull(beanName, "beanName不能為空");
        Object instance = beanMap.get(beanName);
        //如果bean已存在,則直接返回
        if(instance != null){
            return instance;
        }
        HkBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        Objects.requireNonNull(beanDefinition, "beanDefinition不能為空");
        Class<?> class1 = beanDefinition.getBeanClass();
        Objects.requireNonNull(class1, "bean定義中class類型不能為空");
        instance = class1.newInstance();
        
        //實例已創建好,通過反射執行bean的init方法
        String initMethodName = beanDefinition.getInitMethodName();
        if(null!=initMethodName){
            Method method = class1.getMethod(initMethodName, null);
            method.invoke(instance, null);
        }
        
        //將單例bean放到map中,下次可直接拿到
        if(beanDefinition.isSingleton()){
            beanMap.put(beanName, instance);
        }
        return instance;
    }

}
DefaultBeanFactory點擊展開

 

 

測試IOC容器

接下來,我們來測試這個IOC容器的運行:如下先定義一個簡單bean對象TeacherBean,一個是初始化方法init( ),一個是上課方法teach( )。

/**
 * @author HK
 *
 */
public class TeacherBean {
    
    public void teach(){
        System.out.println(this+"執行了teach方法,老師要開始上課了!");
    }
    
    public void init(){
        System.out.println("Teacher類的初始化init方法被執行了");
    }

}

 

 好,我們就將這個teacherBean放到IOC工廠。如下,

 1 import org.junit.AfterClass;
 2 import org.junit.Test;
 3 
 4 import spring.beans.DefaultBeanFactory;
 5 import spring.beans.GenericBeanDefinition;
 6 
 7 /**
 8  * @author HK
 9  *
10  */
11 public class TestIOC {
12     static DefaultBeanFactory factory = new DefaultBeanFactory();
13     
14     @Test
15     public void testRegist() throws Exception {
16         GenericBeanDefinition bd = new GenericBeanDefinition(); 
17         bd.setBeanClass(TeacherBean.class);
18 //        bd.setScope(HkBeanDefinition.PROTOTYPE);
19         bd.setInitMethodName("init");
20         factory.registerBeanDefinition("teacher", bd);
21         
22     }
23     
24     @AfterClass
25     public static void testGetBean() throws Exception{
26         TeacherBean t =(TeacherBean) factory.getBean("teacher");
27         TeacherBean t1 =(TeacherBean) factory.getBean("teacher");
28         t.teach();
29         t1.teach();
30         System.out.println(t==t1);
31     }
32 
33 }

 

解釋:16~20行,我們主要做了注冊bean定義。而在26~30行,是從IOC中獲取bean,由於默認是單例,所以2次獲取bean后是同1個對象。執行結果如下圖

至此,一個簡單的IOC容器就完成了。但我們發現了一些其他問題,比如Teacher類中有很多屬性,如String name;  int age;  Student student;等等

此時,學生類Student也是一個bean的時候,如何給Teacher類賦予這些值呢,接下來,就是我們DI依賴注入要做的事情了。

 

DI

Dependency Injection中文譯名:依賴注入。

由於時間關系,會在之后的周末時間整理 好后更新此處內容(手動捂臉)相信有了上邊的基礎后,此處大家也不難實現了!!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM