Spring IOC的原型
spring框架的基礎(chǔ)核心和起點(diǎn)毫無疑問就是IOC,IOC作為spring容器提供的核心技術(shù),成功完成了依賴的反轉(zhuǎn):從主類的對依賴的主動管理反轉(zhuǎn)為了spring容器對依賴的全局控制。
這樣做的好處是什么呢?
當(dāng)然就是所謂的“解耦”了,可以使得程序的各模塊之間的關(guān)系更為獨(dú)立,只需要spring控制這些模塊之間的依賴關(guān)系并在容器啟動和初始化的過程中將依據(jù)這些依賴關(guān)系創(chuàng)建、管理和維護(hù)這些模塊就好,如果需要改變模塊間的依賴關(guān)系的話,甚至都不需要改變程序代碼,只需要將更改的依賴關(guān)系進(jìn)行修改即可,spring會在再次啟動和初始化容器的過程中使得這些新的依賴關(guān)系重新建立符合新需求的模塊,在這個過程中,需要注意的是代碼本身不需要體現(xiàn)對于模塊具體依賴情形的聲明而只需要定義其所需模塊的接口,所以這是一種典型的面向接口思想,同時最好將依賴關(guān)系以配置文件或者注解的形式表述出來,相關(guān)的spring處理類會根據(jù)這些外部的配置文件組裝模塊,或者掃描注解調(diào)用內(nèi)部的注解處理器組裝模塊,以此完成IOC的過程。
IOC的目的是稱為DI的依賴注入,通過IOC技術(shù),最終容器將幫助我們完成模塊間的依賴注入。
另外,最終的一點(diǎn)是,在spring IOC的過程中,我們必須始終清楚以上這條主線,即時語法和類的結(jié)構(gòu)再復(fù)雜,但是其作用和目的都是一樣的:就是通過依賴描述的配置文件這一裝配“圖紙”去完成模塊的“組裝”,復(fù)雜的語法只是完成這一目的的手段罷了。
所謂的IOC原型,為了展示最簡單的IOC原理圖,我們不妨做一個完全簡單的原型來說明這個過程:
首先是我們定義的幾個模塊,包括主模塊和兩個接口定義的依賴模塊:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
class MainModule{ private DependModuleA moduleA; private DependModuleB moduleB; public DependModuleA getModuleA() { return moduleA; } public void setModuleA(DependModuleA moduleA) { this .moduleA = moduleA; } public DependModuleB getModuleB() { return moduleB; } public void setModuleB(DependModuleB moduleB) { this .moduleB = moduleB; } } interface DependModuleA{ public void funcFromModuleA(); } interface DependModuleB{ public void funcFromModuleB(); } class DependModuleAImpl implements DependModuleA{ @Override public void funcFromModuleA() { System.out.println( "This is func from Module A" ); } } class DependModuleBImpl implements DependModuleB{ @Override public void funcFromModuleB() { System.out.println( "This is func from Module B" ); } } |
如果我們不采用IOC,而是依靠主模塊本身去控制其依賴模塊的創(chuàng)建,那么會是這樣的:
1
2
3
4
5
6
7
8
9
|
public class SimpleIOCDemo { public static void main(String[] args) throws ClassNotFoundException { MainModule mainModule = new MainModule(); mainModule.setModuleA( new DependModuleAImpl()); mainModule.setModuleB( new DependModuleBImpl()); mainModule.getModuleA().funcFromModuleA(); mainModule.getModuleB().funcFromModuleB(); } } |
這是我們經(jīng)過簡化定義的IOC容器原型,容器在啟動后初始化的時候會讀取用戶寫入的配置文件,這里我們以簡單的properties配置文件為例,只有當(dāng)用戶調(diào)取getBean方法的時候才會真正地按照配置文件組裝加載相應(yīng)的bean,在我們定義的容器原型內(nèi)部維護(hù)著一個用于保存裝配好的bean 的map,如果在其中有滿足要求的bean的話就不需要再新建了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
class SimpleIOCContainer{ private Properties properties = new Properties(); private Map<String, Object> moduleMap = new HashMap<>(); { try { properties.load( new FileInputStream( new File( "SimpleIOC.properties" ))); } catch (Exception e) { e.printStackTrace(); } } public Object getBean(String moduleName) throws ClassNotFoundException { Object instanceObj; if (moduleMap.get(moduleName)!= null ){ System.out.println( "return old bean" ); return moduleMap.get(moduleName); } System.out.println( "create new bean" ); String fullClassName = properties.getProperty(moduleName); if (fullClassName == null ) throw new ClassNotFoundException(); else { Class<? extends Object> clazz = Class.forName(fullClassName); try { instanceObj = clazz.newInstance(); instanceObj = buildAttachedModules(moduleName,instanceObj); moduleMap.put(moduleName, instanceObj); return instanceObj; } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return null ; } private Object buildAttachedModules(String modulename , Object instanceObj) { Set<String> propertiesKeys = properties.stringPropertyNames(); Field[] fields = instanceObj.getClass().getDeclaredFields(); for (String key : propertiesKeys) { if (key.contains(modulename)&&!key.equals(modulename)){ try { Class<? extends Object> clazz = Class.forName(properties.getProperty(properties.getProperty(key))); for (Field field : fields) { if (field.getType().isAssignableFrom(clazz)) field.set(instanceObj, clazz.newInstance()); } } catch (Exception e) { e.printStackTrace(); } } } return instanceObj; } } |
這是我們使用properties配置文件寫成的依賴關(guān)系配置文件,這個配置文件是我們裝配模塊的“圖紙”,這里的語法個是完全是我們定義的,在真實(shí)的spring IOC容器中,為了表達(dá)更為復(fù)雜的依賴邏輯,會使用更為發(fā)達(dá)的xml格式配置文件或者更新的注解配置,依靠注解處理器來完成圖紙的解析:
1
2
3
4
5
|
mainModule=com.rocking.demo.MainModule mainModule.moduleA=moduleA mainModule.moduleB=moduleB moduleA=com.rocking.demo.DependModuleAImpl moduleB=com.rocking.demo.DependModuleBImpl |
這是測試代碼,可以看到的是我們可以完整的通過我們定義的IOC容器獲取到符合要求的模塊,同時也可以發(fā)現(xiàn)我們定義的容器可以為我們維護(hù)這些bean,當(dāng)有bean已經(jīng)組裝創(chuàng)建出來之后就不需要再創(chuàng)建了。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class SimpleIOCDemo { public static void main(String[] args) throws ClassNotFoundException { SimpleIOCContainer container = new SimpleIOCContainer(); DependModuleA moduleA = (DependModuleA) container.getBean( "moduleA" ); moduleA.funcFromModuleA(); DependModuleB moduleB = (DependModuleB) container.getBean( "moduleB" ); moduleB.funcFromModuleB(); MainModule mainModule = (MainModule) container.getBean( "mainModule" ); mainModule.getModuleA().funcFromModuleA(); mainModule.getModuleB().funcFromModuleB(); container.getBean( "mainModule" ); } } |
這就是我依據(jù)IOC的基本思想創(chuàng)建的IOC容器原型,spring IOC雖然語法復(fù)雜,但是說到底完成的任務(wù)在核心上都是一樣的,所謂的“萬變不離其宗”。
Spring IOC 的具體過程
上回展示了IOC的大致實(shí)現(xiàn)的原型,那么在Spring框架中具體是怎么實(shí)現(xiàn)這個容器根據(jù)metadata元信息配置加載POJO的過程的呢?在整個Spring IOC容器的工作過程中有很多地方是設(shè)計(jì)地相當(dāng)靈活的,供給使用者很多空間去完成自己的任務(wù),而不是一味地只是完成容器的機(jī)械過程。
這是整個IOC容器工作過程的過程圖:
1、容器啟動階段
(1)加載配置文件信息
(2)解析配置文件信息
(3)裝配BeanDefinition
(4)后處理
首先配置文件或者注解等元信息和JavaBean的類信息被加載到IOC容器中,容器讀取到xml格式的配置文件,這個配置文件是使用者聲明的依賴關(guān)系和裝配中需要特別關(guān)注的地方,是裝配Bean的早期“外部圖紙”,容器中的解析引擎可以把我們寫入的文本形式的字符元信息解析成容器內(nèi)部可以識別的BeanDefinition,可以把BeanDefinition理解成為類似反射機(jī)制的類結(jié)構(gòu),這個通過對JavaBean和配置文件進(jìn)行分析得到的BeanDefinition獲取了組裝一個符合要求的JavaBean的基本結(jié)構(gòu),如果需要除了BeanDefinition之后還要對這個BeanDefinition再做修改的話則執(zhí)行這個后處理,后處理一般是通過Spring框架內(nèi)的BeanFactoryPostProcessor處理的。
我們?nèi)匀皇褂蒙洗问褂眠^的例子來說明這個BeanDefinition的運(yùn)作原理:有三個bean,主模塊MainModule和依賴模塊DependModuleA,DependModuleB,前者依賴后面兩個模塊構(gòu)成,在配置文件里我們一般會這么進(jìn)行依賴的聲明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<? xml version = "1.0" encoding = "UTF-8" ?> < beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> < bean id = "mainModule" class = "com.rocking.demo.MainModule" > < property name = "moduleA" > < ref bean = "moduleA" /> </ property > < property name = "moduleB" > < ref bean = "moduleB" /> </ property > </ bean > < bean id = "moduleA" class = "com.rocking.demo.DependModuleAImpl" ></ bean > < bean id = "moduleB" class = "com.rocking.demo.DependModuleBImpl" ></ bean > </ beans > |
這是我們的程序演示一個標(biāo)準(zhǔn)的BeanFactory容器(Spring IOC容器的實(shí)現(xiàn)之一)對上面配置文件的裝配:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
class MainModule { private DependModuleA moduleA; private DependModuleB moduleB; public DependModuleA getModuleA() { return moduleA; } public void setModuleA(DependModuleA moduleA) { this .moduleA = moduleA; } public DependModuleB getModuleB() { return moduleB; } public void setModuleB(DependModuleB moduleB) { this .moduleB = moduleB; } } interface DependModuleA { public void funcFromModuleA(); } interface DependModuleB { public void funcFromModuleB(); } class DependModuleAImpl implements DependModuleA { @Override public void funcFromModuleA() { System.out.println( "This is func from Module A" ); } } class DependModuleBImpl implements DependModuleB { @Override public void funcFromModuleB() { System.out.println( "This is func from Module B" ); } } public class SimpleIOCDemo { public static void main(String[] args) throws ClassNotFoundException { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions( "Beans.xml" ); MainModule mainModule = (MainModule) beanFactory.getBean( "mainModule" ); mainModule.getModuleA().funcFromModuleA(); mainModule.getModuleB().funcFromModuleB(); } } |
這里我們的配置文件和JavaBean被加載讀取并被解析,這里的BeanDefinition生成使用過程掩藏在其中,這是實(shí)際上在IOC內(nèi)部發(fā)生的大致過程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class SimpleIOCDemo { public static void main(String[] args) throws ClassNotFoundException { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); AbstractBeanDefinition mainModule = new RootBeanDefinition(MainModule. class ); AbstractBeanDefinition moduleA = new RootBeanDefinition(DependModuleAImpl. class ); AbstractBeanDefinition moduleB = new RootBeanDefinition(DependModuleBImpl. class ); beanFactory.registerBeanDefinition( "mainModule" , mainModule); beanFactory.registerBeanDefinition( "moduleA" , moduleA); beanFactory.registerBeanDefinition( "moduleB" , moduleB); MutablePropertyValues propertyValues = new MutablePropertyValues(); propertyValues.add( "moduleA" , moduleA); propertyValues.add( "moduleB" , moduleB); mainModule.setPropertyValues(propertyValues); MainModule module = (MainModule) beanFactory.getBean( "mainModule" ); module.getModuleA().funcFromModuleA(); module.getModuleB().funcFromModuleB(); } } |
對xml的元信息進(jìn)行加載讀取后,IOC解析引擎會將其中提到的模塊依據(jù)其真實(shí)類型創(chuàng)建成BeanDefinition,這個BeanDefinition可以看成是一種反射或者代理的過程,目的是為了讓IOC容器清楚以后要創(chuàng)建的實(shí)例對象的bean結(jié)構(gòu),然后將這些bean結(jié)構(gòu)注冊到BeanFactory中去,之后將主模塊的依賴以setter注入的形式加入到主模塊的屬性中去,(這一點(diǎn)要看主模塊提供的是setter方法還是初始化方法),這個過程結(jié)束后注冊完所有“圖紙”上規(guī)定的bean的Definition后,BeanFactory就已經(jīng)成型。之后只要調(diào)用getBean方法即可將符合要求的bean生產(chǎn)出來,這是下一階段的過程,我們之后再說。
在將BeanDefinition這一“圖紙”上的信息注冊到BeanFactory完畢后,我們?nèi)匀豢梢詫σ呀?jīng)注冊完的BeanDefinition進(jìn)行改動的操作,這就是我們前面提到的Spring為使用者設(shè)計(jì)的靈活的地方之一,不是說所有的過程不可控,而是在很多地方留了很多使用者可以發(fā)揮的余地。具體的辦法是使用BeanFactory處理器BeanFactoryPostProcessor來介入對BeanFactory的處理以進(jìn)一步改寫我們需要修改的BeanDefinition部分。這個過程對應(yīng)流程里的“后處理”過程。
以常見的處理器之一:屬性占位符配置處理器為例,就是在已經(jīng)構(gòu)建完成已注冊完畢的BeanFactory之后再對它處理,以使得BeanDefinition相應(yīng)屬性里的內(nèi)容修改為配置處理器指定配置文件里的信息:
1
2
3
4
5
6
|
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions( new ClassPathResource( "Beans.xml" )); PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer(); configurer.setLocation( new ClassPathResource( "about.properties" )); configurer.postProcessBeanFactory( beanFactory); |
BeanFactoryPostProcessor將對BeanFactory處理,處理的結(jié)果就是把BeanDefinition中定義的某些屬性改成BeanFactoryPostProcessor定義位置處的某些信息。
2、Bean 實(shí)例化階段
有了經(jīng)過處理的BeanDefinition的“內(nèi)部圖紙”的指導(dǎo)下,容器可以進(jìn)一步把BeanDefifnition通過反射或CGLIB動態(tài)字節(jié)碼生產(chǎn)的方式化為存在于內(nèi)存中的活化實(shí)例對象,再將BeanDefinition規(guī)定的依賴對象通過setter注入或者初始化注入的方式裝配進(jìn)新創(chuàng)建的實(shí)例對象中,這里是實(shí)實(shí)在在地將依賴對象的引用賦給需要依賴的對象屬性中。
但是這里需要注意的是創(chuàng)建的實(shí)例不僅僅是一個簡單的bean定義的實(shí)例,而是一個經(jīng)過Spring包裝的BeanWrapper實(shí)例,這里為什么要采用BeanWrapper的方式來包裝bean呢?是因?yàn)锽eanWrapper提供了統(tǒng)一訪問bean屬性的接口,在創(chuàng)建完了基本的bean的框架后要對其中的屬性進(jìn)行設(shè)置,每個bean的setter方法都不一樣,所以如果直接用反射設(shè)置的話會非常復(fù)雜,所以spring提供這種包裝來簡化屬性設(shè)置:
1
2
3
4
5
6
|
BeanWrapper beanWrapper = new BeanWrapperImpl(Class.forName( "com.rocking.demo.MainModule" )); beanWrapper.setPropertyValue( "moduleA" , Class.forName( "com.rocking.demo.DepModuleAImpl" ).newInstance()); beanWrapper.setPropertyValue( "moduleB" , Class.forName( "com.rocking.demo.DepModuleBImpl" ).newInstance()); MainModule mainModule= (MainModule) beanWrapper.getWrappedInstance(); mainModule.getModuleA().funcFromA(); mainModule.getModuleB().funcFromB(); |
以上的過程展示了在Spring內(nèi)部,通過獲取類的反射容器了解將來包裝的實(shí)例bean的結(jié)構(gòu)并作出包裝,使用統(tǒng)一的屬性設(shè)置方法setPropertyValue來對這個包裝的實(shí)例設(shè)置屬性,最后得到的bean實(shí)例通過getWrappedInstance拿到,可以發(fā)現(xiàn)已經(jīng)成功將其屬性賦值。
這個時候的bean實(shí)例其實(shí)已經(jīng)完全可以使用了,但是Spring同樣在實(shí)例化階段也為我們準(zhǔn)備了靈活的策略以完成使用者對這個階段的介入,和容器啟動階段的BeanFactoryPostProcessor控制BeanDefinition類似,在實(shí)例化階段,Spring提供了BeanPostProcessor處理器來對已經(jīng)裝配好的實(shí)例進(jìn)行操作,以完成可能需要的改動:、
這里舉個例子來說明,定義一個BeanPostProcessor的實(shí)現(xiàn)類,實(shí)現(xiàn)其中的方法postProcessAfterInitialization和postProcessBeforeInitialization來定義對在bean實(shí)例裝配之后和之前分別進(jìn)行的操作,在BeanFactory添加了這個處理器后就會在每次調(diào)用getBean方法裝配實(shí)例的時候,都會傳入根據(jù)“圖紙”裝配出的bean實(shí)例(包括裝配過程中創(chuàng)建的依賴實(shí)例bean)調(diào)用這兩個方法,這些方法可以對這些bean實(shí)例實(shí)施修改。
下面是一個這樣的例子(MainModule及其依賴關(guān)系和本文之前的例子相同):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
class ModuleC { private String x; public String getX() { return x; } public void setX(String x) { this .x = x; } } class ModulePostProcessor implements BeanPostProcessor{ @Override public Object postProcessAfterInitialization(Object object, String string) throws BeansException { System.out.println(string); if (object instanceof ModuleC){ System.out.println(string); ((ModuleC)object).setX( "after" ); } return object; } @Override public Object postProcessBeforeInitialization(Object object, String string) throws BeansException { if (object instanceof ModuleC){ ((ModuleC)object).setX( "before" ); } return object; } } public class VerySimpleIOCKernal { public static void main(String[] args) throws ClassNotFoundException, BeansException, InstantiationException, IllegalAccessException { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions( new ClassPathResource( "Beans.xml" )); ModulePostProcessor postProcessor = new ModulePostProcessor(); beanFactory.addBeanPostProcessor(postProcessor); MainModule module = (MainModule) beanFactory.getBean( "mainModule" ); ModuleC moduleC = (ModuleC) beanFactory.getBean( "moduleC" ); System.out.println(moduleC.getX()); } } |
這是bean的依賴關(guān)系配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<? xml version = "1.0" encoding = "UTF-8" ?> < beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > < bean id = "mainModule" class = "com.rocking.demo.MainModule" > < property name = "moduleA" > < ref bean = "moduleA" /> </ property > < property name = "moduleB" > < ref bean = "moduleB" /> </ property > </ bean > < bean id = "moduleA" class = "com.rocking.demo.DepModuleAImpl" > < property name = "infoA" > < value >${moduleA.infoA}</ value > </ property > </ bean > < bean id = "moduleB" class = "com.rocking.demo.DepModuleBImpl" > < property name = "infoB" > < value >info of moduleB</ value > </ property > </ bean > < bean id = "moduleC" class = "com.rocking.demo.ModuleC" > </ bean > </ beans > |
從最終的結(jié)果我們可以看出,每次調(diào)用getBean方法得到的bean實(shí)例(包括因依賴關(guān)系生成的)都將被BeanPostProcessor獲取進(jìn)行前置和后置處理。
除了類似上面的BeanPostProcessor的辦法對裝配好的bean再做處理外,Spring還可以通過配置init-method和destroy-method來對bean的初始化和銷毀過程設(shè)置回調(diào)函數(shù),這些回調(diào)函數(shù)也還可以靈活地提供更改bean實(shí)例的機(jī)會。
整個Spring IOC的過程其實(shí)總體來說和我們自己寫的IOC原型在本質(zhì)上是一樣的,只不過通過復(fù)雜的設(shè)計(jì)使得IOC的過程能夠更靈活有效地提供給使用者更多的發(fā)揮空間,除此之外,Spring的IOC也在安全性、容器的穩(wěn)定性、metadata到bean轉(zhuǎn)換的高效性上做到了精美的設(shè)計(jì),使得IOC這一Spring容器的基礎(chǔ)得以穩(wěn)固。