Spring學習(三)---依賴注入

容器注入

這裏寫圖片描述

設計中要求: 上層類不依賴於下層的具體實現,而是依賴於下層的接口,允許下層的實現進行任意切換

上層類:


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生命週期流程圖:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章