什麼是IoC?何爲DI?
IoC是Sprnig框架實現的一種代替程序員,管理Java類的模式。IoC全稱爲Inversion of Control,即控制反轉。在Spring項目中,將Java類的控制權交給了Spring的IoC容器。類與類之間的泛化關係、依賴關係都能通過Spring的配置來實現,而不需要手動編寫。
DI,全稱爲Dependency Injection,即依賴注入。在Spring通過配置文件配置了依賴關係之後,可以通過多種方式對對象進行注入,比如說Service實現類中的方法需要依賴dao實現類,我們就能通過Spring配置文件寫好注入配置,在使用時,我們只需要定義好dao接口,而不需要去獲取它的實例,由Spring爲我們注入。
能通過Spring的IoC容器和DI做什麼?
1. IoC(控制反轉)
控制反轉,能讓spring容器爲我們創建對象。簡單來說,我們只需要準備好Object object
Spring能幫我們實現餘下的new Object()
部分。在Spring中,每個被放入容器的類被成爲bean,ApplicationContext代表了IoC容器,我們可以通過這個類來讀取配置文件,根據配置文件實例化bean。下面看看如何通過配置實現控制反轉
1.1 配置文件
配置文件一般放在class目錄下,即開發中的src目錄。配置文件的主要結構如下:
<?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-2.5.xsd">
<!--
beans 存放了很多個類
把一個類放入到spring容器中,該類就是bean
-->
<!--
一個bean就是描述一個類
id就是標示符
命名規範:類的第一個字母變成小寫,其他的字母保持不變
class爲類的全名
-->
<bean id="helloWorld" class="com.spring.ioc.HelloWorld"></bean>
</beans>
1.2 ApplicationContext 的使用
ApplicationContext在Spring中代表一個IoC容器,ApplicationContext只是一個接口,在這裏我用ClassPathXmlApplicationContext這個實現類來直接獲取配置文件。其中在容器中對象的創建又有三種方法:根據默認構造方法創建、靜態工廠方法創建、實例工廠方法創建
①默認構造方法創建
springIoc容器默認情況下會根據bean的構造函數來實例化對象。所以在bean的配置中我們可以添加參數,參數可以是變量也可以是另一個bean的引用,如果是bean的引用,則使用ref=”bean_id”來關聯。
配置方式applicationContext.xml
<bean id="helloWorld" class="com.spring.ioc.HelloWorld"></bean>
HelloWorld.java
package com.spring.ioc;
public class HelloWorld {
public HelloWorld(){
System.out.println("create a HelloWorld!");
}
public void hello(String name){
System.out.println("Hello," + name);
}
}
IoCTest.java
package com.spring.ioc;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class IoCTest {
@Test
public void testIoC(){
//讀取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//通過getBean方法得到
HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
helloWorld.hello("Cindy");
}
}
②靜態工廠方法創建對象
可以在配置bean的時候可以配置使用靜態工廠來實例化對象。Spring容器會在內部調用靜態工廠的實例化方法來實例化對象。
配置方式:applicationContext.xml
<bean id="helloWorld" class="com.spring.ioc.HelloWorldFactory"
factory-method="getInstance">
</bean>
靜態工廠HelloWorldFactory.java
package com.spring.ioc;
public class HelloWorldFactory {
public static HelloWorld getInstance(){
return new HelloWorld();
}
}
③實例工廠創建對象
使用實例工廠方法實例化對象,在定義bean的時候使用factory-bean屬性和factory-method屬性,來指定實例工廠的實例方法。與靜態工廠方法不同,這裏是先實例化了一個工廠類,然後調用了實例化工廠方法,而靜態工廠方法不實例化工廠類,直接調用其靜態實例化方法。
配置方式:applicationContext.xml
<bean id="helloWorldFactory" class="com.spring.ioc.HelloWorldFactory2"></bean>
<bean id="helloWorld" factory-method="getInstance" factory-bean="helloWorldFactory"></bean>
實例工廠HelloWorldFactory2.java
package com.spring.ioc;
public class HelloWorldFactory2 {
public HelloWorldFactory2(){
System.out.println("create a HelloWorldFactory");
}
public HelloWorld getInstance(){
return new HelloWorld();
}
}
1.3 什麼時候實例化對象?
①加載Spring容器,加載過程中,調用bean的構造函數爲bean創建對象。
②將bean的lazy-init屬性,設置爲true(默認爲false),設置延遲加載,spring容器加載時不實例化,在getBean()的時候才實例化。
一般情況下使用第一種方式,因爲在啓動過程中就能檢測出spring配置文件中包含的錯誤,更加安全。第二種在啓動過程中發現不了配置文件錯誤。如果要加載的bean實在太消耗性能,再考慮使用第二種方法。
1.4 bean的單例模式和多例模式
在配置bean的過程中,可以爲其指定是否使用單例模式實例化對象,默認爲單例模式,即只實例化一次,多次使用。設置的方法爲:
① 多例模式:scope=prototype
<bean id="helloWorld" class="com.spring.ioc.HelloWorld" scope="prototype"></bean>
②單例模式(defalut): scope=singleton
<bean id="helloWorld" class="com.spring.ioc.HelloWorld" scope="singleton"></bean>
1.5 bean的init方法,destory方法
在配置bean的時候,可以爲其指定init()方法和destory()方法,分別在實例化和銷燬的時候執行。其中在容器關閉時,bean銷燬。
配置了之後整個流程可表示爲:構造方法(創建對象)->init方法->其他方法->destory方法
*當scope=prototype的時候,spring容器不會執行destory方法
配製方法 applicationContext.xml :
<bean id="helloWorld" class="com.spring.ioc.HelloWorld" init-method="init" destroy-method="destroy"></bean>
HelloWorld.java
package com.spring.ioc;
public class HelloWorld {
public HelloWorld(){
System.out.println("create a HelloWorld!");
}
public void hello(String name){
System.out.println("Hello," + name);
}
public void init(){
System.out.println("init HelloWorld");
}
public void destroy(){
System.out.println("destroy HelloWorld");
}
}
1.6 bean繼承關係
在spring容器中使用parent屬性指定父類,實現繼承。例子:
package com.spring.xml.extend;
public class Animal {
public void walk(){
System.out.println("Animal walking");
}
}
package com.spring.xml.extend;
public class Dog extends Animal {
}
配置:
<bean class="com.spring.xml.extend.Animal" id="animal"></bean>
<bean class="com.spring.xml.extend.Dog" id="dog" parent="animal"></bean>
2. DI(依賴注入)
2.1 setter方式注入
配置bean的時候,我們可以在配置文件中指定實例化時,要注入的屬性值。這些值可以是基本數據類型、引用類型、集合類型、或者Propertie,只要在bean的類定義中提供setter方法就能通過配置注入,配置的方法如下:
applicationContext.xml
<bean id="person" class="com.spring.di.Person">
<!--
property描述的就是bean中的屬性
name屬性就是描述屬性的名稱
value就是值 如果是基本屬性(String),就用value賦值
ref 如果是引用類型,用ref賦值
-->
<property name="pid" value="1"></property>
<property name="name" value="Cindy"></property>
<!-- 注入bean -->
<property name="student" ref="student"></property>
<property name="lists">
<list>
<value>list1</value>
<value>list2</value>
<!--
list中存放一個student對象
-->
<ref bean="student"/>
</list>
</property>
<property name="objects">
<list>
<value>obj1</value>
<ref bean="student"/>
</list>
</property>
<property name="sets">
<set>
<value>set1</value>
<ref bean="student"/>
</set>
</property>
<property name="map">
<map>
<entry key="m1">
<value>m1</value>
</entry>
<entry key="m2">
<ref bean="student"/>
</entry>
</map>
</property>
<property name="properties">
<props>
<prop key="p1">p1</prop>
<prop key="p2">p2</prop>
</props>
</property>
</bean>
<bean id="student" class="com.spring.di.Student"
scope="prototype"></bean>
Student.java
package com.spring.di;
public class Student {
public Student(){
System.out.println("student");
}
public void say(){
System.out.println("student");
}
}
Person.java
public class Person {
private Long pid;
private String name;
private Student student;
private List lists;
private Set sets;
private Map map;
private Object[] objects;
private Properties properties;
public Person(){
System.out.println("person");
}
//...
//省略setter方法
}
2.2 Constructor 注入
除了調用setter方法注入,還能通過類的構造器來注入參數,同樣是從配置文件中對bean進行配置。首先在類中定義好構造器方法,然後在配置文件中設置參數。
配置方法:
applicationContext.xml
<bean id="person1" class="com.spring.di.Person1">
<constructor-arg value="Cindy" index="0"></constructor-arg>
<constructor-arg ref="student" index="1"></constructor-arg>
</bean>
Person1.java
package com.spring.di;
public class Person1 {
private String name;
private Student student;
public Person1(String name, Student student) {
this.name = name;
this.student = student;
}
}
IoC 和 DI 帶來的好處(優勢)
讓客戶端實現完全面向接口編程,在編寫客戶端的時候,不用關心實現類爲什麼具體類型,只用接口類型來完成工作,接口需要具體爲何種類型的實例,由配置文件決定。舉個例子,在一個struts2的aciton中,用接口定義了一個service,而這個service通過spring注入,那麼開發action的人員就不需要管service具體是如何實現的,把編寫service的工作分離出去,方便團隊開發,避免編寫過程中的衝突。
Spring是如何實現IoC和DI的
瞭解Spring框架底層是如何實現控制反轉和依賴注入,需要深入研究源碼。因爲我還沒有深入研究過,只知道大概是使用了Java的反射機制,來操作類。有待自己深入研究。