【Spring】理解Ioc控制反轉

IoC 全稱爲 Inversion of Control,翻譯爲 “控制反轉”,依賴注入DI(Dependency Injection)是它的具體實現。依賴注入這個詞讓人望而生畏,現在已經演變成一項複雜的編程技巧或設計模式理念。但事實證明,依賴注入並不像它聽上去那麼複雜。在項
目中應用DI,你會發現你的代碼會變得異常簡單並且更容易理解和測試。本篇文章將主要介紹Ioc的概念和作用,以及Ioc是如何解決程序的耦合性問題的。

程序的耦合與解耦

耦合指的就是就是對象之間的依賴性。耦合具有兩面性:一方面,緊密耦合的代碼難以測試、難以複用、難以理解,並且典型地表現出“打地鼠”式的bug特性(修復一個bug,將會出現一個或者更多新的bug)。另一方面,一定程度的耦合又是必須的—完全沒有耦合的代碼什麼也做不了。爲了完成有實際意義的功能,不同的類必須以適當的方式進行交互。總而言之,耦合是必須的,但應當被小心謹慎地管理。

例如下面的程序(選自Spring實戰第四版):

一個騎士的接口:

public interface Knight {
  void embarkOnQuest();
}

一個探險任務的接口:

public interface Quest {
  void embark();
}

 創建一次拯救少女的探險任務,實現探險任務接口:

public class RescueDamselQuest implements Quest {
  public void embark() {
    System.out.println("Embarking on a quest to rescue the damsel.");
  }
}

 創建一個營救少女的騎士,實現騎士接口。

public class DamselRescuingKnight implement Knight{

         private RescueDamselQuest quest;
 
         pulicDamselRescuingKnight(){
              //RescueDamselQuest類與DamselRescuingKnight類緊密地耦合到了一起
              this.quest=new RescueDamselQuest();
         }
 
         //測試該方法時也會變得很困難,因爲你無法保證quest.embark()方法被調用,因爲quest可能並沒有被創建
         public void embarkOnQuest(){
              quest.embark();
         }
 
}

騎士實現了Knight接口,有了embarkOnQuest功能,DamselRescuingKnight在構造函數中自行構建了RescueDamselQuest,這使得DamselRescuingKnight和RescueDamseQuest緊密地連接在一起,這極大限制了騎士執行的探險任務。如果有其他的Quest讓騎士去embarkOnQuest,那騎士就無能爲力了。

進行改造

通過依賴注入,對象的依賴關係將由系統中負責協調各對象的第三方組件在創建對象的時候進行設定。對象無需自行創建或管理它們的依賴關係。

public class BraveKnight implements Knight {
  private Quest quest;
  public BraveKnight(Quest quest) {
    this.quest = quest;
  }
  public void embarkOnQuest() {
    quest.embark();
  }
}

現在這個勇敢的騎士內部並沒有自行創建任務,而是將探險任務當作構造參數傳進來,屬於構造函數注入。任何探險任務只實現了Ouest接口都可以被注入進來讓BraveKnight響應。這樣就降低了兩個類之間的依賴。

例如現在又有一個打巨龍的任務:

public class SlayDragonQuest implements Quest {

    private PrintStream stream;

    public SlayDragonQuest(PrintStream stream) {
        this.stream = stream;
    }

    public void embark() {

        stream.println("Embarking on quest to slay the dragon!");
    }

}

這個實現Ouest的接口可以注入到時Knight之中,項目中新建一個spring-context.xml文件,在源碼的路徑下。創建對象之間的協作行爲稱之爲裝配,Spring有許多裝配Bean的方式,XML、JavaCode、基於註解的自動裝配。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.xsd">
    <!--聲明一個Bean以及它的依賴關係-->
    <bean id="slayDragonQuest" class="com.test.bean.SlayDragonQuest">
        <constructor-arg value="#{T(java.lang.System).out}"></constructor-arg>
    </bean>
    <bean id="braveKnight" class="com.test.bean.BraveKnight">
        <constructor-arg ref="slayDragonQuest"/>
    </bean>

</beans>

Spring通過應用上下文(Application Context)來裝配bean的定義把它們組裝起來,Spring全權負責bean的創建和組裝。Spring有多種上下文的實現,區別就是如何加載bean的配置。

工廠模式解耦

在Web項目中,UI層,Service層,Dao層之間有着前後調用的關係。

public class MyServiceImpl implements IMyService {

    private IMyDao myDao = new MyDaoImpl();	// 業務層要調用持久層的接口和實現類

    public void myService(){
        myDao.serviceProcess();
    }
}

業務層依賴持久層的接口和實現類,若編譯時不存在沒有持久層實現類,則編譯將不能通過,這構成了編譯期依賴。

在實際開發中我們可以把展示層,業務層,持久層三層的對象都使用配置文件配置起來,當啓動服務器應用加載的時候,讓一個類中的方法通過讀取配置文件,把這些對象創建出來並存起來。在接下來的使用的時候,直接拿過來用就好了。 那麼,這個讀取配置文件,創建和獲取三層對象的類就是工廠。

使用springIOC解決程序耦合

簡單實例

1.準備工作: 創建MAVEN項目,並準備三層接口類和實現類
  創建maven項目,配置其pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.maoritian</groupId>
    <artifactId>learnspring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
    	<!-- 引入-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>

</project>

2. 配置bean:在類的根路徑下的resource目錄下創建bean.xml文件,把對象的創建交給spring來管理。
   每個<bean>標籤對應一個類,其class屬性爲該類的全類名,id屬性爲該類的id,在spring配置中通過id獲取類的對象。

 <?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.xsd">
 
     <!--把對象的創建交給spring來管理-->
     <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
     <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
 </beans>

3.在表現層文件Client.java中通過容器創建對象。通過核心容器的getBean()方法獲取具體對象。

public class Client {
     public static void main(String[] args) {
         // 獲取核心容器對象
         ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
         // 根據id獲取Bean對象
         IAccountService as  = (IAccountService)ac.getBean("accountService");
         
         // 執行as的具體方法
         // ...
     }
 }

我們常用的容器有三種實現類: ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,AnnotationConfigApplicationContext.

  • ClassPathXmlApplicationContext: 它是從類的根路徑下加載配置文件(常用)
  • FileSystemXmlApplicationContext: 它是從磁盤路徑上加載配置文件(前提是有訪問權限)
  • AnnotationConfigApplicationContext: 讀取註解創建容器

核心容器中兩個接口引發的問題:

ApplicationContext(採用):立即加載,讀取完配置文件後就立即創建對象,餓漢式那種

BeanFctory:延遲加載,使用這個對象的時候才創建,懶漢式那種

使用XML配置文件實現IOC

使用配置文件實現IOC,要將託管給spring的類寫進bean.xml配置文件中:

bean標籤

  • 作用: 配置託管給spring的對象,默認情況下調用類的無參構造函數,若果沒有無參構造函數則不能創建成功
  • 屬性:
  1. id: 指定對象在容器中的唯一標識,將其作爲參數傳入getBean()方法可以獲取獲取對應對象。
  2. class: 指定類的全類名,默認情況下調用無參構造函數進行創建。
  3. scope: 指定對象的作用範圍,可選值如下
    1. singleton: 單例對象,默認值
    2. prototype: 多例對象
    3. request: 將對象存入到web項目的request域中
    4. session: 將對象存入到web項目的session域中
    5. global session: 將對象存入到web項目集羣的session域中,若不存在集羣,則global session相當於session
  4. init-method:指定類中的初始化方法名稱,在對象創建成功之後執行。
  5. destroy-method:指定類中銷燬方法名稱,對prototype多例對象沒有作用,因爲多例對象的銷燬時機不受容器控制。

bean的作用範圍和生命週期

  1. 單例對象: scope="singleton"
    1. 作用範圍: 每個應用只有一個該對象的實例,它的作用範圍就是整個應用
    2. 生命週期: 單例對象的創建與銷燬 和 容器的創建與銷燬時機一致
    3. 對象出生: 當應用加載,創建容器時,對象就被創建
    4. 對象活着: 只要容器存在,對象一直活着
    5. 對象死亡: 當應用卸載,銷燬容器時,對象就被銷燬
  2. 多例對象: scope="prototype"
    1. 作用範圍: 每次訪問對象時,都會重新創建對象實例.
    2. 生命週期: 多例對象的創建與銷燬時機不受容器控制
    3. 對象出生: 當使用對象時,創建新的對象實例
    4. 對象活着: 只要對象在使用中,就一直活着
    5. 對象死亡: 當對象長時間不用時,被 java 的垃圾回收器回收了

實例化 Bean 的三種方式

1. 使用默認無參構造函數創建對象

默認情況下會根據默認無參構造函數來創建類對象,若Bean類中沒有默認無參構造函數,將會創建失敗。

<bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl"></bean>

2. 使用靜態工廠的方法創建對象

創建一個模擬的靜態工廠如(在實際項目中,這個工廠可能在一個jar包中):

// 靜態工廠,其靜態方法用於創建對象
public class StaticFactory {
	public static IAccountService createAccountService(){
		return new AccountServiceImpl();
	}
}

使用StaticFactory類中的靜態方法createAccountService創建對象,涉及到<bean>標籤的屬性:

  1. id屬性: 指定對象在容器中的標識,用於從容器中獲取對象
  2. class屬性: 指定靜態工廠的全類名
  3. factory-method屬性: 指定生產對象的靜態方法
<bean id="accountService" class="cn.maoritian.factory.StaticFactory" 
    factory-method="createAccountService"></bean>

其實,類的構造函數也是靜態方法,因此默認無參構造函數也可以看作一種靜態工廠方法。

3.使用實例工廠的方法創建對象

創建實例工廠如下:

public class InstanceFactory {
	public IAccountService createAccountService(){
		return new AccountServiceImpl();
	}
}

先創建實例工廠對象instanceFactory,通過調用其createAccountService()方法創建對象,涉及到<bean>標籤的屬性:

  1. factory-bean屬性: 指定實例工廠的id
  2. factory-method屬性: 指定實例工廠中生產對象的方法
<bean id="instancFactory" class="cn.maoritian.factory.InstanceFactory"></bean>
<bean id="accountService"
	factory-bean="instancFactory"
	factory-method="createAccountService"></bean>

依賴注入

依賴注入(Dependency Injection)是spring框架核心ioc的具體實現.

通過控制反轉,我們把創建對象託管給了spring,但是代碼中不可能消除所有依賴,例如:業務層仍然會調用持久層的方法,因此業務層類中應包含持久化層的實現類對象。
我們等待框架通過配置的方式將持久層對象傳入業務層,而不是直接在代碼中new某個具體的持久化層實現類,這種方式稱爲依賴注入。

因爲我們是通過反射的方式來創建屬性對象的,而不是使用new關鍵字,因此我們要指定創建出對象各字段的取值。一共有三種方式進行依賴注入:

1. 使用構造函數注入

通過類默認的構造函數來給創建類的字段賦值,相當於調用類的構造方法。

涉及的標籤: <constructor-arg>用來定義構造函數的參數,其屬性可大致分爲兩類:

  1. 尋找要賦值給的字段
    1. index: 指定參數在構造函數參數列表的索引位置
    2. type: 指定參數在構造函數中的數據類型
    3. name: 指定參數在構造函數中的變量名,最常用的屬性
  2. 指定賦給字段的值
    1. value: 給基本數據類型和String類型賦值
    2. ref: 給其它Bean類型的字段賦值,ref屬性的值應爲配置文件中配置的Bean的id
public class AccountServiceImpl implements IAccountService {

    //如果是經常變化的數據,並不適用於注入的方式
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public void saveAccount() {
		System.out.println(name+","+age+","+birthday);
    }
}
<!-- 使用Date類的無參構造函數創建Date對象 -->
<bean id="now" class="java.util.Date" scope="prototype"></bean>

<bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl">
	<constructor-arg name="name" value="myname"></constructor-arg>
	<constructor-arg name="age" value="18"></constructor-arg>
	<!-- birthday字段爲已經註冊的bean對象,其id爲now -->
	<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>

2.使用set方法注入(更常用)

在類中提供需要注入成員屬性的set方法,創建對象只調用要賦值屬性的set方法.

涉及的標籤: <property>,用來定義要調用set方法的成員。 其主要屬性可大致分爲兩類:

  1. 指定要調用set方法賦值的成員字段
    1. name:要調用set方法賦值的成員字段
  2. 指定賦給字段的值
    1. value: 給基本數據類型和String類型賦值
    2. ref: 給其它Bean類型的字段賦值,ref屬性的值應爲配置文件中配置的Bean的id
public class AccountServiceImpl implements IAccountService {

	private String name;
	private Integer age;
	private Date birthday;

	public void setName(String name) {
		this.name = name;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	@Override
	public void saveAccount() {
		System.out.println(name+","+age+","+birthday);
	}
}
<!-- 使用Date類的無參構造函數創建Date對象 -->
<bean id="now" class="java.util.Date" scope="prototype"></bean>

<bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl">
	<property name="name" value="myname"></property>
	<property name="age" value="21"></property>
	<!-- birthday字段爲已經註冊的bean對象,其id爲now -->
	<property name="birthday" ref="now"></property>
</bean>

3. 注入集合字段

集合字段及其對應的標籤按照集合的結構分爲兩類: 相同結構的集合標籤之間可以互相替換.

  1. 只有鍵的結構:
    1. 數組字段: <array>標籤表示集合,<value>標籤表示集合內的成員.
    2. List字段: <list>標籤表示集合,<value>標籤表示集合內的成員.
    3. Set字段: <set>標籤表示集合,<value>標籤表示集合內的成員.
    4. 其中<array>,<list>,<set>標籤之間可以互相替換使用.
  2. 鍵值對的結構:
    1. Map字段: <map>標籤表示集合,<entry>標籤表示集合內的鍵值對,其key屬性表示鍵,value屬性表示值.
    2. Properties字段: <props>標籤表示集合,<prop>標籤表示鍵值對,其key屬性表示鍵,標籤內的內容表示值.
    3. 其中<map>,<props>標籤之間,<entry>,<prop>標籤之間可以互相替換使用.

下面使用set方法注入各種集合字段

public class AccountServiceImpl implements IAccountService {
	// 集合字段
	private String[] myArray;
	private List<String> myList;
	private Set<String> mySet;
	private Map<String,String> myMap;
	private Properties myProps;

	// 集合字段的set方法
	public void setMyStrs(String[] myArray) {
		this.myArray = myArray;
	}
	public void setMyList(List<String> myList) {
		this.myList = myList;
	}
	public void setMySet(Set<String> mySet) {
		this.mySet = mySet;
	}
	public void setMyMap(Map<String, String> myMap) {
		this.myMap = myMap;
	}
	public void setMyProps(Properties myProps) {
		this.myProps = myProps;
	}
	
	@Override
	public void saveAccount() {
		System.out.println(Arrays.toString(myArray));
		System.out.println(myList);
		System.out.println(mySet);
		System.out.println(myMap);
		System.out.println(myProps);
	}
}
<bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl3">
	<property name="myStrs">
		<array>
			<value>value1</value>
			<value>value2</value>
			<value>value3</value>
		</array>
	</property>

	<property name="myList">
		<list>
			<value>value1</value>
			<value>value2</value>
			<value>value3</value>
		</list>
	</property>

	<property name="mySet">
		<set>
			<value>value1</value>
			<value>value2</value>
			<value>value3</value>
		</set>
	</property>

	<property name="myMap">
		<map>
			<entry key="key1" value="value1"></entry>
			<entry key="key2">
				<value>value2</value>
			</entry>
			
		</map>
	</property>

	<property name="myProps">
		<props>
			<prop key="key1">value1</prop>
			<prop key="key2">value2</prop>
		</props>
	</property>
</bean>

使用註解實現IOC

使用註解實現IOC,要將註解寫在類的定義中

常用註解

用於創建對象的註解

這些註解的作用相當於bean.xml中的<bean>標籤,該註解告知Spring要爲這個類創建bean。

  1. @Component: 把當前類對象存入spring容器中,其屬性如下:
    1. value: 用於指定當前類的id,也就是該類的唯一標識,不寫時默認值是當前類名,且首字母改小寫。
    2. 註解表明該類會作爲組件類,會告知Spring要創建類的bean。
    @Component("accountService")
    public class AccountServiceImpl implements IAccountService {
    }
    
    @Component("accountService")註解相當於原來的:
        <bean id="accountService" class="com.lwl.service.impl.AccountServiceImpl"></bean>
    
  2. @Controller: 將當前表現層對象存入spring容器中
  3. @Service: 將當前業務層對象存入spring容器中
  4. @Repository: 將當前持久層對象存入spring容器中

@Controller,@Service,@Repository註解的作用和屬性與@Component是一模一樣的,可以相互替代,它們的作用是使三層對象的分別更加清晰.

使用xml啓動組件掃描:使用註解方式時,需要建立一個xml文件,默認組件掃描是不啓用的,需要我們通過xml文件顯示配置一下Spring,好讓程序執行時去掃描這個包中的註解從而命令它去尋找帶有@Component註解的類,爲其創建bean。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:contex="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--告知Spring在創建容器時要掃描的包,配置所需要的標籤不是在bean的約束中,而是在一個名稱爲
    context名稱空間和約束中-->

    <contex:component-scan base-package="com.lwl"></contex:component-scan>
    
</beans>

另外一種啓動組件掃描的方法:@ComponentScan默認會掃描與配置類相同的包,以及該包下面的子包

用於注入數據的註解

這些註解的作用相當於bean.xml中的<property>標籤,表明類中需要要注入的成員變量或方法。

  1. @Autowired: 自動按照成員變量類型注入,只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就可以注入成功。
    1. 注入過程
      1. 當spring容器中有且只有一個對象的類型與要注入的類型相同時,注入該對象。
      2. 當spring容器中有多個對象類型與要注入的類型相同時,使用要注入的變量名作爲bean的id,在spring容器查找鍵,找到相同的則注入該對象,找不到則報錯。
    2. 出現位置: 既可以在變量上,也可以在方法上
    3. 細節: 使用註解注入時,set方法可以省略
  2. @Qualifier: 在自動按照類型注入的基礎之上,再按照bean的id注入。用來指定要注入bean的id
    1. 出現位置: 既可以在成員變量上,也可以在成員方法上。注入成員變量時不能獨立使用,必須和@Autowire一起使用; 注入成員方法時可以獨立使用.
    2. 屬性:
      1. value: 指定bean的id
  3. @Resource: 直接按照bean的id注入,它可以獨立使用.獨立使用時相當於同時使用@Autowired和@Qualifier兩個註解(常用)。
    1. 屬性:
      1. name: 指定bean的id
  4. @Value: 注入基本數據類型和String類型數據。(上面的三個註解無法實現注入基本數據類型和String類型)
    1. 屬性:
      1. value: 用於指定數據的值,可以使用el表達式(${表達式}),Spring中的el表達式成爲SpEL。

用於改變bean作用範圍的註解

這些註解的作用相當於bean.xml中的<bean>標籤的scope屬性.

  • @Scope: 指定bean的作用範圍
    • 屬性:  value: 用於指定作用範圍的取值,"singleton","prototype","request","session","globalsession",不寫value默認情況下是單例的

和生命週期相關的註解

他們的作用和在bean標籤中使用init-method和destory-method的作用是一樣的

  1. @PostConstruct: 用於指定初始化方法
  2. @PreDestroy: 用於指定銷燬方法

實例: 使用純註解配置實現數據庫CRUD

先使用xml文件的方式實現一遍:

1. 項目結構: 其中包com.lwl.service存放業務代碼,dao層選用DBUtilsc3p0.

2. 包com.lwl.service存放業務代碼,其中dao層實現類和service層實現類的代碼如下:

service層:

public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }

    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    public void insertAccount(Account account) {
        accountDao.insertAccount(account);
    }

    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    public void deleteAccount(Integer id) {
        accountDao.deleteAccount(id);
    }
}

dao層:

public class AccountDaoImpl implements IAccountDao {
    private QueryRunner runner; //核心對象

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    public List<Account> findAllAccount() {
        try {
            return runner.query("select * from account1", new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public Account findAccountById(Integer id) {
        try {
            return runner.query("select * from account1 where id = ?",new BeanHandler<Account>(Account.class),id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void insertAccount(Account account) {
        try {
            runner.update("insert into account1(name,money)values(?,?)",account.getName(),account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void updateAccount(Account account) {
        try {
            runner.update("update account1 set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteAccount(Integer id) {
        try {
            runner.update("delete from account1 where id = ?",id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

bean.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.xsd">

    <!--配置Service-->
    <bean id="accountService" class="com.lwl.service.impl.AccountServiceImpl">
        <!--注入Dao對象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置Dao對象-->
    <bean id="accountDao" class="com.lwl.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner對象的實例-->
        <property name="runner" ref="runner"></property>
    </bean>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入數據源,用構造函數注入-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置數據源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--注入連接數據庫的必備信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="1234"></property>
    </bean>
</beans>

測試類:

public class AccountServiceTest {

    @Test
    public void testFindAll(){
        //1.獲取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.得到業務層對象
        IAccountService as = ac.getBean("accountService", IAccountService.class);
        //3.執行方法
        List<Account> accounts = as.findAllAccount();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne(){
        //1.獲取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.得到業務層對象
        IAccountService as = ac.getBean("accountService", IAccountService.class);
        //3.執行方法
        Account account = as.findAccountById(3);
        System.out.println(account);
    }

    @Test
    public void testInsert(){
        //1.獲取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.得到業務層對象
        IAccountService as = ac.getBean("accountService", IAccountService.class);
        //3.執行方法
        Account account = new Account();
        account.setMoney(1200.0);
        account.setName("張張");
        //as.insertAccount(account);
    }

    @Test
    public void testUpdate(){
        //1.獲取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.得到業務層對象
        IAccountService as = ac.getBean("accountService", IAccountService.class);
        //3.執行方法
        Account account = new Account();
        account.setMoney(1200.0);
        account.setName("ddd");
        account.setId(6);
        as.updateAccount(account);
    }
    @Test
    public void testDelete(){
        //1.獲取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.得到業務層對象
        IAccountService as = ac.getBean("accountService", IAccountService.class);
        //3.執行方法
        as.deleteAccount(7);
    }
}

部分使用註解方式實現:

dao層實現類和service層實現類的代碼如下:

service層

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }

}

dao層

@Repository("AccountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private QueryRunner runner; //核心對象

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    public List<Account> findAllAccount() {
        try {
            return runner.query("select * from account1", new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

bean.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--告知Spring在創建容器的時候要掃描的包-->
    <context:component-scan base-package="com.lwl"></context:component-scan>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入數據源,用構造函數注入-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置數據源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--注入連接數據庫的必備信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="1022"></property>
    </bean>
</beans>

測試類代碼不變。

完全使用註解的方式

@Configuration: 用於指定當前類是一個spring配置類,當創建容器時會從該類上加載註解,獲取容器時需要使用AnnotationApplicationContext(有@Configuration註解的類.class).

細節:當配置類作爲AnnotationApplicationContext對象創建的參數時,該註解可以不寫。

@ComponentScan: 指定spring在初始化容器時要掃描的包,作用和bean.xml 文件中<context:component-scan base-package="要掃描的包名"/>是一樣的。 其屬性如下:

  • basePackages: 用於指定要掃描的包,是value屬性的別名

@Bean: 該註解只能寫在方法上,表明使用此方法創建一個對象,並放入spring容器中,其屬性如下:

  • name: 指定此方法創建出的bean對象的id。
  • 細節: 使用註解配置方法時,如果方法有參數,Spring框架會到容器中查找有沒有可用的bean對象,查找的方式與@Autowired註解時一樣的。

@PropertySource: 用於加載properties配置文件中的配置。例如配置數據源時,可以把連接數據庫的信息寫到properties配置文件中,就可以使用此註解指定properties配置文件的位置,其屬性如下:

  • value: 用於指定properties文件位置.如果是在類路徑下,需要寫上"classpath:"

@Import: 用於導入其他配置類.當我們使用@Import註解之後,有@Import註解的類就是父配置類,而導入的都是子配置類. 其屬性如下:

  • value: 用於指定其他配置類的字節碼。

dao層實現類和service層實現類的代碼不變,測試類的方法:

public class AccountServiceTest {

    @Test
    public void testFindAll(){
        //1.獲取容器
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.得到業務層對象
        IAccountService as = ac.getBean("accountService", IAccountService.class);
        //3.執行方法
        List<Account> accounts = as.findAllAccount();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

連接數據庫的配置類:

@PropertySource("classpath:jdbcConfig.properties")
@Configuration
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    //用於創建一個QueryRunner對象
    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    //創建數據源對象
    @Bean(name = "dataSource")
    public DataSource creatDataSource(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

另一個空的配置類:

@Configuration
@ComponentScan(basePackages = "com.lwl")
public class SpringConfiguration {

}

Spring整合junit

1.導入Spring整合junit的jar包

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

2.使用junit提供的@Runwith註解把原有的main方法,替換成spring提供的

3.告知Spring的xml,spring和ioc創建是基於xml還是註解,並且說明位置:

@contextConfigration

    參數:Location:指定xml文件的位置,加上classpath關鍵字,表示在類路徑下

              classes:指定註解類所在位置

注意:當使用Spring5.x版本的時候,要求junit的版本必須是4.12及以上

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {

    @Autowired
    private IAccountService as;

    @Test
    public void testFindAll(){
        //3.執行方法
        List<Account> accounts = as.findAllAccount();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

 

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