【spring系列】一個袖珍版的spring

前言

​ spring源碼是一個困擾我很久的問題,這裏面水又深有渾,跟一會就蒙圈了,好不容易找到了一點思路,時間一久,概念又模糊了,然後再去跟源碼的時候又是一個噁心的循環。萬般無奈,高仿一個spring來幫助下次快速梳理springIOC源碼;

​ 那就從第一次接觸spring來梳理吧:

    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserInfo userInfo = (UserInfo) classPathXmlApplicationContext.getBean("userInfo");
        System.out.println(userInfo.getName());
    }

​ 第一次接觸spring,就聽說,對象不用new,直接從容器中拿就可以了,我想大多數人心中都會有一個這樣的概念吧。就如上代碼,spring加載完xml文件之後,直接getBean方法就可以獲取對象了,省去了new UserInfo() 。但是new ClassPathXmlApplicationContext("spring.xml") 又幫我們做了什麼呢?

至此,對於如上3行代碼進行高仿袖珍版springIOC容器,只是最簡單的springIOC容器,如有不對或者不嚴謹的地方,請大家即使指出,我會盡快修改,免得誤導別人。

1.創建核心接口

​ 既然高仿就要像一點,第一步要創建心臟 BeanFactory 了。然後核心接口一定要有的。


/**
 * Created by yangman on 2020/5/8.
 * 精簡版本也要高仿的像一點 BeanFactory 不能少。
 */
public interface BeanFactory {
    // 來兩個核心方法得了,其他的暫時先不搞了。
    Object getBean(String name) throws IllegalAccessException, InstantiationException, NoSuchFieldException;
    <T> T getBean(String name, @Nullable Class<T> requiredType) throws IllegalAccessException, NoSuchFieldException, InstantiationException, NoSuchMethodException, InvocationTargetException;
}

接着創建子接口,HierarchicalBeanFactoryListableBeanFactoryApplicationContext但是前兩個用的不多,注重說ApplicationContext吧。

​ 網上對於ApplicationContext接口有一個這樣的說法,就是說如果比喻BeanFactory是心臟,則ApplicationContext就是軀幹。由於這裏只是高仿,大部分功能是用不到,所以不寫了。

/**
 * Created by yangman on 2020/5/8.
 * beanFactory只有一些簡單的,核心的功能,
 * ApplicationContext的接口就是一個全面化的接口,集結所有功能爲一身。
 *  1.可以裝配管理bean
 *  2.源碼中實現了ResourcePatternResolver->ResourceLoader 接口,可以加載資源文件
 *  3.可以進行國際化處理等等
 */
public interface ApplicationContext extends HierarchicalBeanFactory,ListableBeanFactory {

}

接口有了,進行實現吧。創建ClassPathXmlApplicationContext實現ApplicationContext接口,注:spring源碼中ClassPathXmlApplicationContext類不是直接實現的ApplicationContext接口。

public class ClassPathXmlApplicationContext implements ApplicationContext {
  public ClassPathXmlApplicationContext(String configLocation) {
        this(new String[] {configLocation});
    }
   public ClassPathXmlApplicationContext(String[] configLocations) {
        // 源碼中做了相應的處理, 這裏進行簡化
        this.configLocations = configLocations;
        synchronized (this.startupShutdownMonitor) {
            try{
                // 讀取配置文件
                Document document = loadConfig();
                //生成BeanDefinition
                createBeanDefinition(document);
                // IOC注入
                IocAutoWrite();
            }catch (Exception ex){
                    ex.printStackTrace();
            }
        }
    }
}

2.讀取配置文件

​ 通過loadConfig方法進行處理文件路徑,並且加載配置。

​ 應該還記得classpath吧,在源碼中PathMatchingResourcePatternResolver.getResources 可見真正的解析過程。

    private Document loadConfig() throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        // 這裏其實是一個數組,只是爲了演示,這裏就不循環處理了。
        InputStream inputStream = this.getFileInputStream(configLocations[0]);
        // 獲取doc對象
        Document document= docBuilder.parse(inputStream);
        return document;
    }
    // 獲取並處理文件路徑
    private InputStream getFileInputStream(String configLocation) {
        // 真正的源碼中spring 有對classpath* 和 classpath 這兩個開頭做了處理  PathMatchingResourcePatternResolver.getResources 可見
        int prefixEnd =   (configLocation.startsWith("war:") ? configLocation.indexOf("*/") + 1 :
                configLocation.indexOf(':') + 1);
        // this.getClass().getClassLoader().getResourceAsStream 這個函數尋找文件的起點是JAVA項目編譯之後的根目錄 ,也就是/target/class目錄啦
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
        return inputStream;
    }

3.生成BeanDefinition

​ 可能有的小夥伴對與BeanDefinition這個東西有點陌生,BeanDefinition是用來存放bean的元數據的,比如bean的類名,屬性,scope等信息。但是就疑問了?這些東西配置文件中(xml)原本就有呀,何必在存一份呢?這裏可以打個比方,BeanDefinition有如手機配件,bean是手機,手機配件從工廠進行加工出來,需要進行檢測才能放到手機上,xml中的信息就是剛剛加工出來的手機配件,BeanDefinition就是檢驗合格的手機配件。xml中的信息加載到BeanDefinition中,纔可以人爲xml配置的信息被成功解析。

xml文件讀取之後,接下進行驗證xml文件,並且初始化到BeanDefinition中了。

通過createBeanDefinition初始化BeanDefinition

3.1創建BeanDefinition

BeanDefinition是一個接口,這裏用到AbstractBeanDefinition進行實現,現在存儲xml文件內容

/**
 * Created by likuo on 2020/5/9.
 * 高仿spring AbstractBeanDefinition.java
 */
public class AbstractBeanDefinition implements BeanDefinition {
    // 類
    private volatile Object beanClass;
    // bean名稱,用來.getBean()中的值
    private String beanName;
    // bean的屬性值 主要用來存放  <property name="name" value="aa"></property> 這一段
    private MutablePropertyValues propertyValues;

    public Object getBeanClass() {
        return beanClass;
    }

    public void setBeanClass(Object beanClass) {
        this.beanClass = beanClass;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public MutablePropertyValues getPropertyValues() {
        return propertyValues;
    }

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

3.2讀取配置文件

主要完成從xml讀取,並且生成BeanDefinition,BeanDefinition中存入類名,bean名稱,和屬性值,相當於把配置文件完全讀取到內存中。

// 源碼中就比較複雜, 這裏就從簡了哈。
private void createBeanDefinition(Document document) throws ClassNotFoundException {
    // 獲取根節點
    Element documentElement = document.getDocumentElement();
    //這個方法名字也是高仿的,下載源碼搜索就能搜索到此方法
    doRegisterBeanDefinitions(documentElement);
}

xml文件中有很多種標籤,,,等,這裏進行解析。

private void doRegisterBeanDefinitions(Element documentElement) throws ClassNotFoundException {
        // 看到這兩個判斷就豁然開朗了吧,此內容高仿 DefaultBeanDefinitionDocumentReader.parseDefaultElement方法,有興趣的看一下哈。
        // 如果是<beans> 則解析下子元素
        if(NESTED_BEANS_ELEMENT.equals(documentElement.getNodeName())){
            NodeList childNodes = documentElement.getChildNodes();
            for(int i = 0 ;i<childNodes.getLength();i++){
                Node item = childNodes.item(i);
                if(item instanceof Element) {
                    // beans下面應該是bean,這裏進行循環解析
                    doRegisterBeanDefinitions((Element)item);
                }

            }
        }else if(BEAN_ELEMENT.equals(documentElement.getNodeName())){
            AbstractBeanDefinition beanDefinition = new AbstractBeanDefinition();
            // 解析id屬性
            String beanName = documentElement.getAttribute(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
            // 解析class屬性
            String className = documentElement.getAttribute(BeanDefinitionParserDelegate.CLASS_ATTRIBUTE);
            // 設置值
            beanDefinition.setBeanClass(Class.forName(className));
            beanDefinition.setBeanName(beanName);
            // 如果有設置了值 property,則進行解析
            NodeList childNodes = documentElement.getChildNodes();
            if(childNodes!=null){
                List<PropertyValue> propertyValueList = new ArrayList<>();
                for(int i = 0 ;i<childNodes.getLength();i++){
                    Node item = childNodes.item(i);
                    if(item instanceof Element) {
                         Element property = (Element)item;
                        if(BeanDefinitionParserDelegate.PROPERTY_ELEMENT.equals(property.getNodeName())){
                            String propertyName = property.getAttribute(BeanDefinitionParserDelegate.NAME_ATTRIBUTE);
                            String propertyValue = property.getAttribute(BeanDefinitionParserDelegate.VALUE_ATTRIBUTE);
                            propertyValueList.add(new PropertyValue(propertyName,propertyValue));
                        }
                    }
                }
                beanDefinition.setPropertyValues(new MutablePropertyValues(propertyValueList));
            }
            // 保存beanDefinition
            beanDefinitionMap.put(beanName,beanDefinition);
            // 獲取的beanName放到集合中
            beanDefinitionNames.add(beanName);
        }
    }

4.IOC注入

通過IocAutoWrite()進行IOC注入,主要是通過BeanDefinition轉換成bean對象,並且放到map中。原本bean是一個代理對象,但是這裏就從簡,直接就放真實對象了。

    // 這裏進行注入
    private void IocAutoWrite() throws IllegalAccessException, NoSuchFieldException, InstantiationException {
        System.out.println("進行注入");
        for(String beanName:beanDefinitionNames){
            this.getBean(beanName);
        }
    }
 @Override
    public Object getBean(String name)   {
        // 如果存在則直接返回
        if(singletonObjects.containsKey(name)){
            return singletonObjects.get(name);
        }else{
            Object o = null;
            try {
                o = doCreateBean(name);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return o;
        }
    }

在spring中,一般do開頭的方法都需要重點的看下, 裏面會實現很多邏輯。這裏也高仿真一下。

    // 高仿 DefaultListableBeanFactory
    private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
    //高仿DefaultSingletonBeanRegistry 用來存放單例的對象
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);   


private Object doCreateBean(String name) throws IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        // 現在只處理單例情況
        AbstractBeanDefinition beanDefinition = beanDefinitionMap.get(name);
        // 這次不說代理的情況,原本是使用cglib進行代理,這裏簡化下
        // 創建對象
         Class clazz = (Class) beanDefinition.getBeanClass();
         Object o = clazz.newInstance();
        // 設置屬性  注意, 真實spring並不是這麼設置的。。
        if(beanDefinition.getPropertyValues()!=null){
            List<PropertyValue> propertyValueList = beanDefinition.getPropertyValues().getPropertyValueList();
            for(PropertyValue propertyValue:propertyValueList){
                // 獲取set方法的名稱
                String setMethod = this.getSetMethod(propertyValue.getName());
                // 通過調用set方法進行賦值
                // 
                Method method = clazz.getMethod(setMethod,String.class);
                method.invoke(o,propertyValue.getValue());
            }
        }
        // 用來存放單例bean的map
        singletonObjects.put(name,o);
        return o;
    }

至此IOC注入完成。

5.測試

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userInfo" class="com.base.entity.UserInfo">
        <property name="name" value="張三"></property>
    </bean>
</beans>

創建xml文件

public class UserInfo {
    public Long id;
    public String name;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
        UserInfo userInfo = (UserInfo) classPathXmlApplicationContext.getBean("userInfo");
        System.out.println(userInfo.getName());
    }
}

輸出:

加載配置文件
初始化BeanDefinition
進行注入
張三

git地址:https://gitee.com/likuoblog/spring_in_pocket

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章