【Spring】重構--手寫Spring核心邏輯(三)實現IOC/DI(context包)

A minor發表於2020-12-06

在上一篇 【Spring】重構–手寫Spring核心邏輯(二)實現IOC/DI(beans包) 我們已經實現了 beans 包中的相關類,本篇就來實現 context 中關於容器的具體邏輯。

在這裡插入圖片描述

1.MYAbstractApplicationContext

IOC容器頂層設計,是最頂層容器的規範,不管是 XmlApplication 還是 AnnotationApplication 都必須去實現。這種設計也便於我們日後擴充套件新容器。

public abstract class MYAbstractApplicationContext {
    // 受保護,只提供給子類重寫(最少知道原則)
    protected void refresh() throws Exception {}
}

2.MYDefaultListableBeanFactory

上面有了 MYAbstractApplicationContext 那我們就可以多種選擇,但是也必須提供 IOC 容器的預設實現。這就好比平常我們手機支付時可以用微信、支付寶、銀行卡等,但是在具體的支付頁面它都會設定好一個預設支付選項。

注意,這個類其實是在 beans 包中,因為 beans包更多放的是規範、配置、標準等。放到 context 包中說是為了更直觀的看到 AbstractApplicationContext、DefaultListableBeanFactory、ApplicationContext 的關係。

public class MYDefaultListableBeanFactory extends MYAbstractApplicationContext {
    // 偽IOC容器,儲存了BeanDefinition(類資訊)
    // 這裡的key是factoryBeanName,即beanName(對於一種Bean而言factoryName是唯一的)
    protected final Map<String, MYBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, MYBeanDefinition>();
}

3.MYApplicationContext

// 實現 BeanFactory 介面,繼承 DefaultListableBeanFactory
public class MYApplicationContext extends MYDefaultListableBeanFactory implements MYBeanFactory {

    private String[] configLocations;
    private MYBeanDefinitionReader reader;

    // 通用IOC容器,存的是 BeanWrapper
    private Map<String, MYBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, MYBeanWrapper>();
    // 單例的IOC容器,存的是例項物件(相當於快取,避免重複建立Bean)
    private Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();

    // 建構函式,要傳入載入的配置檔案,然後呼叫refresh方法
    public MYApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
	
	//...
}

我們再來看一遍繼承關係:

在這裡插入圖片描述

refresh()

在 AbstractApplicationContext 定義的模板方法,是IOC容器初始化的入口

@Override
protected void refresh() throws Exception {
   // 1.定位,定位配置檔案
   reader = new MYBeanDefinitionReader(configLocations);

   // 2.載入,載入配置檔案到記憶體(BeanDefinition)
   List<MYBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

   // 3.註冊,註冊配置資訊到容器裡面(偽IOC容器)
   doRegisterBeanDefinition(beanDefinitions);

   // 4.把不是延時載入的類,提前初始化
   // 一般不開啟懶載入,即在IOC容器初始化時就完成例項化與注入
   doAutowired();
}

doRegisterBeanDefinition()

將BeanDefinition們注入容器(偽IOC容器)。該方法執行完後,IOC容器初始化就完成了

private void doRegisterBeanDefinition(List<MYBeanDefinition> beanDefinitions) throws Exception {
    for (MYBeanDefinition beanDefinition : beanDefinitions) {
        if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
            throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
        }
        super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
    }
}

doAutowired()

對於非延時載入的bean,在IOC容器初始化時就將Bean建立出來並完成依賴注入

private void doAutowired() {
    // 遍歷BeanDefinition,看哪個是延時載入了
    for (Map.Entry<String, MYBeanDefinition> entry : super.beanDefinitionMap.entrySet()) {
        String beanName = entry.getKey();
        // 這裡預設isLazyInit=false,即先提前將Bean放入IOC容器中,即這裡所有BeanDefinition都會建立一個bean
        if (!entry.getValue().isLazyInit()) {
            try {
                getBean(beanName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

getBean()

初始化Bean並完成依賴注入:

  1. 初始化:讀取BeanDefinition,通過反射建立Bean例項,包裝成BeanWrapper,並放入IOC容器
  2. 注入:對IOC容器管理的Bean進行依賴注入

Spring中呼叫getBean的時機:

  1. DispatchServlet 建立 IOC容器:refresh --> doAutowired
  2. DispatchServlet 建立 HandlerMapping,要拿出所有Bean
  3. 手動呼叫 getBean 方法去獲取Bean,比如上面doAutowired()在IOC容器初始化時呼叫
@Override
public Object getBean(String beanName) throws Exception {

    MYBeanDefinition myBeanDefinition = this.beanDefinitionMap.get(beanName);
    Object instance = null;

    // 建立事件處理器
    MYBeanPostProcessor beanPostProcessor = new MYBeanPostProcessor();
    // 在建立bean之前進行一些動作
    beanPostProcessor.postProcessBeforeInitialization(instance, beanName);

    // 判斷是否是Spring管理的物件
    // 在建立BeanDefinition時,不是Spring管理的就沒有BeanDefinition(這裡是通過Properties配置scanPackage不是註解)
    if (myBeanDefinition == null) {
        throw new Exception("This Bean not exists!");
    }

    // 1.初始化
    // 注:所有Bean例項都要封裝成BeanWrapper,然後在BeanWrapper中再取出Bean例項
    MYBeanWrapper beanWrapper = instantiteBean(beanName, myBeanDefinition);

    // 2.將拿到的BeanWrapper放入IOC容器
    this.factoryBeanInstanceCache.put(beanName, beanWrapper);
	
	// 在建立bean之後進行一些動作
    beanPostProcessor.postProcessAfterInitialization(instance, beanName);

    // 3.注入
    populateBean(beanName, new MYBeanDefinition(), beanWrapper);

    Object o = this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    // 注:即使是單例模式有單例IOC容器,但獲取Instance也要先封裝為Wrapper,然後再在通用容器中取
    return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
}

// 通過型別獲取bean的方法跟beanName獲取大同小異,這裡就不實現了
@Override
public Object getBean(Class<?> beanClass) throws Exception {
     return null;
}

迴圈注入:class A{ B b; } --> class B{ A a; }。因此要分成初始化與注入兩個方法,即先將物件建立出來,然後再將依賴注入

instantiteBean()

建立 Bean 例項,並封裝成 BeanWrapper

private MYBeanWrapper instantiteBean(String beanName, MYBeanDefinition myBeanDefinition) {
    // 1.拿到要例項化的類名
    String className = myBeanDefinition.getBeanClassName();
    // 2.通過反射進行例項化
    Object instance = null;
    try {
        // 預設所有物件都是單例的,即都可以通過單例IOC容器中獲取Bean 
        if (this.factoryBeanObjectCache.containsKey(className)) {
            instance = this.factoryBeanObjectCache.get(className);
        } else {
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();
            
            this.factoryBeanObjectCache.put(myBeanDefinition.getFactoryBeanName(), instance);
            // 注:這裡還要根據className(全類名)放一個
            // 因為在通過型別進行getBean時,BeanDefinition只封裝了介面做factoryName
            this.factoryBeanObjectCache.put(className, instance);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    // 3.封裝BeanWrapper
    // 注:無論單例多例,都要先封裝成 BeanWrapper
    MYBeanWrapper beanWrapper = new MYBeanWrapper(instance);
    return beanWrapper;
}

populateBean()

對IOC中的Bean進行依賴注入:

  1. byName:指定 beanName(首字母小寫,介面,自定義) -----> @Resource時
  2. byType:型別注入(預設) ----> @Autowired
private void populateBean(String beanName, MYBeanDefinition myBeanDefinition, MYBeanWrapper myBeanWrapper) {
    Class<?> clazz = myBeanWrapper.getWrappedClass();
    // 只有容器管理的bean才會給他依賴注入
    if (! clazz.isAnnotationPresent(MYController.class ) || clazz.isAnnotationPresent(MYService.class)) { return; }

    Object instance = myBeanWrapper.getWrappedInstance();
    // 注:這裡是getDeclaredFields,getFields只能獲取到public欄位
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        if (!field.isAnnotationPresent(MYAutowired.class)) { continue; }
		
		// 1.獲取註解中指定注入的beanName(byName注入)
        MYAutowired annotation = field.getAnnotation(MYAutowired.class);
        String autowiredBeanName = annotation.value().trim();

        // 2.沒有指定beanName的話,通過型別進行注入(byType注入)
        // 注意:型別 = 自身型別 || 介面型別。在BeanDefinitionReader#loadBeanDefinitions已經對介面建立過BeanDefinition了(當一個介面有多個實現類時,後掃描的會覆蓋先掃描的)
        if ("".equals(autowiredBeanName)) {
            // 除了simpleName,通過class拿到的都是全類名
            // 前面初始化時已經用className向容器中注入過wrapper了
            autowiredBeanName = field.getType().getName();
        }

        field.setAccessible(true);

        try {
            // 因為要給當前Bean注入時,可能要注入的Bean還沒初始化,因此就暫時不給這個欄位注入
            // 但是當正式使用時還會getBean一次,這時所有bean都初始化完成了,就可以注入了
            if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){ continue; }

            // 獲取具體Bean例項:這裡是在通用IOC容器中獲取,因為可能有多例情況
            field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

getBeanDefinitionNames()

返回所有的 beanName

public String[] getBeanDefinitionNames() {
    return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
}

getConfig()

獲取配置檔案的內容,實際上呼叫到 MYBeanDefinition#getConfig 方法

public Properties getConfig() {
    return this.reader.getConfig();
}

4.MYApplicationContextAware

通過解耦方式獲得IOC容器的頂層設計。後面將通過一個監聽器去掃描所有的類,只要實現了此介面,將自動呼叫setApplicationContext()方法,從而將IOC容器注入到目標類中。

public interface MYApplicationContextAware {

    void setApplicationContext(MYApplicationContext applicationContext);
}

IOC和DI模組到此就寫完了,下面我們寫個測試類來看看能否執行。我們首先在 MyAction 中增加一個 test 方法

public void test(String name) {
        System.out.println(queryService.query(name));
    }

然後再 test 包下新建一個 Test 類

public class Test {

    public static void main(String[] args) {
    	// 初始化 IOC 容器
        MYApplicationContext context = new MYApplicationContext("classpath:application.properties");
        try {
        	// 測試 IOC 
            Object bean = context.getBean("myAction");
            System.out.println(bean);
            // 測試 DI,即 myAction 物件中是否成功注入 QueryService 物件
            MyAction myAction = (MyAction)bean;
            myAction.test("張三");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

結果如下:

在這裡插入圖片描述
可以看到 IOC 容器中已經有了 myAction 物件,並且也成功注入 QueryService!

相關文章