1. 程式人生 > 其它 >五分鐘,手擼一個簡單的Spring容器

五分鐘,手擼一個簡單的Spring容器

工廠和Spring容器
Spring是一個成熟的框架,為了滿足擴充套件性、實現各種功能,所以它的實現如同枝節交錯的大樹一樣,現在讓我們把視線從Spring本身移開,來看看一個萌芽版的Spring容器怎麼實現。

Spring的IOC本質就是一個大工廠,我們想想一個工廠是怎麼執行的呢?
————————————————

生產產品:一個工廠最核心的功能就是生產產品。在Spring裡,不用Bean自己來例項化,而是交給Spring,應該怎麼實現呢?——答案毫無疑問,反射。

那麼這個廠子的生產管理是怎麼做的?你應該也知道——工廠模式。

庫存產品:工廠一般都是有庫房的,用來庫存產品,畢竟生產的產品不能立馬就拉走。Spring我們都知道是一個容器,這個容器裡存的就是物件,不能每次來取物件,都得現場來反射建立物件,得把創建出的物件存起來。

訂單處理:還有最重要的一點,工廠根據什麼來提供產品呢?訂單。這些訂單可能五花八門,有線上籤籤的、有到工廠籤的、還有工廠銷售上門籤的……最後經過處理,指導工廠的出貨。

在Spring裡,也有這樣的訂單,它就是我們bean的定義和依賴關係,可以是xml形式,也可以是我們最熟悉的註解形式。
————————————————
涉及到需要的事物有:

訂單:Bean定義

Bean可以通過一個配置檔案定義,我們會把它解析成一個型別。

userDao = com.example.apidemo.spring.UserDao

BeanConfig.java

bean定義類,配置檔案中bean定義對應的實體

package com.example.apidemo.spring;

import lombok.Data;

/** * bean定義類,配置檔案中bean定義對應的實體 */ @Data public class BeanConfig { private String beanName; private Class beanClass; //省略getter、setter }

獲取訂單:資源載入

接下訂單之後,就要由銷售向生產部門交接,讓生產部門知道商品的規格、數量之類。

資源載入器,就是來完成這個工作的,由它來完成配置檔案中配置的載入。

/**
 * 獲取訂單:資源載入
 * @return
 */
public class ResourceLoader {

    
public static Map<String, BeanConfig> getResource() { Map<String, BeanConfig> beanDefinitionMap = new HashMap<>(16); Properties properties = new Properties(); try { //配置beans.properties檔案: userDao = com.example.apidemo.spring.UserDao InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties"); properties.load(inputStream); Iterator<String> it = properties.stringPropertyNames().iterator(); while (it.hasNext()) { String key = it.next(); String className = properties.getProperty(key); BeanConfig beanConfig = new BeanConfig(); beanConfig.setBeanName(key); Class clazz = Class.forName(className); beanConfig.setBeanClass(clazz); beanDefinitionMap.put(key, beanConfig); } inputStream.close(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return beanDefinitionMap; } }

訂單分配:Bean註冊

物件註冊器,這裡用於單例bean的快取,我們大幅簡化,預設所有bean都是單例的。可以看到所謂單例註冊,也很簡單,不過是往HashMap裡存物件。

/**
 * 倉庫:訂單分配:Bean註冊
 */
public class BeanRegister {

    //單例Bean快取
    private Map<String, Object> singletonMap = new HashMap<>(32);

    /**
     * 獲取單例Bean
     *
     * @param beanName bean名稱
     * @return
     */
    public Object getSingletonBean(String beanName) {
        return singletonMap.get(beanName);
    }

    /**
     * 註冊單例bean
     *
     * @param beanName
     * @param bean
     */
    public void registerSingletonBean(String beanName, Object bean) {
        if (singletonMap.containsKey(beanName)) {
            return;
        }
        singletonMap.put(beanName, bean);
    }

}

生產車間:物件工廠

好了,到了我們最關鍵的生產部門了,在工廠裡,生產產品的是車間,在IOC容器裡,生產物件的是BeanFactory。

  • 物件工廠,我們最核心的一個類,在它初始化的時候,建立了bean註冊器,完成了資源的載入。

  • 獲取bean的時候,先從單例快取中取,如果沒有取到,就建立並註冊一個bean

/**
 * 生產車間:物件工廠
 * 流程:《生產車間:物件工廠》==執行BeanFactory()初始化方法,ResourceLoader().getResource()去==《獲取訂單:資源載入》
 *      返回一個BeanFactory,裡面包含配置的bean例項。 如果第一次呼叫,就是==《倉庫:訂單分配:Bean註冊》,用BeanRegister去註冊一個單例的bean,
 *      並且儲存到BeanRegister,如果第二次呼叫,先從快取beanRegister取,獲取不到再註冊。
 */
public class BeanFactory {

    private Map<String, BeanConfig> beanDefinitionMap = new HashMap<>();

    private BeanRegister beanRegister;

    public BeanFactory() {
        //建立bean註冊器
        beanRegister = new BeanRegister();
        //載入資源
        this.beanDefinitionMap = new ResourceLoader().getResource();
    }

    /**
     * 獲取bean
     *
     * @param beanName bean名稱
     * @return
     */
    public Object getBean(String beanName) {
        //從bean快取中取
        Object bean = beanRegister.getSingletonBean(beanName);
        if (bean != null) {
            return bean;
        }
        //根據bean定義,建立bean
        return createBean(beanDefinitionMap.get(beanName));
    }

    /**
     * 建立Bean
     *
     * @param beanConfig bean定義
     * @return
     */
    private Object createBean(BeanConfig beanConfig) {
        try {
            Object bean = beanConfig.getBeanClass().newInstance();
            //快取bean
            beanRegister.registerSingletonBean(beanConfig.getBeanName(), bean);
            return bean;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

生產銷售:測試

  • UserDao.java

    我們的Bean類,很簡單

/**
 * 生產銷售:測試
 */
public class UserDao {
    public void queryUserInfo(){
        System.out.println("new A good man.");
    }
}

單元測試:

import org.junit.Test;

public class ApiTest {

    /**
     * 演示一個簡單的spring容器框架
     */
    @Test
    public void test_BeanFactory() {

        //1.建立bean工廠(同時完成了載入資源、建立註冊單例bean註冊器的操作)
        BeanFactory beanFactory = new BeanFactory();

        //2.第一次獲取bean(通過反射建立bean,快取bean)
        UserDao userDao1 = (UserDao) beanFactory.getBean("userDao");
        userDao1.queryUserInfo();

        //3.第二次獲取bean(從快取中獲取bean)
        UserDao userDao2 = (UserDao) beanFactory.getBean("userDao");
        userDao2.queryUserInfo();
    }
}

結果: