【spring】spring_IOC和DI
Spring概述
Spring是分層的 Java SE/EE應用full-stack輕量級開源框架,以IOC(Inverse Of Control:反轉控制)和 AOP(Aspect Oriented Programming:面向切面程式設計)為核心。
提供了表現層SpringMVC和持久層Spring JDBCTemplate以及業務層事務管理等眾多的企業級應用技術,簡單的說就是簡化java開發。還能整合開源世界眾多著名的第三方框架和類庫,逐漸成為使用最多的Java EE 企業應用開源框架。
這裡需要理解幾個基本概念
控制反轉 :不是什麼技術,它是一種降低物件耦合關係的一種設計思想。在Java開發中,Ioc意味著將物件交給容器控制
其中最常見的方式叫做 依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查詢”(Dependency Lookup)
依賴注入 :指容器負責建立和維護物件之間的依賴關係,而不是通過物件本身負責自己的建立和解決自己的依賴。在當前類需要用到其他類的物件,由Spring為我們提供(就是省略
new
),我們只需要在配置中說明。面向切面程式設計 : 自己的理解:提取相同的程式碼,例如後臺所有內容都需要登入為前提,呢麼這個判斷是否登入的方法就是面向切面程式設計
- 作用:在程式執行期間,在不修改原始碼的情況下對方法進行功能增強。
ioc和依賴注入的由來
我們知道在面向物件設計的軟體系統中,它的底層都是由N個物件構成的,各個物件之間通過相互合作,最終實現系統地業務邏輯,例如一個懷錶,它擁有多個獨立的齒輪,這些齒輪相互齧合在一起,協同工作,共同完成某項任務。我們可以看到,在這樣的齒輪組中,如果有一個齒輪出了問題,就可能會影響到整個齒輪組的正常運轉。這就是耦合關係非常緊密的關係,所以為了解決,軟體專家Michael Mattson 1996年提出了IOC理論,用來實現物件之間的“解耦”,目前這個理論已經被成功地應用到實踐當中。
IOC是Inversion of Control的縮寫,多數書籍翻譯成“控制反轉”。IOC理論提出的觀點大體是這樣的:藉助於“第三方”實現具有依賴關係的物件之間的解耦。如下圖:
大家看到了吧,由於引進了中間位置的“第三方”,也就是IOC容器,使得A、B、C、D這4個物件沒有了耦合關係,齒輪之間的傳動全部依靠“第三方”了,全部物件的控制權全部上繳給“第三方”IOC容器,所以,IOC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有物件粘合在一起發揮作用,如果沒有這個“粘合劑”,物件與物件之間會彼此失去聯絡,這就是有人把IOC容器比喻成“粘合劑”的由來。
我們再來做個試驗:把上圖中間的IOC容器拿掉,然後再來看看這套系統:
我們現在看到的畫面,就是我們要實現整個系統所需要完成的全部內容。這時候,A、B、C、D這4個物件之間已經沒有了耦合關係,彼此毫無聯絡,這樣的話,當你在實現A的時候,根本無須再去考慮B、C和D了,物件之間的依賴關係已經降低到了最低程度。所以,如果真能實現IOC容器,對於系統開發而言,這將是一件多麼美好的事情,參與開發的每一成員只要實現自己的類就可以了,跟別人沒有任何關係!
我們再來看看,控制反轉(IOC)到底為什麼要起這麼個名字?我們來對比一下:
-
軟體系統在沒有引入IOC容器之前,如圖1所示,物件A依賴於物件B,那麼物件A在初始化或者執行到某一點的時候,自己必須主動去建立物件B或者使用已經建立的物件B。無論是建立還是使用物件B,控制權都在自己手上。
-
軟體系統在引入IOC容器之後,這種情形就完全改變了,如圖3所示,由於IOC容器的加入,物件A與物件B之間失去了直接聯絡,所以,當物件A執行到需要物件B的時候,IOC容器會主動建立一個物件B注入到物件A需要的地方。
通過前後的對比,我們不難看出來:物件A獲得依賴物件B的過程,由主動行為變為了被動行為,控制權顛倒過來了,這就是“控制反轉”這個名稱的由來。
2004年,Martin Fowler探討了同一個問題,既然IOC是控制反轉,那麼到底是“哪些方面的控制被反轉了呢?”,經過詳細地分析和論證後,他得出了答案:“獲得依賴物件的過程被反轉了”。控制被反轉之後,獲得依賴物件的過程由自身管理變為了由IOC容器主動注入。於是,他給“控制反轉”取了一個更合適的名字叫做“依賴注入(Dependency Injection)”。他的這個答案,實際上給出了實現IOC的方法:注入。所謂依賴注入,就是由IOC容器在執行期間,動態地將某種依賴關係注入到物件之中。
所以,依賴注入(DI)和控制反轉(IOC)是從不同的角度的描述的同一件事情,就是指通過引入IOC容器,利用依賴關係注入的方式,實現物件之間的解耦。
Spring的優勢
1) 方便解耦,簡化開發
通過Spring提供的IOC容器,可以將物件間的依賴關係交由Spring進行控制,避免硬編碼所造成的過度耦合。
使用者也不必再為單例模式類、屬性檔案解析等這些很底層的需求編寫程式碼,可以更專注於上層的應用。
2) AOP 程式設計的支援
通過Spring的AOP功能,方便進行面向切面程式設計,許多不容易用傳統OOP實現的功能可以通過AOP輕鬆實現。
3) 宣告式事務的支援
可以將我們從單調煩悶的事務管理程式碼中解脫出來,通過宣告式方式靈活的進行事務管理,提高開發效率和質量
4) 方便程式的測試
可以用非容器依賴的程式設計方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可做的事情。
Spring的體系結構
Spring配置檔案 => IoC容器
Bean標籤基本配置
作用:通過配置將物件的建立交給Spring容器進行管理。
預設情況下它呼叫的是類中的無參建構函式,如果沒有無參建構函式則不能建立成功。
相關屬性
- id:Bean例項在Spring容器中的唯一標識;
- class:Bean的全限定名稱。
Bean標籤範圍配置
scope,指物件的作用範圍,取值如下:
取值範圍 | 說明 |
---|---|
singleton | 預設值,單例的 |
prototype | 多例的 |
request | WEB專案中,Spring建立一個Bean的物件,將物件存入到request域中 |
session | WEB專案中,Spring建立一個Bean的物件,將物件存入到session域中 |
global session | WEB專案中,應用在Portlet環境,如果沒有Portlet環境那麼globalSession相當於session |
當scope的取值為singleton時
- Bean的例項化個數:1個
- Bean的例項化時機:當Spring核心檔案被載入時,例項化配置的Bean例項
- Bean的生命週期:
- 物件建立:當應用載入,建立容器時,物件就被建立了;
- 物件執行:只要容器在,物件一直活著;
- 物件銷燬:當應用解除安裝,銷燬容器時,物件就被銷燬了。
當scope的取值為prototype時
- Bean的例項化個數:多個
- Bean的例項化時機:當呼叫getBean()方法時例項化Bean
- Bean的生命週期:
- 物件建立:當使用物件時,建立新的物件例項;
- 物件執行:只要物件在使用中,就一直活著;
- 物件銷燬:當物件長時間不用時,被 Java 的垃圾回收器回收了。
bean生命週期配置
init-method:指定類中的初始化方法名稱
destroy-method:指定類中銷燬方法名稱
bean例項化三種方式
使用無參構造方法例項化
根據預設無參構造方法來建立類物件,如果bean中沒有預設無參建構函式,將會建立失敗。
<bean id="userDao" class="com.qfedu.dao.impl.UserDaoImpl">
工廠靜態方法例項化
建立靜態工廠
public class StaticBeanFactory {
public static UserDao getUserDaoImpl() {
return new UserDaoImpl();
}
}
在Spring配置檔案中配置
<!-- 靜態工廠初始化 -->
<bean id="userDao" class="com.qfedu.factory.StaticBeanFactory" factory-method="getUserDaoImpl"></bean>
測試
//演示通過靜態工廠建立Bean
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.save();
}
工廠例項方法例項化
建立動態工廠
public class DynamicBeanFactory {
public UserDao getUserDao() {
return new UserDaoImpl();
}
}
在Spring配置檔案中配置
<bean id="factory" class="com.qfedu.factory.DynamicBeanFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>
測試同上
DI(依賴注入)
依賴注入:Dependency Injection ,指容器負責建立和維護物件之間的依賴關係,而不是通過物件本身負責自己的建立和解決自己的依賴。在當前類需要用到其他類的物件,由Spring為我們提供,我們只需要在配置中說明。
業務層和持久層的依賴關係,在使用 Spring 之後,就讓 Spring 來維護了。
簡單的說,就是坐等框架把持久層物件傳入業務層,而不用我們自己去獲取。
即service中的private UserDao userDao = new 物件()
,變成 private UserDao userDao;
,就是省略new
構造方法注入
- 建立介面UserService和實現類UserServiceImpl
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
//這裡一定要有該屬性,我們最終的目的是讓該屬性關聯一個UserDaoImpl的物件
private UserDao userDao;
public UserServiceImpl() {
}
//一定要有該有參的構造方法,通過該方法完成依賴注入
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
}
- 在Spring配置檔案中配置
<bean id="userDao" class="com.qfedu.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.qfedu.service.impl.UserServiceImpl">
<!-- 構造方法注入,通過ref將id為“userDao”的bean傳遞給了UserServiceImpl構造方法的userDao形參 -->
<constructor-arg name="userDao" ref="userDao" />
</bean>
- 測試
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = (UserService)context.getBean("userService");
userService.save();
}
set方法注入(重點)
在UserServiceImpl中新增set方法
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
在Spring配置檔案中配置
<bean id="userService" class="UserServiceImpl">
<!-- set方法注入 -->
<property name="userDao" ref="userDao"></property>
</bean>
測試方法同上
p名稱空間注入
p名稱空間注入本質也是set方法注入,但比起上述的set方法注入更加方便,主要體現在配置檔案中
引入P名稱空間
xmlns:p="http://www.springframework.org/schema/p"
在Spring配置檔案中配置
<!-- p名稱空間注入 -->
<bean id="userService" class="com.qfedu.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
依賴注入其他型別
上面的案例,我們學習瞭如何注入引用型別的資料,除了引用資料型別,普通資料型別,集合資料型別也可以注入。
普通資料型別注入
建立Department實體類
//表示部門的實體類
public class Department {
private Integer id;//部門編號
private String name;//部門名稱
private String desc;//部門描述
//set、get方法
//toString方法
}
- 在Spring配置檔案中配置
<!--
通過Spring的IOC容器建立Department類的物件,併為其屬性注入值
無參構造方法例項化
-->
<bean id="department" class="com.qfedu.entity.Department">
<!-- set方法注入
value:簡單型別
-->
<property name="id" value="1" />
<property name="name" value="研發部" />
<property name="desc" value="專案研發" />
</bean>
測試
@Test
public void test6() {
//解析配置檔案 -- 建立物件 -- 物件交給Spring的IOC容器進行管理
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//獲取Department的物件
Department department = (Department)context.getBean("department");
//列印物件
System.out.println(department);
}
引用型別注入
- 建立實體類Department
//表示部門的實體類
public class Department {
private Integer id;//部門編號
private String name;//部門名稱
private String desc;//部門描述
private Address address;//部門地址
//set、get
//toString
}
在Spring配置檔案中配置
<bean id="address" class="com.qfedu.entity.Address">
<property name="province" value="山東省" />
<property name="city" value="青島市" />
<property name="county" value="市北區" />
<property name="street" value="龍城路" />
<property name="no" value="31號" />
</bean>
<!--
通過Spring的IOC容器建立Department類的物件,併為其屬性注入值
無參構造方法例項化
-->
<bean id="department" class="com.qfedu.entity.Department">
<!-- set方法注入
value:簡單型別
-->
<property name="id" value="1" />
<property name="name" value="研發部" />
<property name="desc" value="專案研發" />
<!-- set方法注入
ref:引用型別
-->
<property name="address" ref="address" />
</bean>
測試同上
集合資料型別(List<String>
)的注入
建立Employee實體類
//表示員工的實體類
public class Employee {
private Integer id;//員工編號
private String name;//姓名
private Integer age;//年齡
private String gender;//性別
private List<String> hobby;//愛好
//set、get方法
//toString方法
}
- 在Spring配置檔案中配置
<!--
通過Spring的IOC容器建立Employee類的物件,併為其屬性注入值
無參構造方法例項化
-->
<bean id="e1" class="com.qfedu.entity.Employee">
<property name="id" value="1" />
<property name="name" value="zs" />
<property name="age" value="30" />
<property name="gender" value="男" />
<!-- 集合型別注入 -->
<property name="hobby">
<list>
<value>學習1</value>
<value>學習2</value>
<value>學習3</value>
</list>
</property>
</bean>
測試同上
集合資料型別(List<物件>
)的注入
- 修改Department實體類
//表示部門的實體類
public class Department {
private Integer id;//部門編號
private String name;//部門名稱
private String desc;//部門描述
private Address address;//部門地址
private List<Employee> emps;//普通員工
//set、get方法
//toString方法
}
- 在Spring配置檔案中配置
<bean id="e1" class="com.qfedu.entity.Employee">
<property name="id" value="1" /> <property name="name" value="zs" /> <property name="age" value="30" /> <property name="gender" value="男" />
<!-- 集合型別注入 -->
<property name="hobby"> <list> <value>學習1</value> <value>學習2</value> <value>學習3</value> </list> </property>
</bean>
<bean id="e2" class="com.qfedu.entity.Employee">
<property name="id" value="1" /> <property name="name" value="ls" /> <property name="age" value="31" /> <property name="gender" value="男" />
<!--集合型別注入 -->
<property name="hobby"> <list> <value>爬山</value> <value>游泳</value> <value>網遊</value> </list> </property>
</bean>
<bean id="department" class="com.qfedu.entity.Department">
<!-- set方法注入
value:簡單型別
-->
<property name="id" value="1" />
<property name="name" value="研發部" />
<property name="desc" value="專案研發" />
<!-- set方法注入
ref:引用型別
-->
<property name="address" ref="address" />
<property name="emps">
<list>
<ref bean="e1" />
<ref bean="e2" />
</list>
</property>
</bean>
測試同上
集合資料型別(Map<String>
)的注入
- 修改Department,新增屬性
//表示部門的實體類
public class Department {
private Integer id;//部門編號
private Map<String, Employee> leader;//部門主管
//set、get
//toString
}
- 在Spring配置檔案中配置
<bean id="e1" class="com.qfedu.entity.Employee">
<property name="id" value="1" />
</bean>
<bean id="e2" class="com.qfedu.entity.Employee">
<property name="id" value="2" />
</bean>
<bean id="department" class="com.qfedu.entity.Department">
<property name="id" value="1" />
<property name="leader">
<map>
<entry key="CEO" value-ref="e1" />
<entry key="CTO" value-ref="e2" />
</map>
</property>
</bean>
測試同上
集合資料型別(properties)的注入
建立實體類JdbcConfig,新增Properties
package com.qfedu.entity;
import java.util.Properties;
public class JdbcConfig {
private Properties config;
public Properties getConfig() {
return config;
}
public void setConfig(Properties config) {
this.config = config;
}
@Override
public String toString() {
return "JdbcConfig{" +
"config=" + config +
'}';
}
}
在Spring配置檔案中配置
<bean id="jdbcConfig" class="com.qfedu.entity.JdbcConfig">
<!-- Properties型別的注入 -->
<property name="config">
<props>
<prop key="driverName">com.mysql.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/test</prop>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
測試
@Test
public void test8() {
//解析配置檔案 -- 建立物件 -- 物件交給Spring的IOC容器進行管理
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//獲取Employee的物件
JdbcConfig config = (JdbcConfig)context.getBean("jdbcConfig");
//列印物件
System.out.println(config);
}
引入其他配置檔案
實際開發中,Spring的配置內容非常多,這就導致Spring配置很繁雜且體積很大,所以,可以將部分配置拆解到其他配置檔案中,而在Spring主配置檔案通過import標籤進行載入。
<import resource="applicationContext-xxx.xml"/>