菜鳥學Spring之——Spring入門、理解控制反轉IoC

Spring

如果你對Spring有一些認識之後,你一定就會覺得Spring就是一個容器。

首先了解一下Spring是爲了解決對象與對象之間耦合性的框架

理解耦合與非耦合的區別

public class TestService {
    public void foo() {
        System.out.println("hahahahha");
    }
}
public class Main {
    public static void main(String[] args) {
        TestServie service = new TestService();//簡單的創建對象的過程
        service.foo();
    }
}

上面的Main和TestService之間是強耦合關係。因爲如果將TestService刪除掉,Main中創建對象的過程會報錯,這就是強耦合。要是刪除掉不報錯叫做弱耦合關係。

public class FooService {
    public void foo() {
        System.out.println("我是Foo");
    }
}
-------------------------------------------------------------------------------------
public class TestService {
    private FooServie service = new FooService();
    public void foo() {
        service.foo();
    }
}

這時如果刪除掉FooService方法,Main不會報錯,而是TestService報錯,這時Main和FooService之間就不存在了強耦合關係。但是TestService和FooService、TestService和Main之間還存在強耦合關係。只要強耦合鏈上有一個出了問題程序都不能正常執行。

可以通過工廠模式來解耦合

解耦合之後

public interface IService {
    public void foo();
}
-------------------------------------------------------------------------------------
public class FooService implements IService {
    @Override
    public void foo() {
        System.out.println("我是Foo");
    }
}
-------------------------------------------------------------------------------------
public class TestServiceFactory {
/*

    public static IService create() {
        return new FooService();//TestServiceFactory依然和FooService存在耦合關係
    }

*/
    public static Map<String, Object> map = new HashMap<>();//Spring底層用的是ConcurrentHashMap

//消除TestServiceFactory和FooService存在的耦合關係
    public static Object create(String name) {
        if (map.containsKey(name)) {
            return map.get(name);
        }
        Object o = null;
        try {
            Class aClass = Class.forName(name);
            o = aClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        map.put(name, o);
        return o;
    }
}
-------------------------------------------------------------------------------------
public class Main {
    public static void main(String[] args) {
//        IService service = new FooService();//Main和FooService強耦合
        IService service = (IService) TestServiceFactory.create("com.home.test.service.FooService");
        //這時Main和TestServiceFactory之間存在強耦合,而TestServiceFactory和FooService之間是完全消除了的耦合
        service.foo();
    }
}

這時刪除了FooService類之後,編譯不會報錯,可以正常運行,只是運行後會拋出找不到類的異常。

上面的TestServiceFactory就可以簡單看成我們Spring。也就是說如果我們的框架用Spring搭建,那麼我們自己的項目與Spring是強耦合的,但是和除Spring以外的所有東西都是弱耦合的,因爲其他所有的對象都由Spring來創建了。因此可以理解Spring就是容器

上面的這個過程就叫做控制反轉。

控制反轉 IoC:將原來由動作發起者控制創建對象的行爲改成由中間的工廠來創建的行爲的過程叫做IoC。一個類與工廠之間如果IoC以後,這個時候,動作發起者(Main)已經不能明確的知道自己獲得到的對象是不是自己想要的對象了,因爲這個對象的創建的權利與交給我這個對象的權力全部轉移到了工廠上了。(原來在Main中通過new創建對象,控制權在Main手中,現在控制器交給了工廠。比如拿上面的代碼來說,在create方法中,如果傳入的參數name是com.home.test.service.FooService,表明Main想要一個FooService的對象。但是我工廠偏不聽,增加一個判斷語句,判斷如果你想要FooService,我就非給你創建一個TestService。)

當實現控制反轉後,以後再拿到對象是不是你想要的、或者要不要創建對象、這個對象怎麼使用、要拿哪個對象,這些所有的控制過程全部都轉移到了中間的工廠中了。

TestServiceFactory就像是一個502一樣把Main和IService粘合在了一起。這個粘合的過程有些麻煩。並且要創建誰的對象已經寫死了(在創建的過程給create傳參寫死了),程序運行起來後就無法再更改了,無法達到動態效果。這時就引入了配置文件。

myfactory.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans><!--這些標籤名是隨便定義的-->
    <bean id="testservice" class="com.home.test.service.TestService"></bean>
    <bean id="fooservice" class="com.home.test.service.FooService"></bean>
</beans>
public class TestServiceFactory {
    Map<String, Object> objectFactory = new HashMap<>();//該容器用於放創建的對象
    Map<String, String> objectIDMap = new HashMap<>();//該容器用於放讀取的配置文件的屬性信息

    //該方法主要用於讀取配置文件,並將其信息保存在objectIDMap容器中
    public TestServiceFactory(String path) {
        //在創建TestServiceFactory時用dom4j解析xml文件
        SAXReader reader = new SAXReader();
        File file = new File(path);
        try {
            Document document = reader.read(file);
            Element root = document.getRootElement();//root就是xml文件的根節點
            List<Element> childElements = root.elements();
            for (Element e : childElements) {
                int count = e.attributeCount();//拿到每個標籤有多少個屬性
                String pName = null;
                String pValue = null;
                for (int i = 0; i < count; i++) {
                    Attribute attribute = e.attribute(i);//拿到每個屬性
                    if(attribute.getName().equals("id")) {//如果拿到的屬性是id
                        pName = attribute.getValue();
                    } else if (attribute.getName().equals("class")){
                        pValue = attribute.getValue();
                    }
                }
                objectIDMap.put(pName,pValue);//即 testservice對映com.home.test.service.TestService
                objectFactory.put(pValue,null);
            }
            System.out.println(objectIDMap.get("testservice"));
        } catch (DocumentException e) {
            System.err.println("系統沒有找到對應的配置文件,請檢查配置文件路徑是否正確");
            e.printStackTrace();
        }
    }

    //該方法主要用於通過傳來的參數創建對應的對象
    public Object getObjectById(String name) {
        String className = objectIDMap.get(name);
        Class c = null;
        Object o = null;
        try {
            c = Class.forName(className);
            o = c.newInstance();
        } catch (ClassNotFoundException e) {
            System.err.println("沒有找到對應的類,請檢查配置文件中的映射關係,id=\"" + name + "\" class=\"" + className+"\"");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            System.err.println("該類型沒有共有的默認的構造方法");
            e.printStackTrace();
        } catch (InstantiationException e) {
            System.err.println("該類型的參數集合不對");
            e.printStackTrace();
        }
        return o;
    }

}

測試:

public class Main {
    public static void main(String[] args) {
        TestServiceFactory testServiceFactory = new TestServiceFactory("./src/myfactory.xml");
        IService service = (IService) testServiceFactory.getObjectById("testservice");
        service.foo();
    }
}

上面的過程在每次使用testServiceFactory時他都在重新創建對象。現在如果需要所有對象只被創建一次。

只需要對TestServiceFactory的getObjectById稍加修改即可:(且需要在xml文件的標籤中定義屬性:scope=prototype)

//還有一次修改此處省略,在TestServiceFactory中定義一個scopeMap,在構造函數中,把從xml文件的標籤中讀取到的scope屬性保存進來
public Object getObjectById(String name) {
        String className = objectIDMap.get(name);
        Class c = null;
        Object o = objectFactory.get(name);//如果容器中有則直接拿到,沒有就爲null
        if((scopeMap.get(name) == null || !scopeMap.get(name).equals("prototype")) && o != null) {//scope爲null,或者scope不爲prototype。並且o爲不爲null則爲單例,不需要再創建對象,返回原有的
            return o;
        }
        try {
            c = Class.forName(className);
            o = c.newInstance();
            objectFactory.put(name,o);//將創建的對象保存在容器中
        } catch (ClassNotFoundException e) {
            System.err.println("沒有找到對應的類,請檢查配置文件中的映射關係,id=\"" + name + "\" class=\"" + className+"\"");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            System.err.println("該類型沒有共有的默認的構造方法");
            e.printStackTrace();
        } catch (InstantiationException e) {
            System.err.println("該類型的參數集合不對");
            e.printStackTrace();
        }
        return o;
    }

那麼如果需要指定某些二對象是單例的,有些創建的是新的,這時候該怎麼辦呢?

在xml文件中加定義新的屬性lazy-init(是否懶加載)

    public TestServiceFactory(String path) {
        //在創建TestServiceFactory時用dom4j解析xml文件
        SAXReader reader = new SAXReader();
        File file = new File(path);
        try {
            Document document = reader.read(file);
            Element root = document.getRootElement();//root就是xml文件的根節點
            List<Element> childElements = root.elements();
            for (Element e : childElements) {
                int count = e.attributeCount();//拿到每個標籤有多少個屬性
                String pName = null;
                String pValue = null;
                String scope = null;
                Boolean flag = true;
                for (int i = 0; i < count; i++) {
                    Attribute attribute = e.attribute(i);//拿到每個屬性
                    if(attribute.getName().equals("id")) {//如果拿到的屬性是id
                        pName = attribute.getValue();
                    } else if (attribute.getName().equals("class")){
                        pValue = attribute.getValue();
                    } else if (attribute.getName().equals("scope")) {
                        scope = attribute.getValue();
                    } else if (attribute.getName().equals("lazy-init")) {
                        flag = Boolean.valueOf(attribute.getValue());
                    }
                }
                objectIDMap.put(pName,pValue);//即 testservice對映com.home.test.service.TestService
                Object o = null;
                if ((scope == null || !scope.equals("prototype")) && !flag) {//如果lazy-init設置爲false,到這裏取非,加載文件時創建對象
                    Class c = Class.forName(pValue);
                    o = c.newInstance();
                }//否則不創建對象,o爲null。直到在誰調用getObjectById指定要用它時才被創建
                objectFactory.put(pName,o);
                scopeMap.put(pName,scope);
            }
            System.out.println(objectIDMap.get("testservice"));
        } catch (DocumentException e) {
            System.err.println("系統沒有找到對應的配置文件,請檢查配置文件路徑是否正確");
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

這裏Spring的入門就完成了,通過上面的過程我們瞭解到直接在主類中創建對象的弊端(高耦合),所以通過工廠來創建,但是每次自己寫一個工廠又很麻煩,這時就引入了Spring。(其實上面的過程是我們手寫了一個非常簡單的spring)

Spring的對象保存在beanFactory中

Spring的IoC:將創建並使用對象的過程由原來對象的執行者反向依託於Spring的過程了。Spring替我們創建對象,替我們保存對象(可以把它當成一個容器),我們再需要用對象的時候去Spring中拿就行了。

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