Spring Ioc 源碼分析之Bean的加載和構造

咱們都知道,Spring Ioc和Aop是Spring的核心的功能,所以花一點時間去研究仍是頗有意義的,若是僅僅是知其因此然,也就體會不到大師設計Spring的精華,還記得那句話,Spring爲JavaEE開發帶來了春天。
IOC就是Inversion of control 也就是控制反轉的意思,另外一種稱呼叫作依賴注入,這個可能更直觀一點,拿個例子來講吧:java

@Component
public class UserService {
    @Autowired
    private UserMapper mapper;
}

好比在UserService可能要調用一個Mapper,這個Mapper去作DAO的操做,在這裏咱們直接經過@Autowired註解去注入這個Mapper,這個就叫作依賴注入,你想要什麼就注入什麼,不過前提它是一個Bean。至因而怎麼注入的,那是Spring容器作的事情,也是咱們今天去探索的。
在進行分析以前,我先聲明一下,下面的這些代碼並非從spring 源碼中直接拿過來,而是經過一步步簡化,抽取spring源碼的精華,若是直接貼源碼,我以爲可能不少人都會被嚇跑,並且還不必定可以學到真正的東西。
Spring要去管理Bean首先要把Bean放到容器裏,那麼Spring是如何得到Bean的呢?node

首先,Spring有一個數據結構,BeanDefinition,這裏存放的是Bean的內容和元數據,保存在BeanFactory當中,包裝Bean的實體:面試

public class BeanDefinition {
    //真正的Bean實例
    private Object bean;
    //Bean的類型信息
    private Class beanClass;
    //Bean類型信息的名字
    private String beanClassName;
    //用於bean的屬性注入 由於Bean可能有不少熟屬性
    //因此這裏用列表來進行管理
    private PropertyValues propertyValues = new PropertyValues();
}

PerpertyValues存放Bean的全部屬性spring

public class PropertyValues {
    private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
}

PropertyValue存放的是每一個屬性,能夠看到兩個字段,name和valu。name存放的就是屬性名稱,value是object類型,能夠是任何類型安全

public class PropertyValue {
    private final String name;
    private final Object value;
}

定義好這些數據結構了,把Bean裝進容器的過程,其實就是其BeanDefinition構造的過程,那麼怎麼把一些類裝入的Spring容器呢?數據結構

Spring有個接口就是獲取某個資源的輸入流,獲取這個輸入流後就能夠進一步處理了:架構

public interface Resource {
    InputStream getInputStream() throws IOException;
}

UrlResource 是對Resource功能的進一步擴展,經過拿到一個URL獲取輸入流。app

public class UrlResource implements Resource {
    private final URL url;
    public UrlResource(URL url) {
        this.url = url;
    }
    @Override
    public InputStream getInputStream() throws IOException{
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }

ResourceLoader是資源加載的主要方法,經過location定位Resource,
而後經過上面的UrlResource獲取輸入流:框架

public class ResourceLoader {
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return new UrlResource(resource);
    }
}

你們可能會對上面的這段代碼產生疑問:分佈式

URL resource = this.getClass().getClassLoader().getResource(location);

爲何經過獲得一個類的類類型,而後獲得對應的類加載器,而後調用類加載器的Reource怎麼就獲得了URL這種類型呢?
咱們來看一下類加載器的這個方法:

public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

從這個方法中咱們能夠看出,類加載器去加載資源的時候,先會去讓父類加載器去加載,若是父類加載器沒有的話,會讓根加載器去加載,若是這兩個都沒有加載成功,那就本身嘗試去加載,這個一方面爲了java程序的安全性,不可能你用戶本身隨便寫一個加載器,就用你用戶的。

接下來咱們看一下重要角色,這個是加載BeanDefinition用的。

public interface BeanDefinitionReader {
    void loadBeanDefinitions(String location) throws Exception;
}

這個接口是用來從配置中讀取BeanDefinition:
其中registry key是bean的id,value存放資源中全部的BeanDefinition

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    private Map<String,BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<String, BeanDefinition>();
        this.resourceLoader = resourceLoader;
    }

    public Map<String, BeanDefinition> getRegistry() {
        return registry;
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}

最後咱們來看一個經過讀取Xml文件的BeanDefinitionReader:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    /**
     * 構造函數 傳入咱們以前分析過的ResourceLoader 這個經過
     * location 能夠 加載到Resource
     */
    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    /**
     * 這個方法實際上是BeanDefinitionReader這個接口中的方法
     * 做用就是經過location來構造BeanDefinition
     */
    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        //把location傳給ResourceLoader拿到Resource,而後獲取輸入流
        InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
        //接下來進行輸入流的處理
        doLoadBeanDefinitions(inputStream);
    }

    protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
        //由於xml是文檔對象,因此下面進行一些處理文檔工具的構造
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        //把輸入流解析成一個文檔,java能夠處理的文檔
        Document doc = docBuilder.parse(inputStream);
        // 處理這個文檔對象 也就是解析bean
        registerBeanDefinitions(doc);
        inputStream.close();
    }

    public void registerBeanDefinitions(Document doc) {
        //獲得文檔的根節點,知道根節點後獲取子節點就是經過層級關係處理就好了
        Element root = doc.getDocumentElement();
        //解析根節點 xml的根節點
        parseBeanDefinitions(root);
    }

    protected void parseBeanDefinitions(Element root) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            //element有屬性的包裝
            if (node instanceof Element) {
                Element ele = (Element) node;
                processBeanDefinition(ele);
            }
        }
    }

    protected void processBeanDefinition(Element ele) {
        /**
         *  <bean id="object***" class="com.***.***"/>
         */
        //獲取element的id
        String name = ele.getAttribute("id");
        //獲取element的class
        String className = ele.getAttribute("class");
        BeanDefinition beanDefinition = new BeanDefinition();
        //處理這個bean的屬性
        processProperty(ele, beanDefinition);
        //設置BeanDefinition的類名稱
        beanDefinition.setBeanClassName(className);
        //registry是一個map,存放全部的beanDefinition
        getRegistry().put(name, beanDefinition);
    }

    private void processProperty(Element ele, BeanDefinition beanDefinition) {
        /**
         *相似這種:
         <bean id="userServiceImpl" class="com.serviceImpl.UserServiceImpl">
         <property name="userDao" ref="userDaoImpl"> </property>
         </bean>
         */
        NodeList propertyNode = ele.getElementsByTagName("property");
        for (int i = 0; i < propertyNode.getLength(); i++) {
            Node node = propertyNode.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                //得到屬性的名稱
                String name = propertyEle.getAttribute("name");
                //獲取屬性的值
                String value = propertyEle.getAttribute("value");
                if (value != null && value.length() > 0) {
                    //設置這個bean對應definition裏的屬性值
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
                } else {
                    //value是Reference的話  就會進入到這裏處理
                    String ref = propertyEle.getAttribute("ref");
                    if (ref == null || ref.length() == 0) {
                        throw new IllegalArgumentException("Configuration problem: <property> element for property '"
                                + name + "' must specify a ref or value");
                    }
                    //構造一個BeanReference 而後把這個引用方到屬性list裏
                    BeanReference beanReference = new BeanReference(ref);
                    beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
                }
            }
        }
    }
}

上面用到了BeanReference(以下),其實這個和PropertyValue相似,用不一樣的類型是爲了更好的區分:

public class BeanReference {
     private String name;
     private Object bean;
}

好了,到如今咱們已經分析完了Spring是如何找到Bean並加載進入Spring容器的,這裏面最主要的數據結構就是BeanDefinition,ReourceLoader來完成資源的定位,讀入,而後獲取輸入流,進一步的處理,這個過程當中有對xml文檔的解析和對屬性的填充。

感興趣能夠加Java架構師羣獲取Java工程化、高性能及分佈式、高性能、深刻淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的直播免費學習權限 都是大牛帶飛 讓你少走不少的彎路的 羣..號是:855801563 對了 小白勿進 最好是有開發經驗

注:加羣要求

一、具備工做經驗的,面對目前流行的技術不知從何下手,須要突破技術瓶頸的能夠加。

二、在公司待久了,過得很安逸,但跳槽時面試碰壁。須要在短期內進修、跳槽拿高薪的能夠加。

三、若是沒有工做經驗,但基礎很是紮實,對java工做機制,經常使用設計思想,經常使用java開發框架掌握熟練的,能夠加。

四、以爲本身很牛B,通常需求都能搞定。可是所學的知識點沒有系統化,很難在技術領域繼續突破的能夠加。

5.阿里Java高級大牛直播講解知識點,分享知識,多年工做經驗的梳理和總結,帶着你們全面、科學地創建本身的技術體系和技術認知!

相關文章
相關標籤/搜索