深入理解spring(spring源碼(IOC))(七)

回顧,總結!

1、上一篇講道JDK的動態代理。我們已經手動實現了一個山寨版本的動態代理。
2、思考一個問題?動態代理給我們的都是代理類。如果我們不想要代理類呢?怎麼辦?

1、spring 源碼 之IOC(手動裝配,自動裝配)!

1、今天主要跟大家分享一下Spring 的IOC。

老規矩,先上代碼!引入問題。
如下圖所示,UserServiceImpl中要調用 UserDao的query()方法?我們該怎麼做?

在這裏插入圖片描述

方法很多!

1、我們可以用代理。
2、可以用裝飾者模式,把userDao對象傳進去,
3、可以直接new UserDao()對象出來。。

先看看Spring怎麼做的(Spring 手動裝配)
1、先寫一個我們自己的spring.xml 下面的xml 用過spring的人都很熟悉,這裏就不做多的講解了。
<?xml version="1.0" encoding="UTF-8"?>
<!--
    1、哪些類需要我來關聯
    2、怎麼告訴我這些類(寫bean)
    3、怎麼維護依賴關係(settr、constr)
    4、怎麼體現setter或者constr
 -->
<beans>
    <bean id="dao" class="com.myspring.dao.impl.UserDaoImpl1"></bean>
    <bean id="service" class="com.myspring.service.UserServiceImpl">
        <!--set方法 方式 注入-->
        <property name="dao" ref="dao"></property>
    </bean>
</beans>
2、有了XML 我們只需要dom4j來解析XML 然後反射就能拿到bean對象完成set方法裝配?

1、這裏講一下如下代碼片段 山寨版本BeanFactory 的思路。
2、先傳入需要解析的XML,實例化dom4j對象。
3、獲取到XML的跟節點 拿到子節點,獲取ID 與class就能拿到XML中我們定義的內容
4、判斷如果 xml中存在字標籤,且子標籤的名稱爲property
5、從map中拿出 ref的引用對象,反射拿到名稱爲 name的屬性,調用set方法,set對象到當前實例

public class BeanFactory {
    /**
     * 構造方法,傳入要解析的xml
     */
    public BeanFactory(String xml) throws UnsupportedEncodingException {
        parseXml(xml);
    }
    //定義 模擬spring 容器
    Map<String,Object> map = new HashMap<String,Object>();
    /**
     * 解析XML
     * @param xml
     * @throws UnsupportedEncodingException
     */
    public void parseXml(String xml) throws UnsupportedEncodingException {
        //獲取到當前類根目錄下的 spring 地址
        File file = new File(this.getClass().getResource("/").getPath()+"//"+xml);
        //創建Dom4j讀取spring.xml內容信息
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            //獲取到跟節點
            Element elementRoot = document.getRootElement();
            //遍歷節點
            for (Iterator<Element> itFirlst = elementRoot.elementIterator(); itFirlst.hasNext();) {
                Element elementFirstChil = itFirlst.next();
                //拿到 xml中id屬性 <bean id="dao" class="com.myspring.dao.UserDaoImpl"></bean>
                Attribute attributeId = elementFirstChil.attribute("id");
                String beanName = attributeId.getValue();
                //拿到xml中class屬性
                Attribute attributeClass = elementFirstChil.attribute("class");
                String clazzName  = attributeClass.getValue();
                Class clazz = Class.forName(clazzName);
                /**
                 * 維護依賴關係
                 * 看這個對象有沒有依賴(判斷是否有property。或者判斷類是否有屬性)
                 * 如果有則注入
                 */
                Object object = null;
                for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext();){
                   // 得到ref的value,通過value得到對象(map)
                    // 得到name的值,然後根據值獲取一個Filed的對象
                    //通過field的set方法set那個對象
                    //<property name="dao" ref="dao"></property>
                    Element elementSecondChil = itSecond.next();
                    if(elementSecondChil.getName().equals("property")){
                        //由於是property所以是 setter方法注入
                        object= clazz.newInstance();
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        String nameVlaue = elementSecondChil.attribute("name").getValue();
                        Field field = clazz.getDeclaredField(nameVlaue);
                        field.setAccessible(true);
                        field.set(object,injetObject);
                    }
                }
                if(object==null){//沒有子標簽
                    object = clazz.newInstance();
                }
                map.put(beanName,object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(map);
    }

    /**
     * 獲取Bean
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        return map.get(beanName);
    }
3、測試一把?
    public static void main(String[] args) throws UnsupportedEncodingException {
        UserService service = (UserService) new BeanFactory("spring.xml").getBean("service");
        service.find();
    }
    ------------------控制檯輸出-------------------------------
    {dao=com.myspring.dao.impl.UserDaoImpl1@3b9a45b3, service=com.myspring.service.UserServiceImpl@7699a589}
	service
	dao1

以上就完成 我們手動裝配的 set方法裝配!

4、那麼問題來了,spring的構造方法裝配。又是怎麼實現的呢?

1、改造一下 剛剛的spring.xml文件。我們用構造方式裝配。其實這些標籤我們可以隨便寫。都懂吧?

<beans>
    <bean id="dao" class="com.myspring.dao.impl.UserDaoImpl1"></bean>
    <bean id="service" class="com.myspring.service.UserServiceImpl">
        <!--構造方法方式 注入-->
        <constructor-arg name="dao" ref="dao"></constructor-arg>
    </bean>
</beans>

2、改造一下,我們的實現類。加上一個構造方法。(既然是構造方法裝配,肯定得有構造方法是吧?)

public class UserServiceImpl implements UserService {
    UserDao dao;
    public void setDao(UserDao userDao) {
        this.dao = userDao;
    }
    public UserServiceImpl(UserDao dao) {
        this.dao = dao;
    }
    @Override
    public void find() {
        System.out.println("service");
        dao.query();
    }
}

3、改造一下 我們山寨版的BeanFactory 添加構造方法裝配的邏輯。
1.因爲我們這個是 借鑑spring源碼 山寨版實現的方式。。所以就沒判斷多實現的一種情況。。大家可以完善
1、具體思路,如果不爲property 證明有構造方式 裝配。
2、然後拿到 ref標籤,通過ref名稱 找到接口的第一個實現,(可能會多實現大家自己完善)
3、通過反射 實現構造方法模式的初始化。

public class BeanFactory {
    /**
     * 構造方法,傳入要解析的xml
     */
    public BeanFactory(String xml) throws UnsupportedEncodingException {
        parseXml(xml);
    }
    //定義 模擬spring 容器
    Map<String,Object> map = new HashMap<String,Object>();
    /**
     * 解析XML
     * @param xml
     * @throws UnsupportedEncodingException
     */
    public void parseXml(String xml) throws UnsupportedEncodingException {
        //獲取到當前類根目錄下的 spring 地址
        File file = new File(this.getClass().getResource("/").getPath()+"//"+xml);
        //創建Dom4j讀取spring.xml內容信息
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            //獲取到跟節點
            Element elementRoot = document.getRootElement();
            //遍歷節點
            for (Iterator<Element> itFirlst = elementRoot.elementIterator(); itFirlst.hasNext();) {
                Element elementFirstChil = itFirlst.next();
                //拿到 xml中id屬性 <bean id="dao" class="com.myspring.dao.UserDaoImpl"></bean>
                Attribute attributeId = elementFirstChil.attribute("id");
                String beanName = attributeId.getValue();
                //拿到xml中class屬性
                Attribute attributeClass = elementFirstChil.attribute("class");
                String clazzName  = attributeClass.getValue();
                Class clazz = Class.forName(clazzName);
                /**
                 * 維護依賴關係
                 * 看這個對象有沒有依賴(判斷是否有property。或者判斷類是否有屬性)
                 * 如果有則注入
                 */
                Object object = null;
                for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext();){
                   // 得到ref的value,通過value得到對象(map)
                    // 得到name的值,然後根據值獲取一個Filed的對象
                    //通過field的set方法set那個對象
                    //<property name="dao" ref="dao"></property>
                    Element elementSecondChil = itSecond.next();
                    if(elementSecondChil.getName().equals("property")){
                        //由於是property所以是 setter方法注入
                        object= clazz.newInstance();
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        String nameVlaue = elementSecondChil.attribute("name").getValue();
                        Field field = clazz.getDeclaredField(nameVlaue);
                        if (null == field)throw new RuntimeException("沒有找到"+nameVlaue+"屬性");
                        field.setAccessible(true);
                        field.set(object,injetObject);
                    }else {
                        //證明構造方法裝配
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        Class injectObjectClazz = injetObject.getClass();
                        Constructor constructor = clazz.getConstructor(injectObjectClazz.getInterfaces()[0]);
                        object = constructor.newInstance(injetObject);
                    }
                }
                if(object==null){//沒有子標簽
                    object = clazz.newInstance();
                }
                map.put(beanName,object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(map);
    }

    /**
     * 獲取Bean
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        return map.get(beanName);
    }
5、測試構造方法裝配?

同樣得方式,這裏就不貼 代碼出來了。。大家可以自己去試一下。

6、spring 除了手動裝配。還有一個自動裝配。

1、接下來 演示一下自動裝配的實現方式。

第一步 改造spring.xml

1、加上一個標籤 default=“byType” 我們這次主要演示,byType的方式 實現自動裝配。

<beans default="byType">
    <bean id="dao" class="com.myspring.dao.impl.UserDaoImpl1"></bean>
    <bean id="service" class="com.myspring.service.UserServiceImpl"></bean>
</beans>
第二步,改造業務代碼類。

1、刪掉了,構造方法,與set方法。這裏沒有加註解。我們一步一步來。。後面會引入註解。byName方式注入。

public class UserServiceImpl implements UserService {
    UserDao dao;
    @Override
    public void find() {
        System.out.println("service");
        dao.query();
    }
}
第三步,改造BeanFactory類。

1、如下,是我們第三次改造後 完整的 解析XML方法
2、具體思路如下。先判斷是否包含有 自動注入的標籤。如果有則 flag=true;
3、如果是自動注入,先拿到當前裝配類的所有屬性。
4、如果map中的class 與當前裝配對象屬性的CLass相等。則裝配該屬性到當前對象。

  /**
     * 解析XML
     * @param xml
     * @throws UnsupportedEncodingException
     */
    public void parseXml(String xml) throws UnsupportedEncodingException {
        //獲取到當前類根目錄下的 spring 地址
        File file = new File(this.getClass().getResource("/").getPath()+"//"+xml);
        //創建Dom4j讀取spring.xml內容信息
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(file);
            //獲取到跟節點
            Element elementRoot = document.getRootElement();
            //判斷是否自動裝配
            Attribute attribute = elementRoot.attribute("default");
            boolean flag=false;
            if (attribute!=null){
                flag=true;
            }
            //遍歷節點
            for (Iterator<Element> itFirlst = elementRoot.elementIterator(); itFirlst.hasNext();) {
                /**
                 * setup1、實例化對象
                 */
                Element elementFirstChil = itFirlst.next();
                //拿到 xml中id屬性 <bean id="dao" class="com.myspring.dao.UserDaoImpl"></bean>
                Attribute attributeId = elementFirstChil.attribute("id");
                String beanName = attributeId.getValue();
                //拿到xml中class屬性
                Attribute attributeClass = elementFirstChil.attribute("class");
                String clazzName  = attributeClass.getValue();
                Class clazz = Class.forName(clazzName);
                /**
                 * 維護依賴關係
                 * 看這個對象有沒有依賴(判斷是否有property。或者判斷類是否有屬性)
                 * 如果有則注入
                 */
                Object object = null;
                for (Iterator<Element> itSecond = elementFirstChil.elementIterator(); itSecond.hasNext();){
                   // 得到ref的value,通過value得到對象(map)
                    // 得到name的值,然後根據值獲取一個Filed的對象
                    //通過field的set方法set那個對象
                    //<property name="dao" ref="dao"></property>
                    Element elementSecondChil = itSecond.next();
                    if(elementSecondChil.getName().equals("property") && elementSecondChil.getName().equals("property")){
                        //由於是property所以是 setter方法注入
                        object= clazz.newInstance();
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        String nameVlaue = elementSecondChil.attribute("name").getValue();
                        Field field = clazz.getDeclaredField(nameVlaue);
                        if (null == field)throw new RuntimeException("沒有找到"+nameVlaue+"屬性");
                        field.setAccessible(true);
                        field.set(object,injetObject);
                    }else {
                        //證明構造方法裝配
                        String refVlaue = elementSecondChil.attribute("ref").getValue();
                        Object injetObject= map.get(refVlaue) ;
                        Class injectObjectClazz = injetObject.getClass();
                        Constructor constructor = clazz.getConstructor(injectObjectClazz.getInterfaces()[0]);
                        object = constructor.newInstance(injetObject);
                    }
                }
                //spring 自動裝配邏輯
                if(object==null) {
                    if (flag) {
                        if (attribute.getValue().equals("byType")) {
                            //判斷是否有依賴
                            Field fields[] = clazz.getDeclaredFields();
                            for (Field field : fields) {
                                //得到屬性的類型,比如String aa那麼這裏的field.getType()=String.class
                                Class injectObjectClazz = field.getType();
                                /**
                                 * 由於是bytype 所以需要遍歷map當中的所有對象
                                 * 判斷對象的類型是不是和這個injectObjectClazz相同
                                 */
                                int count = 0;
                                Object injectObject = null;
                                for (String key : map.keySet()) {
                                    Class temp = map.get(key).getClass().getInterfaces()[0];
                                    if (temp.getName().equals(injectObjectClazz.getName())) {
                                        injectObject = map.get(key);
                                        //記錄找到一個,因爲可能找到多個count
                                        count++;
                                    }
                                }
                                if (count > 1) {
                                    throw new RuntimeException("需要一個對象,但是找到了兩個對象");
                                } else {
                                    object = clazz.newInstance();
                                    field.setAccessible(true);
                                    field.set(object, injectObject);
                                }
                            }
                        }
                    }
                }
                if(object==null){//沒有子標簽
                    object = clazz.newInstance();
                }
                map.put(beanName,object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(map);
    }
7、測試自動裝配

1、正向流程,就跟前面的測試一模一樣。。大家可以自己去試。
2、這裏講一下反向流程
3、如圖所示,如果我們userdao是有多個實現類。則按類型注入,因爲都實現了 UserDao.就不知道找哪一個了。。
在這裏插入圖片描述

總結!

1、如上類容 我們先實現了。山寨版本的 set方法手動注入,
2、接着實現了。構造方法的方式注入。
3、然後實現了 自動裝配。。
那麼我們自己寫的山寨版本的 BeanFactory 跟spring的BeanFactory有什麼區別呢?
其實基本上spring 大致的思路 都是我們寫的差不多。。不過spring的代碼會複雜很多。。
後面我們會慢慢來研究spring的源碼。。包括註解方式注入。後面都會一一研究。。

有興趣的 朋友可以點一個關注哦。

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