什么是Ioc?
控制反轉(zhuǎn)((Inversion of Control,英文縮寫為IoC)把創(chuàng)建對象的權(quán)利交給框架,是框架的重要特征,并非面向?qū)ο缶幊痰膶S眯g(shù)語。它包括依賴注入(Dependency Injection,簡稱DI和依賴查找(Dependency Lookup)。
說得簡單一點就是我們在寫Java代碼的時候我們的對象不需要自己來創(chuàng)建,而是通過容器來幫助我們生成。用我們生活中的例子來說,打個比方,我們想要一輛汽車,我們不可能去自己造一輛汽車,而是去買一輛。
所有的類都會在spring容器中登記,告訴spring你是個什么東西,你需要什么東西,然后spring會在系統(tǒng)運行到適當(dāng)?shù)臅r候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的創(chuàng)建、銷毀都由 spring來控制,也就是說控制對象生存周期的不再是引用它的對象,而是spring。對于某個具體的對象而言,以前是它控制其他對象,現(xiàn)在是所有對象都被spring控制,所以這叫控制反轉(zhuǎn)。
Ioc容器的初始化

IoC容器初始化過程主要經(jīng)過以下幾個階段:
1.解析階段:Spring會解析Bean的XML配置文件,將XML元素進行抽象,抽象成Resource對象。
2.轉(zhuǎn)換階段:通過Resource對象將配置文件進行抽象后轉(zhuǎn)換成Spring能夠理解的BeanDefinition結(jié)構(gòu)。
3.注冊階段:Spring IoC容器的實現(xiàn),從根源上是beanfactory,但真正可以作為一個可以獨立使用的ioc容器還是DefaultListableBeanFactory。
DefaultListableBeanFactory間接實現(xiàn)了BeanFactory接口,是整個bean加載的核心部分,是Spring注冊及加載bean的默認實現(xiàn),我們可以理解為Spring bean工廠的發(fā)動機。
Spring Bean 定義
被稱作 bean 的對象是構(gòu)成應(yīng)用程序的支柱也是由 Spring IoC 容器管理的。bean 是一個被實例化,組裝,并通過 Spring IoC 容器所管理的對象。這些 bean 是由用容器提供的配置元數(shù)據(jù)創(chuàng)建的。
| 屬性 | 描述 |
|---|---|
| class | 這個屬性是強制性的,并且指定用來創(chuàng)建 bean 的 bean 類。 |
| name | 這個屬性指定唯一的 bean 標識符。在基于 XML 的配置元數(shù)據(jù)中,你可以使用 ID 和/或 name 屬性來指定 bean 標識符。 |
| scope | 這個屬性指定由特定的 bean 定義創(chuàng)建的對象的作用域,它將會在 bean 作用域的章節(jié)中進行討論。 |
| lazy-initialization mode | 延遲初始化的 bean 告訴 IoC 容器在它第一次被請求時,而不是在啟動時去創(chuàng)建一個 bean 實例。 |
| initialization | 在 bean 的所有必需的屬性被容器設(shè)置之后,調(diào)用回調(diào)方法 |
| destruction | 當(dāng)包含該 bean 的容器被銷毀時,使用回調(diào)方法 |
Spring Bean作用域
當(dāng)在 Spring 中定義一個 bean 時,你必須聲明該 bean 的作用域的選項。例如,為了強制 Spring 在每次需要時都產(chǎn)生一個新的 bean 實例,你應(yīng)該聲明 bean 的作用域的屬性為prototype。同理,如果你想讓 Spring 在每次需要時都返回同一個bean實例,你應(yīng)該聲明 bean 的作用域的屬性為singleton。
Spring框架支持以下五個作用域:
| 作用域 | 描述 |
|---|---|
| singleton | 該作用域?qū)?bean 的定義的限制在每一個 Spring IoC 容器中的一個單一實例(默認)。 |
| prototype | 該作用域?qū)我?bean 的定義限制在任意數(shù)量的對象實例。 |
| request | 該作用域?qū)?bean 的定義限制為 HTTP 請求。只在 web-aware Spring ApplicationContext 的上下文中有效。 |
| session | 該作用域?qū)?bean 的定義限制為 HTTP 會話。 只在web-aware Spring ApplicationContext的上下文中有效。 |
| global-session | 該作用域?qū)?bean 的定義限制為全局 HTTP 會話。只在 web-aware Spring ApplicationContext 的上下文中有效。 |
看下面的例子:第一個是singleton,第二個是prototype。
Hello.java
package com.tutorialspoint;
public class Hello {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("Your message is:" + message);
}
}
MainApp.java
package com.tutorialspoint;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Hello object1 = (Hello) context.getBean("bean");
object1.setMessage("這不大傻瓜嗎");
object1.getMessage();
Hello object2 = (Hello) context.getBean("bean");
object2.getMessage();
}
}
Beans.xml
<?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="bean" class="com.tutorialspoint.Hello" scope="singleton">
</bean>
</beans>
輸出的結(jié)果為如圖:

將Beans.xml中的scope="singleton"改為scope="prototype"后,其余代碼不變,輸出結(jié)果可以猜到了,第二個Bean將不會得到實例化,因此輸出的值為空即null。
結(jié)果正確:

Spring Bean的生命周期
Spring的生命周期跟Servlet的生命周期像,都有初始化跟銷毀的方法,參數(shù)分別為:init-mehtod和destroy-method。init-method屬性指定一個方法,在實例化 bean 時,立即調(diào)用該方法。同樣,destroy-method指定一個方法,只有從容器中移除 bean 之后,才能調(diào)用該方法。
Hello.java
package com.tutorialspoint;
public class Hello {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("Your message is:" + message);
}
public void init() {
System.out.println("bean正在被初始化");
}
public void destroy() {
System.out.println("bean正在被銷毀");
}
}
MainApp.java
package com.tutorialspoint;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Hello object1 = (Hello) context.getBean("bean");
object1.setMessage("這不大傻瓜嗎");
object1.getMessage();
//在非web應(yīng)用中關(guān)閉Ioc容器
context.registerShutdownHook();
}
}
Beans.xml
<?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="bean" class="com.tutorialspoint.Hello" init-method="init" destroy-method="destroy">
</bean>
</beans>

這是關(guān)于registryShutdownHook()的詳細解釋。
Spring Bean定義繼承
bean 定義可以包含很多的配置信息,包括構(gòu)造函數(shù)的參數(shù),屬性值,容器的具體信息例如初始化方法,靜態(tài)工廠方法名,等等。子 bean 的定義繼承父定義的配置數(shù)據(jù)。子定義可以根據(jù)需要重寫一些值,或者添加其他值。Spring Bean 定義的繼承與 Java 類的繼承無關(guān),但是繼承的概念是一樣的。你可以定義一個父 bean 的定義作為模板和其他子 bean 就可以從父 bean 中繼承所需的配置。當(dāng)你使用基于 XML 的配置元數(shù)據(jù)時,通過使用父屬性,指定父 bean 作為該屬性的值來表明子 bean 的定義。
看例子:
Hello.java
package com.tutorialspoint;
public class Hello {
private String message1;
private String message2;
public void setMessage1(String message) {
this.message1 = message;
}
public void getMessage1() {
System.out.println("Your message is:" + message1);
}
public void setMessage2(String message) {
this.message2 = message;
}
public void getMessage2() {
System.out.println("Your message is:" + message2);
}
}
HelloWorld.java
package com.tutorialspoint;
public class HelloWorld {
private String message1;
private String message2;
private String message3;
public void setMessage1(String message){
this.message1 = message;
}
public void setMessage2(String message){
this.message2 = message;
}
public void setMessage3(String message){
this.message3 = message;
}
public void getMessage1(){
System.out.println("Your message is: " + message1);
}
public void getMessage2(){
System.out.println("Your message is:" + message2);
}
public void getMessage3(){
System.out.println("Your message is: " + message3);
}
}
MainApp.java
package com.tutorialspoint;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Hello obj1 = (Hello) context.getBean("hello");
obj1.getMessage1();
obj1.getMessage2();
HelloWorld obj2 = (HelloWorld) context.getBean("helloworld");
obj2.getMessage1();
obj2.getMessage2();
obj2.getMessage3();
}
}
Beans.xml
<?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="hello" class="com.tutorialspoint.Hello">
<property name="message1" value="message1" />
<property name="message2" value="message2" />
</bean>
<bean id="helloworld" class="com.tutorialspoint.HelloWorld" parent="hello">
<property name="message1" value="helloworld1" />
<property name="message3" value="helloworld2"/>
</bean>
</beans>
輸出結(jié)果如下:

可以看出,我們在HelloWorld.java中并未定義有getMessage2()的方法,但是在主類中我們調(diào)用了該方法,因此Bean就繼承了主類的該方法,所以子類調(diào)用的是父類的方法,輸出跟父類的結(jié)果一致。