拋開Spring去理解IOC思想 - 你會發現IOC原來就這麼簡單!

  • 不少小夥伴們看到標題可能就會想到拋開Spring就不會存在IOC思想了,其實否則在接下來的文章中就會講述到。
  • 不少小夥伴在理解IOC的時候一般會和Spring放到一塊兒去學習,首先呢Spring設計的很是之巧妙並且裏面包含了不少除去IOC的其餘功能。這樣會致使咱們在Spring的基礎去理解IOC就變得很困難。很難抓住其核心思想。
  • 因此本文的標題的含義就是單純的去理解IOC思想,而後經過自定義的IOC去加深對IOC的理解。
  • 看完本文以後再去理解Spring中的IOC其實思想是一致的,只是實現上有些出入。畢竟Spring是大神們通過深思熟慮後的成果。

拋開Spring去理解IOC思想思惟導圖.jpg

傳統的思想(沒有IOC容器的狀況下)

在沒有IOC容器的狀況下,若是咱們須要某個類具體的操做以下所示:java

15884867335273.jpg
傳統的Bean建立git

  1. 程序員對A進行了主動的使用(new)即建立了A類,A類中又依賴B類而後在對B類進行建立
  2. 建立對象的權利如今是程序員手上須要哪一個類就會對那個類進行建立
  3. B類是因爲程序員調用了A類有對B類的依賴隨着而建立
  4. 總之就是本身動手豐衣足食,沒有藉助任何中間產物。

優勢

  • 很直接的一個優勢就是簡單,咱們須要什麼就去建立什麼,在程序員的角度上也是比較直觀容易理解的。

缺點

  • Bean之間的協調關係是由程序內部代碼來控制的即經過New關鍵字與咱們的業務代碼進行了強耦合。
  • 沒有對Bean進行管理。
  • 對Bean沒有進行統一的管理和配置。

IOC思想

首先在這裏強調一下IOC不是Spring提出來了,在Spring以前就已經有人提出了IOC思想,只不過在Spring以前都是偏理論化沒有一個具體的落地方案,Spring在技術層面把IOC思想體現的淋漓盡致。程序員

什麼是IOC(Inversion of controller)

  • IOC是一種思想,而不是一個技術的實現。
  • 主要的描述是在軟件開發的領域對象的建立和管理的問題。
  • 上述咱們瞭解了傳統的開發模式,咱們在來看看若是有IOC的思想下程序員是如何使用對象的。

15884884547204.jpg

從上圖可知:github

  • 程序員只須要告訴IOC本身須要那個Bean。就不須要關係該Bean建立的細節已經該Bean的相關依賴。這一切IOC容器已經幫你作好了。
  • 凡事有得必有失: 這個過程當中咱們失去了建立Bean的權利。
  • 瞭解了基本的使用後,有人說IOC就是控制反轉,講到這裏你還沒將控制反轉?好!別急接下來就是細講咱們常說的控制反轉。

控制反轉

在理解控制反轉以前咱們首先要清楚控制是指什麼? 反轉又反轉了什麼?面試

  • 控制: 指的就是咱們上述說的咱們失去的權利(建立對象的建立,或者說控制對象的權利)
  • 反轉: 指的是控制權的轉變。在沒有IOC的容器上咱們程序員想建立誰就建立誰的權利。在IOC容器下程序員就只能委屈巴巴的向IOC容器索取對象。建立對象的權利由程序員到IOC容器手裏了。

IOC解決了什麼問題?

  • 其實就是解決了對象之間的耦合問題。
  • 咱們不須要在經過New關鍵字來建立對象,而是從容器中獲取達到一種鬆耦合的目的。
  • 同時IOC容器也方便管理容器內的全部Bean對象。所謂的Bean的生命週期。

IOC和DI的區別

將到IOC確定會有人想到DI(Dependancy Injection)依賴注入,那這二者有什麼不一樣和相同呢?spring

相同點

  • IOC和DI描述的都是同一件事情(對象的實例化以及維護對象與對象已經的依賴關係)

不一樣點

  • 首先IOC是一種思想,而DI是一種具體的技術實現手段。
  • IOC是站着對象的角度上對象的實例化以及管理從程序員的手裏交給了IOC容器
  • DI是站着容器的角度的上會把對象的依賴的其餘對象注入到容器中,上述案例中的A類依賴B類IOC容器不只僅將A類放到容器中還須要將其依賴的B類也一併加載到IOC容器中。

如何自定義實現一個IOC容器

  • 小夥伴們看到這裏其實對IOC容器已經有了必定的瞭解。那若是在面試的過程當中面試官問你如何實現一個自定義的IOC容器。你能夠講出的具體思路嘛? 能夠先想想在繼續往下看,看看是否是和本身的想發不謀而合。
  • 思路大體以下所示:

15885008070515.jpg

  • 想必你們都有本身的必定的理解,可能作法比較簡單,可是對於理解IOC容器而已其實已經足夠了。若是想更加準確或者深刻了解其底層實現,能夠按照這個思路去看Spring的相關源碼實現,相信你必定會駕輕就熟。

小案例

  • 咱們經過一個小案例來寫咱們的IOC容器
  • 咱們常常購物,咱們能夠把購物簡單的理解成下單和減庫存兩個操做。
  • 有同窗會問爲何要寫這個案例啊,不只爲了咱們可以理解IOC容器也爲了咱們後續的文章將AOP和AOP的經典實現事務控制鋪墊的。

Coding

  • 首先總體的代碼結構很簡單,在這裏也說明一下

15885015935198.jpg

  • Bean的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--跟標籤beans,裏面配置一個又一個的bean子標籤,每個bean子標籤都表明一個類的配置-->
<beans>
    <!--id標識對象,class是類的全限定類名-->
    <bean id="orderDao" class="com.customize.spring.dao.impl.OrderDaoImpl">
    </bean>

    <bean id="stockDao" class="com.customize.spring.dao.impl.StockDaoImpl">
    </bean>

    <bean id="orderService" class="com.customize.spring.service.impl.OrderServiceImpl">
        <!--經過set方法注入-->
        <property name="setOrderDao" ref="orderDao"></property>
        <property name="setStockDao" ref="stockDao"></property>
    </bean>
</beans>
  • 建立Bean對象」工廠「進行建立
  • 主要就是讀取xml,經過set方法傳值。
public class BeanFactory {

    /**
     * 存放對象
     */
    private static Map<String, Object> map = new ConcurrentHashMap<>();

    /**
     * 對外提供的接口
     * @param id
     * @return
     */
    public static  Object getBean(String id) {
        return map.get(id);
    }

    static {
        // 只加載一次就是在BeanFactory初始化的時候去加載類
        // 任務一:讀取解析xml,經過反射技術實例化對象而且存儲待用(map集合)
        System.out.println("開始加載Bean對象");
        // 加載xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//bean");
            for (int i = 0; i < beanList.size(); i++) {
                Element element =  beanList.get(i);
                // 處理每一個bean元素,獲取到該元素的id 和 class 屬性
                String id = element.attributeValue("id");
                String clazz = element.attributeValue("class");
                // 經過反射技術實例化對象
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();
                // 存儲到map中待用
                map.put(id,o);
            }
            // 實例化完成以後維護對象的依賴關係,檢查哪些對象須要傳值進入,根據它的配置,咱們傳入相應的值
            // 有property子元素的bean就有傳值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            // 解析property,獲取父元素
            for (int i = 0; i < propertyList.size(); i++) {
                Element element =  propertyList.get(i);
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");

                // 找到當前須要被處理依賴關係的bean
                Element parent = element.getParent();
                // 調用父元素對象的反射功能
                String parentId = parent.attributeValue("id");
                Object parentObject = map.get(parentId);
                // 遍歷父對象中的全部方法,找到set方法
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    Method method = methods[j];
                    // 該方法就是 set方法
                    if(method.getName().equalsIgnoreCase(name)) {
                        method.invoke(parentObject,map.get(ref));
                    }
                }
                // 把處理以後的parentObject從新放到map中
                map.put(parentId,parentObject);
            }
            System.out.println("加載完畢,Map中的Bean對象個數爲:" + map.size());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 業務代碼
public class OrderServiceImpl implements OrderService {

    private OrderDao orderDao;
    private StockDao stockDao;

    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }

    public void setStockDao(StockDao stockDao) {
        this.stockDao = stockDao;
    }

    @Override
    public void order(Order order) {
//        沒有IOC容器的狀況下
//        OrderDao orderDao = new OrderDaoImpl();
//        // 保存訂單
//        orderDao.save(order);
//
//        //扣除庫存
//        StockDao stockDao = new StockDaoImpl();
//        stockDao.subStock(order.getName());

        // 有IOC容器的基礎上
        orderDao.save(order);

        //扣除庫存
        stockDao.subStock(order.getName());
        System.out.println("下單成功");
    }
}
  • 啓動
    15885022719537.jpg
  • 測試ide

15885022930114.jpg

項目地址

總結

  • 本人水平有限有什麼不正確的地方,歡迎你們留言區討論。
相關文章
相關標籤/搜索