主頁 > 後端開發 > 我的自定義Spring框架 | 自定義Spring IoC功能

我的自定義Spring框架 | 自定義Spring IoC功能

2021-10-16 08:45:54 後端開發

分析完與Spring IoC功能相關的介面之后,接下來我們就要來自定義Spring IoC功能了,

首先,我們先來看一下需求:現要對下面的組態檔進行決議,并自定義Spring框架的IoC功能對涉及到的物件進行管理,

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl">
        <property name="username" value="zhangsan"></property>
        <property name="password" value="123456"></property>
    </bean>

    <bean id="userService" class="com.meimeixia.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>

</beans>

要想完成這一需求,可不是件容易的事情,我們得分如下幾部分來進行定義,

  1. 定義bean相關的pojo類,
  2. 定義注冊表相關的類,
  3. 定義決議器相關的類,
  4. 定義IoC容器相關的類,

下面,我們先來定義bean相關的pojo類,

定義bean相關的pojo類

首先,我們先使用IDEA來創建一個Maven工程,工程名字你可以取為liayun_spring,然后再來創建相應的包,這里為了讓大家清楚地看到我都創建了哪些包,干脆我把最終Maven工程的結構給大家展示出來吧!

在這里插入圖片描述

PropertyValue類

在這一部分,我們會創建不同的類,第一個類就是PropertyValue,該類的作用就是用來封裝bean的屬性的,看一下一開始的組態檔,最終我們是要決議該組態檔的,所以嚴格來說,PropertyValue這個類是用來封裝<bean>標簽的<property>子標簽中的屬性的,由于<property>子標簽中有name、ref、value等屬性,因此PropertyValue類里面至少得有name、ref、value這三個屬性,

package com.meimeixia.framework.beans;

/**
 * 用來封裝bean標簽下的property標簽的屬性,屬性有這些:
 *      name屬性
 *      ref屬性
 *      value屬性:給基本資料型別及String型別的資料賦值
 * @author liayun
 * @create 2021-09-20 9:41
 */
public class PropertyValue {

    private String name;
    private String ref;
    private String value;

    public PropertyValue() {

    }

    public PropertyValue(String name, String ref, String value) {
        this.name = name;
        this.ref = ref;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRef() {
        return ref;
    }

    public void setRef(String ref) {
        this.ref = ref;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}

MutablePropertyValues類

創建完PropertyValue類之后,接下來我們再來創建第二個類,那就是MutablePropertyValues,為什么要創建這個類呢?因為一個<bean>標簽可以有多個<property>子標簽,而每一個<property>子標簽都會被封裝成一個PropertyValue物件,對于多個PropertyValue物件我們就要進行存盤以及管理了,所以在這里我們就要創建一個MutablePropertyValues類,用來存盤并管理多個PropertyValue物件了,

注意,在創建該類時,我們需要用到迭代器模式,所以該類得去實作Iterable介面,至于為什么這兒要用到迭代器模式,我不說,相信大家也知道,因為MutablePropertyValues類是用來存盤并管理多個PropertyValue物件的,所以它必須是可以迭代的,

package com.meimeixia.framework.beans;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 用于存盤和管理多個PropertyValue物件
 * @author liayun
 * @create 2021-09-20 10:15
 */
public class MutablePropertyValues implements Iterable<PropertyValue> {

    // 定義List集合物件,用來存盤PropertyValue物件
    private final List<PropertyValue> propertyValueList; // 如果用final修飾的話,那么就意味著它只能被賦值一次

    // 以下構造方法是用來為以上成員變數賦值的
    public MutablePropertyValues() {
        this.propertyValueList = new ArrayList<PropertyValue>();
    }

    public MutablePropertyValues(List<PropertyValue> propertyValueList) {
        if (propertyValueList == null) {
            this.propertyValueList = new ArrayList<PropertyValue>();
        } else {
            this.propertyValueList = propertyValueList;
        }
    }

    // 獲取所有的PropertyValue物件,當然,是以陣列的形式回傳
    public PropertyValue[] getPropertyValues() {
        // 將集合轉換為陣列并回傳
        return propertyValueList.toArray(new PropertyValue[0]);
    }

    // 根據name屬性值獲取PropertyValue物件
    public PropertyValue getPropertyValue(String propertyName) {
        // 遍歷集合物件
        for (PropertyValue propertyValue : propertyValueList) {
            if (propertyValue.getName().equals(propertyName)) {
                return propertyValue;
            }
        }
        return null;
    }

    // 判斷集合是否為空
    public boolean isEmpty() {
        return propertyValueList.isEmpty();
    }

    // 往集合里面添加PropertyValue物件,注意,該方法的回傳值型別是MutablePropertyValues,目的是為了能實作鏈式編程
    public MutablePropertyValues addPropertyValue(PropertyValue pv) {
        // 判斷集合中存盤的PropertyValue物件是否和傳遞進來的重復了,如果重復了,那么就進行覆寫
        for (int i = 0; i < propertyValueList.size(); i++) {
            // 獲取集合中每一個PropertyValue物件
            PropertyValue currentPv = propertyValueList.get(i);
            if (currentPv.getName().equals(pv.getName())) {
                propertyValueList.set(i, pv); // 如果重復了,那么就進行覆寫
                return this; // 回傳當然物件(即MutablePropertyValues型別的物件),目的就是實作鏈式編程
            }
        }
        this.propertyValueList.add(pv); // 如果沒有重復的,那么就直接添加進集合里面去
        return this; // 回傳當然物件(即MutablePropertyValues型別的物件),目的就是實作鏈式編程
    }

    // 判斷是否有指定name屬性值的PropertyValue物件,有的話回傳true,沒有的話回傳false
    public boolean contains(String propertyName) {
        return getPropertyValue(propertyName) != null;
    }

    // 獲取迭代器物件
    @Override
    public Iterator<PropertyValue> iterator() {
        /*
         * 獲取迭代器物件的這個方法應該如何來實作呢?
         *
         * 由于PropertyValue物件是存盤在一開始定義的List集合里面的,所以這里我們直接呼叫其獲取迭代器的方法(即iterator)即可,
         */
        return propertyValueList.iterator();
    }

}

相信大家可以看到,我們在以上MutablePropertyValues類中定義了很多方法,這些方法我也寫了一些比較詳細的注釋,相信大家都能看懂,

BeanDefinition類

接下來,我們再來創建bean相關的pojo類里面的最后一個類,也是最重要的一個類,叫BeanDefinition,其實,之前我們在分析Spring IoC功能的相關介面時,就見過類似這玩意,只不過它是介面,而在這里我們是直接將其定義成類了,主要是為了簡單,圖省事,

BeanDefinition類主要是用來封裝bean資訊的,主要包含id(即bean物件的名稱)、class(需要交由Spring管理的類的全類名)及子標簽<property>中的資料,

package com.meimeixia.framework.beans;

/**
 * 用來封裝bean標簽資料,包含:
 *      id屬性
 *      class屬性
 *      property子標簽中的資料
 * @author liayun
 * @create 2021-09-20 11:06
 */
public class BeanDefinition {

    private String id;
    private String className;
    private MutablePropertyValues propertyValues;

    public BeanDefinition() {
        this.propertyValues = new MutablePropertyValues();
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public MutablePropertyValues getPropertyValues() {
        return propertyValues;
    }

    public void setPropertyValues(MutablePropertyValues propertyValues) {
        this.propertyValues = propertyValues;
    }

}

可以看到,雖然這個類特別重要,但是創建起來還是比較簡單的,

定義注冊表相關的類

BeanDefinitionRegistry介面

定義完bean相關的pojo類之后,接下來我們就要來定義注冊表相關的類了,當然,這一部分就不僅僅是有類了,還有介面,說白了,在這一部分,我們會創建一個介面及其子實作類,至于介面的話,我們就命名為BeanDefinitionRegistry了,下面我們就來看一下該介面需要定義哪些功能?

BeanDefinitionRegistry介面應定義注冊表的相關操作,所以需要定義如下功能:

  • 注冊BeanDefinition物件到注冊表中,
  • 從注冊表中洗掉指定名稱的BeanDefinition物件,
  • 根據名稱從注冊表中獲取BeanDefinition物件,
  • 判斷注冊表中是否包含指定名稱的BeanDefinition物件,
  • 獲取注冊表中BeanDefinition物件的個數,
  • 獲取注冊表中所有的BeanDefinition物件的名稱,

以上這些功能,相信大家應該很熟悉,因為之前我們在分析Spring原始碼里面的BeanDefinitionRegistry介面時就看到過,BeanDefinitionRegistry介面也定義了以上這些功能,

根據以上分析,我們創建出來的BeanDefinitionRegistry介面就應該是下面這個樣子的,

package com.meimeixia.framework.beans.factory.support;

import com.meimeixia.framework.beans.BeanDefinition;

/**
 * 注冊表物件所屬介面
 * @author liayun
 * @create 2021-09-20 11:16
 */
public interface BeanDefinitionRegistry {
    // 注冊BeanDefinition物件到注冊表中
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

    // 從注冊表中洗掉指定名稱的BeanDefinition物件
    void removeBeanDefinition(String beanName) throws Exception;

    // 根據名稱從注冊表中獲取BeanDefinition物件
    BeanDefinition getBeanDefinition(String beanName) throws Exception;

    boolean containsBeanDefinition(String beanName);

    int getBeanDefinitionCount();

    String[] getBeanDefinitionNames();
}

BeanDefinitionRegistry介面創建完畢之后,接下來我們就要來創建它的子實作類了,注意,這里我們只創建一個子實作類,

SimpleBeanDefinitionRegistry類

我們在創建該類時,要讓該類去實作BeanDefinitionRegistry介面,并去重寫它里面所有的抽象方法,注意,在該類里面我們還得定義一個Map集合,讓其作為注冊表容器,

package com.meimeixia.framework.beans.factory.support;

import com.meimeixia.framework.beans.BeanDefinition;

import java.util.HashMap;
import java.util.Map;

/**
 * 注冊表介面的子實作類
 * @author liayun
 * @create 2021-09-20 11:23
 */
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
    // 定義一個Map集合,用來存盤BeanDefinition物件
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>(); // 注意,在這里我們選擇創建的是雙列集合,因為我們不僅要存盤BeanDefinition物件,還要存盤其名稱

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public void removeBeanDefinition(String beanName) throws Exception {
        beanDefinitionMap.remove(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

定義決議器相關的類

接下來,我們就來定義決議器相關的介面和類,相信大家也都知道了,我們均是參照Spring里面的介面和類來定義的,所以在這一部分我們就將介面命名為BeanDefinitionReader,既然它是一個介面的話,那么它里面定義的便是最基本的功能規范了,還有,我們還得為該介面創建一個子實作類,名字不妨就叫做XmlBeanDefinitionReader,

為什么我們還要創建BeanDefinitionReader介面呢?之前我帶著大家分析Spring IoC功能相關的介面時,你也看到了,針對于不同的組態檔,Spring會提供不同的子類來進行決議,例如,決議properties格式的組態檔用的是PropertiesBeanDefinitionReader類,決議XML格式的組態檔用的是XmlBeanDefinitionReader,大家要是能夠去看一下BeanDefinitionReader介面的繼承體系的話,你會發現這倆類都是其子實作類,當然了,這里我們在自定義Spring IoC功能時,只會針對XML格式的組態檔來創建決議類,

BeanDefinitionReader介面

由于BeanDefinitionReader是用來決議組態檔并在注冊表中注冊bean的資訊的,所以我們應在它里面定義如下兩個規范,

  1. 獲取注冊表的功能,讓外界可以通過該物件獲取注冊表物件,
  2. 加載組態檔,并注冊bean資料,

根據以上分析,我們創建出來的eanDefinitionReader介面就應該是下面這個樣子的,

package com.meimeixia.framework.beans.factory.support;

/**
 * 用來決議組態檔的,而且該介面只是定義了規范,具體的應由子類來實作
 * @author liayun
 * @create 2021-09-20 11:49
 */
public interface BeanDefinitionReader {
    // 獲取注冊表物件
    BeanDefinitionRegistry getRegistry();

    // 加載組態檔,并在注冊表中進行注冊
    void loadBeanDefinitions(String configLocation) throws Exception;
}

XmlBeanDefinitionReader類

BeanDefinitionReader介面創建完畢之后,接下來我們就來創建其子實作類,名字上面我也說了,就叫XmlBeanDefinitionReader,

相信大家也知道了,XmlBeanDefinitionReader類是專門用來解析XML格式的組態檔的,而且創建該類時,不用我說,大家都應該知道該類得實作BeanDefinitionReader介面并去重寫它里面的兩個功能,如下所示,

package com.meimeixia.framework.beans.factory.xml;

import com.meimeixia.framework.beans.BeanDefinition;
import com.meimeixia.framework.beans.MutablePropertyValues;
import com.meimeixia.framework.beans.PropertyValue;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionReader;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/**
 * 針對XML格式的組態檔進行決議的類
 * @author liayun
 * @create 2021-09-20 11:58
 */
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
    /*
     * 宣告注冊表物件
     *
     * 為什么要在成員變數位置處宣告注冊表物件呢?大家不妨來想一下,XmlBeanDefinitionReader物件(即決議器)是
     * 專門用來決議XML格式的組態檔的,決議完之后,自然是會將組態檔里面的<bean>標簽封裝成BeanDefinition對
     * 象,那么這些BeanDefinition物件是存放在哪呢?是不是就是注冊到了注冊表物件里面呀?所以,我們就在這個位置聲
     * 明了一個注冊表物件,
     */
    private BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader() {
        registry = new SimpleBeanDefinitionRegistry();
    }

    /**
     * 獲取注冊表物件
     * @return 直接回傳成員注冊表物件
     */
    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

    /**
     * 加載組態檔,并在注冊表中進行注冊
     * @param configLocation 類路徑下組態檔的路徑
     * @throws Exception
     */
    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
        // 使用dom4j進行XML組態檔的決議
        SAXReader reader = new SAXReader();
        // 獲取類路徑下的組態檔,注意,這里我們只實作類路徑下的組態檔的加載
        InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);
        Document document = reader.read(is);
        // 根據Document物件獲取根標簽物件(根標簽很明顯就是<beans>標簽)
        Element rootElement = document.getRootElement();
        // 獲取根標簽下所有的<bean>子標簽物件
        List<Element> beanElements = rootElement.elements("bean");
        // 遍歷集合
        for (Element beanElement : beanElements) {
            // 獲取id屬性
            String id = beanElement.attributeValue("id");
            // 獲取class屬性
            String className = beanElement.attributeValue("class");

            // 將id屬性和class屬性封裝到BeanDefinition物件中
            // 1. 創建BeanDefinition物件
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setId(id);
            beanDefinition.setClassName(className);

            // 創建MutablePropertyValues物件
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();

            // 獲取<bean>標簽下所有的<property>子標簽物件
            List<Element> propertyElements = beanElement.elements("property");
            for (Element propertyElement : propertyElements) {
                String name = propertyElement.attributeValue("name");
                String ref = propertyElement.attributeValue("ref");
                String value = propertyElement.attributeValue("value");
                PropertyValue propertyValue = new PropertyValue(name, ref, value);
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            // 將MutablePropertyValues物件封裝到BeanDefinition物件中
            beanDefinition.setPropertyValues(mutablePropertyValues);

            // 將BeanDefinition物件注冊到注冊表中
            registry.registerBeanDefinition(id, beanDefinition);
        }
    }
}

注意,由于我們要在XmlBeanDefinitionReader類的loadBeanDefinitions方法中使用dom4j進行XML組態檔的決議,所以我們應在工程的pom.xml檔案里面匯入對應的jar包的坐標,如下所示,

<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>

定義IoC容器相關的類

定義完決議器相關的介面和類之后,接下來我們來定義IoC容器相關的介面和類,大家要注意了,這一部分是我們自定義Spring IoC功能最核心的部分,而且在這一部分我們需要定義如下這些介面和類,

BeanFactory介面

在該介面中我們需要定義IoC容器的統一規范,即獲取bean物件的方法,

package com.meimeixia.framework.beans.factory;

/**
 * IoC容器父介面
 * @author liayun
 * @create 2021-09-20 17:34
 */
public interface BeanFactory {
    // 根據bean物件的名稱獲取bean物件
    Object getBean(String name) throws Exception;
    
    // 根據bean物件的名稱獲取bean物件,并進行型別轉換
    <T> T getBean(String name, Class<? extends T> clazz) throws Exception;
}

ApplicationContext介面

BeanFactory介面創建完畢之后,接下來我們來創建它的一個子介面,這里我們就取名為ApplicationContext了,

之前我帶領著大家分析Spring IoC功能相關的介面時,相信大家也知道了ApplicationContext屬于非延時加載,也就是說(使用者)在創建容器物件的時候,就會去加載組態檔,并實體化bean物件,最終將其存盤在容器里面,

這里,我也不廢話了,直接給出ApplicationContext介面的代碼,如下所示,

package com.meimeixia.framework.context;

import com.meimeixia.framework.beans.factory.BeanFactory;

/**
 * 定義非延時加載功能
 * @author liayun
 * @create 2021-09-20 17:39
 */
public interface ApplicationContext extends BeanFactory {
    // 進行組態檔加載并進行物件創建
    void refresh() throws Exception;
}

可以看到,我們在創建ApplicationContext介面時,在它里面只定義了一個refresh方法,該方法主要完成以下兩個功能,

  • 加載組態檔,
  • 根據注冊表中的BeanDefinition物件封裝的資料進行bean物件的創建,

AbstractApplicationContext類

ApplicationContext介面創建完畢之后,接下來我們來創建它的一個子實作類,這里我們就取名為AbstractApplicationContext了,

那么,AbstractApplicationContext類有什么特點以及作用呢?

  • 作為ApplicationContext介面的子類,所以該類也是非延時加載,也就是立即加載,這樣,我們就需要在該類中定義一個Map集合,以作為bean物件存盤的容器,

  • 宣告BeanDefinitionReader型別的變數,用來進行XML組態檔的決議,這是符合單一職責原則的,也就是說,如果你要去決議組態檔的話,那么就不要在該類里面去自己實作了,而是直接呼叫決議器的方法進行決議就行了,

    當然了,BeanDefinitionReader型別的物件創建應交由子類去實作,因為只有子類明確到底會創建BeanDefinitionReader哪個子實作類物件,

明確了AbstractApplicationContext類具有的以上特點之后,相信大家不難創建出該類,如下所示,

package com.meimeixia.framework.context.support;

import com.meimeixia.framework.beans.factory.support.BeanDefinitionReader;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.context.ApplicationContext;

import java.util.HashMap;
import java.util.Map;

/**
 * ApplicationContext介面的子實作類,用于立即加載
 * @author liayun
 * @create 2021-09-20 18:07
 */
public abstract class AbstractApplicationContext implements ApplicationContext {
    // 宣告決議器變數
    protected BeanDefinitionReader beanDefinitionReader; // 注意,這里我們只是宣告決議器變數而已,具體的物件應交由子類去創建,
                                                         // 而且,為了讓子類更好的去訪問,我們將會使用protected來修飾!

    // 定義用于存盤bean物件的Map容器,也是為了讓子類更好的去訪問,我們同樣會使用protected來修飾!
    protected Map<String, Object> singletonObjects = new HashMap<String, Object>(); // 注意,這里我們不考慮執行緒安全問題,直接創建一個HashMap物件就可以了

    // 宣告組態檔類路徑的變數,也是為了讓子類更好的去訪問,我們同樣會使用protected來修飾!
    protected String configLocation;

    @Override
    public void refresh() throws Exception {
        // 加載BeanDefinition物件,加載BeanDefinition物件,我們只需要去呼叫決議器里面的方法即可
        beanDefinitionReader.loadBeanDefinitions(configLocation);
        // 初始化bean物件,也就是創建bean物件
        finishBeanInitialization();
    }

    /**
     * bean物件的初始化
     *
     * 不妨我們來思考一個問題,就是如果我們要進行bean物件的初始化,那么應該獲取哪個東東呢?
     * 很顯然,就是BeanDefinition物件,因為BeanDefinition物件里面記錄了bean的相關信
     * 息,只有拿到這些資訊,你才能去創建物件,所以我們要去獲取BeanDefinition物件,而
     * BeanDefinition物件又是被注冊在注冊表里面的,所以首先我們還得先去獲取對應的注冊
     * 表物件!
     */
    private void finishBeanInitialization() throws Exception {
        // 獲取注冊表物件
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();

        // 獲取BeanDefinition物件
        String[] beanNames = registry.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            // 進行bean的初始化
            getBean(beanName);
        }
    }
}

注意:該類finishBeanInitialization方法中呼叫getBean方法使用到了模板方法模式,

ClassPathXmlApplicationContext類

接下來,我們來定義IoC容器介面的具體子實作類,這個子實作類的名字不妨就取名為ClassPathXmlApplicationContext,通過該類的類名我們就能知道該類主要是加載類路徑下的XML格式的組態檔,并進行bean物件的創建的

接下來,我們就來看一下該類具體要完成的功能主要有哪些?

  • 在構造方法中,創建BeanDefinitionReader物件,因為我們只是在父類中宣告了一個BeanDefinitionReader型別的變數而已,而該BeanDefinitionReader型別的物件的創建應交由具體的子類來實作,
  • 在構造方法中,呼叫refresh方法,用于進行組態檔加載、創建bean物件并存盤到容器中,
  • 重寫父介面中的getBean方法,并實作依賴注入(DI)操作,

根據以上分析,我們創建出來的ClassPathXmlApplicationContext類就應該是下面這個樣子的,

package com.meimeixia.framework.context.support;

import com.meimeixia.framework.beans.BeanDefinition;
import com.meimeixia.framework.beans.MutablePropertyValues;
import com.meimeixia.framework.beans.PropertyValue;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.beans.factory.xml.XmlBeanDefinitionReader;
import com.meimeixia.framework.utils.StringUtils;

import java.lang.reflect.Method;

/**
 * IoC容器具體的子實作類:用于加載類路徑下的XML格式的組態檔
 * @author liayun
 * @create 2021-09-20 19:00
 */
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {

    /**
     * 提供一個有參構造,該有參構造需要傳入組態檔的類路徑
     * @param configLocation
     */
    public ClassPathXmlApplicationContext(String configLocation) {
        this.configLocation = configLocation;
        // 構建決議器物件
        this.beanDefinitionReader = new XmlBeanDefinitionReader(); // 注意,我們現在是已經規定了要加載的就是XML格式的組態檔,所以這里我們創建的是XmlBeanDefinitionReader物件
        try {
            this.refresh();
        } catch (Exception e) {
            // 這兒我們不做任何處理啊!
        }
    }

    // 根據bean物件的名稱獲取bean物件
    @Override
    public Object getBean(String name) throws Exception {
        // 判斷物件容器中是否包含指定名稱的bean物件,若包含,則直接回傳即可,若不包含,則還需要自行創建
        Object obj = singletonObjects.get(name);
        if (obj != null) { // 物件容器中確實包含指定名稱的bean物件,所以直接獲取到之后直接進行回傳
            return obj;
        }

        // 物件容器中并沒有包含指定名稱的bean物件,所以我們還需要自行創建
        // 獲取BeanDefinition物件,因為它里面記錄了bean的相關資訊
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);
        // 獲取bean資訊中的className,也就是全類名
        String className = beanDefinition.getClassName();
        // 通過反射創建物件
        Class<?> clazz = Class.forName(className);
        Object beanObj = clazz.newInstance(); // 我們的目的就是為了獲取這個bean物件

        // 進行依賴注入操作
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        // 由于MutablePropertyValues類使用到了迭代器模式,所以我們就可以使用迭代器去遍歷了
        for (PropertyValue propertyValue : propertyValues) { // 如果你能使用迭代器去遍歷的話,那么就意味著你也可以使用增強for回圈去遍歷
            // 獲取name屬性的值
            String propertyName = propertyValue.getName();
            // 獲取value屬性的值
            String value = propertyValue.getValue();
            // 獲取ref屬性的值
            String ref = propertyValue.getRef();
            // 這里大家一定要注意,<property>標簽里面的value屬性和ref屬性只能存在一個!
            if (ref != null && !"".equals(ref)) {
                // 獲取依賴的bean物件
                Object bean = getBean(ref); // 這里涉及到遞回操作
                /*
                 * 拿到name屬性的值之后,我們就要去拼接對應的set方法名了,
                 *
                 * 為什么要去拼接對應的set方法呢?因為<property>標簽里面的name
                 * 屬性值是要和類中的set方法相對應的屬性名保持一致的!我相信使用過
                 * Spring框架的童鞋應該都知道這一點,
                 */
                String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
                // 獲取所有的方法物件
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (methodName.equals(method.getName())) {
                        // 通過反射執行該set方法
                        method.invoke(beanObj, bean);
                    }
                }
            }

            if (value != null && !"".equals(value)) {
                // 拼接set方法名
                String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
                // 獲取Method物件
                Method method = clazz.getMethod(methodName, String.class);
                method.invoke(beanObj, value);
            }
        }

        /*
         * 在回傳beanObj物件之前,將該物件存盤到Map容器中,
         *
         * 為什么要存放在Map容器里面呢?因為如果你不存放到Map容器里面的話,
         * 那么下一次你從Map容器里面去獲取bean物件時,肯定是獲取不到的,獲
         * 取不到的話,就意味著你還需要再去重新創建一遍,顯然這就很愚蠢了!
         */
        singletonObjects.put(name, beanObj);
        return beanObj;
    }

    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
        Object bean = getBean(name);
        if (bean == null) {
            return null;
        }
        return clazz.cast(bean); // 該cast方法就是用來進行強制型別轉換的
    }

}

由于以上類的getBean方法中需要根據<property>標簽的name屬性值拼接set方法名,所以在這里我們就專門創建了一個工具類,即StringUtils,如下所示,

package com.meimeixia.framework.utils;

/**
 * 工具類
 * @author liayun
 * @create 2021-09-20 19:31
 */
public class StringUtils {

    private StringUtils() {

    }

    // 拼接set方法名,例如userDao ---> setUserDao
    public static String getSetterMethodByFieldName(String fieldName) {
        String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        return methodName;
    }

}

測驗

,,,

總結

,,,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/317901.html

標籤:java

上一篇:java為什么要寫方法?方法是什么、如何使用?

下一篇:非科班出身,面試兩個月成功識訓阿里、美團等6個offer,經驗分享

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more