容器注入
設計中要求: 上層類不依賴於下層的具體實現,而是依賴於下層的接口,允許下層的實現進行任意切換
上層類:
public class UserServImpl {
private IUserDao userDao;//上層類只和下層接口耦合,並不耦合具體的
public void abc() {
// 具體的業務邏輯處理
userDao.pp();
}
public IUserDao getUserDao() {
return userDao;
}
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
}
底層接口:
//接口有多個不同的實現,未來在UserServImpl中具體使用哪個具體實現,只有配置文件知道
public interface IUserDao {
public void pp();
}
底層實現類:
public class MySqlUserDao implements IUserDao {
@Override
public void pp() {
System.err.println("使用MySql存儲User對象");
}
}
public class OracleUserDao implements IUserDao {
@Override
public void pp() {
System.err.println("使用Oracle存儲User對象");
}
}
配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置方法一 -->
<bean id="mysql" class="com.lq.di01.MySqlUserDao"/>
<bean id="oracle" class="com.lq.di01.OracleUserDao"/>
<bean id="userService" class="com.lq.di01.UserServImpl">
<!-- 配置將受管bean中名稱爲userDao的對象[ref=userDao]設置到userService對象的userDao屬性[name=userDao]上 -->
<property name="userDao" ref="oracle"/>
</bean>
<!-- 配置方法二 -->
<bean id="mysql" class="com.lq.di01.MySqlUserDao"/><!--由容器負責創建userDao對象 -->
<bean id="oracle" class="com.lq.di01.OracleUserDao"/>
<bean id="userService" class="com.lq.di01.UserServImpl"
p:userDao-ref="mysql"/><!--由容器負責創建userServcice對象,並且將userDao對象通過userService的set方法注入到IUserDao userDao屬性上 -->
</beans>
在DAO中需要使用連接池,使用Spring管理連接池【應用控制反轉】
1.添加成品連接池jar,比如使用阿里的連接池druid
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
2.配置連接池
<!-- 配置方法一 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--value用於注入8種簡單類型及其包裝類和String類型,ref用於注入另外一個受管bean對象-->
<property name="url" value="jdbc:mysql:///test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- 配置方法二 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql:///test"
p:username="root" p:password="root" />
<!--p:名稱=值 用於注入8種簡單類型及其包裝類和String類型,p:名稱-ref用於注入另外一個受管bean對象-->
3、將連接池注入dao
public class UserDaoImpl implements IUserDao {
private DataSource ds;
@Override
public void pp() {
System.out.println("UserDaoImpl.pp()");
try {
System.out.println(ds.getConnection());
} catch (SQLException e) {
e.printStackTrace();
}
}
public DataSource getDs() {
return ds;
}
public void setDs(DataSource ds) {
this.ds = ds;
}
}
配置:
<bean id="userServ" class="com.lq.biz.UserServImpl"
p:userDao-ref="userDao" />
<bean id="userDao" class="com.lq.dao.UserDaoImpl" p:ds-ref="dataSource" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql:///test"
p:username="root" p:password="root" />
4、進行單元測試
@RunWith(SpringJUnit4ClassRunner.class) //設置所使用的是Spring提供的執行器
@ContextConfiguration(locations = "classpath:applicationContext.xml") //定義對應的配置文件的位置
public class Test1 {
@Resource(name = "userServ") //注入所需要的測試對象
IUserServ userv;
@Test
public void test() {
userv.abc();
}
}
IoC/DI模式的優點
- 顛覆了“使用對象之前必須創建” 的基本Java語言定律 ,降低了模塊之間的耦合度,提高了應用的靈活性和代碼重用度。
- 使用IoC模式,完全在一個抽象層次進行描述和技術架構,因此,IoC模式可以爲容器、框架之類的軟件實現提供了具體的實現手段
工廠模式和IoC的特點和區別
- 主要區別體現在調用的代碼,如果使用IoC,在代碼中將不需要嵌入任何工廠模式等的代碼,因爲這些工廠模式其實還是與被調用者有些間接的聯繫,這樣使用IoC徹底解耦了工廠和被調用之間的聯繫
- 使用IoC帶來的代價是:需要在客戶端或其它某處進行工廠和被調用之間聯繫的組裝。所以IoC並沒有消除工廠和被調用之間這樣的聯繫,只是轉移了這種聯繫
- 這種聯繫轉移實際也是一種分離關注,它的影響巨大,它提供了AOP實現的可能
依賴注入通常有三種方法:接口注入、設置注入和構造器注入[Spring工廠注入]
1.接口注入
- 接口注入方式發展的比較早,在實際中也得到了普遍的應用。在編程時,常常藉助接口來將調用者與實現者相分離。對於一個接口注入型IoC容器而言,加載接口實現並創建其實例的工作由容器完成,J2EE開發中常用的Context.lookup()都是接口注入型IoC的表現形式
- Servlet中的doGet()和doPost()方法是接口注入,HttpServletRequest和HttpServletResponse實例由Servlet
Container在運行期動態注入 - 由於其在靈活性、易用性上不如其他兩種注入模式,因而在IoC的專題世界內並不被看好
2.設置器注入:依賴於set方法傳入需要依賴的對象
- 要求無參構造器和對應的set方法
- 設值注入是指通過setter()方法傳入被調用者的實例。使用進行設置。這種注入方式簡單、直觀,因而在Spring的依賴注入中大量使用
public class Person {
private Date birth;
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Person [birth=" + birth + "]";
}
}
配置文件:
<bean id="now" class="java.util.Date"/>
<!-- Person的運行需要依賴於birth屬性,由容器負責創建now對象和Person對象,同時將now對象採用setBirth方法設置到birth屬性上 -->
<bean id="person" class="com.lq.di02.Person" p:birth-ref="now"/>
測試類:
public class Test {
public static void main(String[] args) {
AbstractApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = ac.getBean("person", Person.class);
System.out.println(person);
ac.close();//關閉容器
}
}
3.構造器注入
- 通過類的帶參構造器創建對象,並注入依賴對象
3.1 案例一:
更改前:
public class Person {
private Date birth;
private String username;
private int age;
public Person(Date birth,String username,int age) {
this.birth = birth;
this.username=username;
this.age=age;
}
<bean id="now" class="java.util.Date"/>
<bean id="p" class="com.lq.di03.Person">
<constructor-arg ref="now"/>
<constructor-arg value="100"/>
<constructor-arg value="zhangsan"/>
</bean>
注意:在編譯階段,iDE工具不能發現數據異常。但是在具體運行時”100”按照順序賦值給username,這裏沒有問題,但是將”zhangsan”賦值給age時,由於不能進行數據類型轉換則:UnsatisfiedDependencyException
有以下兩種方法可供選擇,
更改後:
1.
public Person(Date birth, int age, String username) {
this.birth = birth;
this.username = username;
this.age = age;
}
注意:如果數據具備一定的二義性時,可以考慮使用type說明參數類型的方法解決
2.
<bean id="now" class="java.util.Date"/>
<bean id="p" class="com.lq.di03.Person">
<constructor-arg ref="now"/>
<constructor-arg value="100" type="int"/>告知系統,這個參數是int類型
<constructor-arg value="zhangsan" type="java.lang.String"/>
</bean>
2.2 案例2
public class Person {
private Date birth;
private String firstName;
private String lastName;
//這裏不能依靠類型進行區分兩個屬性
public Person(Date birth, String firstName, String lastName) {
super();
this.birth = birth;
this.firstName = firstName;
this.lastName = lastName;
}
//配置文件(錯誤):
<bean id="p" class="com.lq.di03.Person">
<constructor-arg value="zhang"/>
<constructor-arg ref="now"/>
<constructor-arg value="san"/>
</bean>
注意:系統識別參數時,按照默認的配置順序識別兩個字串類型屬性
解決方法有以下兩種可供選擇:
1.
<bean id="p" class="com.lq.di03.Person">
<constructor-arg value="zhang" index="2"/>
<!--通過序號強制定義這個參數的序號,注意序號從0開始算起-->
<constructor-arg ref="now"/>
<constructor-arg value="san"/>
</bean>
2.
<bean id="p" class="com.lq.di03.Person">
<constructor-arg value="zhang" name="lastName"/>
<!--通過構造器中形參名稱強制定義這個參數,注意這個定義方法在Spring4-不支持-->
<constructor-arg ref="now"/>
<constructor-arg value="san"/>
</bean>
兩種方法的選擇
- 設置器注入和構造器模式各有千秋,而Spring對構造器注入和設置器注入類型的依賴注入機制提供了良好支持。理論上,以構造器注入類型爲主,輔之以設置器注入類型機制作爲補充,可以達到最好的依賴注入效果。一般來說,對於基於Spring
Framework開發的應用而言,設置器注入使用更加廣泛
Bean的生命週期
init-method:
- init-method=”方法名稱”老方法是實現一個接口Spirng的InitializingBean爲bean提供了定義初始化方法的方式。InitializingBean是一個接口,它僅僅包含一個方法:afterPropertiesSet()。這個方法在對象創建完畢(構造器),然後執行setXXX方法後自動執行,目前建議採用init-method配置即可,不用實現接口,主要可以定義一些初始化動作,這個方法在整個對象的生命週期中運行且只運行一次.方法簽名中可以返回任意類型,允許拋出異常,但是不能包含參數
- Spring要求init-method是一個無參數的方法,如果init-method指定的方法中有參數,那麼Spring將會拋出java.lang.NoSuchMethodException
- init-method指定的方法可以是public、protected以及private的,並且方法也可以是final的
- init-method指定的方法可以是聲明爲拋出異常的
- 如果在init-method方法中拋出了異常,那麼Spring將中止這個Bean的後續處理,並且拋出一個org.springframework.beans.factory.BeanCreationException異常
destroy-method:
destroy-method配置對應DisposableBean接口,可以定義一個在對象銷燬之前執行的方法,可以用於進行資源的釋放.. 這個方法在整個對象的生命週期中運行且只運行一次.方法規則同init-method配置
bean生命週期流程圖: