Spring框架筆記(三)——Spring容器、屬性注入和構造器注入詳解

Spring 容器java

在 Spring IOC 容器讀取 Bean 配置建立 Bean 實例以前, 必須對它進行實例化. 只有在容器實例化後, 才能夠從 IOC 容器裏獲取 Bean 實例並使用.spring

Spring 提供了兩種類型的 IOC 容器實現. shell

BeanFactory: IOC 容器的基本實現.app

ApplicationContext: 提供了更多的高級特性. 是 BeanFactory 的子接口.框架

BeanFactory 是 Spring 框架的基礎設施,面向 Spring 自己;eclipse

ApplicationContext 面向使用 Spring 框架的開發者,幾乎全部的應用場合都直接使用 ApplicationContext 而非底層的 BeanFactoryide

不管使用何種方式, 配置文件時相同的.測試


ApplicationContext 的主要實現類:this

    ClassPathXmlApplicationContext:從 類路徑下加載配置文件lua

    FileSystemXmlApplicationContext: 從文件系統中加載配置文件

ConfigurableApplicationContext 擴展於 ApplicationContext,新增長兩個主要方法:refresh() 和 close(), 讓 ApplicationContext 具備啓動、刷新和關閉上下文的能力。(後面的博文會提到Bean的聲明週期方法的時候會詳細介紹)

ApplicationContext 在初始化上下文時就實例化全部單例的 Bean。(順便提一下,是Bean能夠不是單實例的,這個等到Bean的做用域大的時候再說)

WebApplicationContext 是專門爲 WEB 應用而準備的,它容許從相對於 WEB 根目錄的路徑中完成初始化工做

咱們以前用的ClassPathXmlApplicationContext對象的getBean方法其實源自接口BeanFactory。


在 Spring 的 IOC 容器裏配置 Bean(在applicationContext.xml中配置)

<bean id="BKBeanId" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="defaultName"></property>
	</bean>

id是Bean 的名稱,以前我在博文中已經介紹過,這裏咱們對其細節概括爲如下三點:

1. 在 IOC 容器中必須是惟一的

2. 若 id 沒有指定,Spring 自動將權限定性類名做爲 Bean 的名字

3. id 能夠指定多個名字,名字之間可用逗號、分號、或空格分隔

使用Spring IOC容器的方法以下:

@Test
	public void test2()
	{		
		//建立spring IOC容器對象。ClassPathXmlApplicationContext表示配置文件在類路徑下,它是接口ApplicationContext的一個實現類。實現類從類路徑下加載配置文件。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//從IOC容器中獲取Bean實例
		BKBean dfBean=(BKBean)ctx.getBean("BKBeanId");
		//調用對象方法
		dfBean.doJob();
	}

這個BeanFactory的getBean方法有多個重載方法,咱們還能夠利用Bean的類來進行getBean

@Test
	public void test5()
	{		
		//建立spring IOC容器對象。ClassPathXmlApplicationContext表示配置文件在類路徑下,它是接口ApplicationContext的一個實現類。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//從IOC容器中獲取Bean實例
		BKBean dfBean=(BKBean)ctx.getBean(BKBean.class);
		//調用對象方法
		dfBean.doJob();
	}

可是這裏運行的時候報錯了!!

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.happBKs.spring.iocaop.beans.BKBean] is defined: expected single matching bean but found 4: BKBeanId,BKBeanId_BK1,BKBeanId_BK2,BKBeanId_BK3
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:365)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:968)
	at com.happBKs.spring.iocaop.TestApp1.test5(TestApp1.java:65)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

緣由是咱們的容器中,即applicationContext.xml中存在多個Bean的class制定的類是相同的。

<?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 id="BKBeanId" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="defaultName"></property>
	</bean>


	<bean id="BKBeanId_BK1" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="BK1"></property>
	</bean>
	<bean id="BKBeanId_BK2" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="BK2"></property>
	</bean>
	<bean id="BKBeanId_BK3" class="com.happBKs.spring.iocaop.beans.BKBean">
		<property name="name" value="BK3"></property>
	</bean>

</beans>

所以,getBean沒法知道應該加載那個bean的對象。因此咱們須要其餘相同類的bean去除。

注意:當applicationContext.xml中有多個相同類型的bean時,請不要使用class的這種getBean的方法,而是使用id來getBean。即利用類型返回IOC容器中的bean,但要求IOC容器中只有一個該類型的bean。


依賴注入的方式

Spring 支持 3 種依賴注入的方式

屬性注入

構造器注入

工廠方法注入(不多使用,不推薦)


屬性注入

屬性注入即經過 setter 方法注入Bean 的屬性值或依賴的對象

屬性注入使用 <property> 元素, 使用 name 屬性指定 Bean 的屬性名稱,value 屬性或 <value> 子節點指定屬性值 

屬性注入是實際應用中最經常使用的注入方式

這種方式,實際咱們以前的例已經介紹過了,這裏再也不累述。

(本文出自:http://my.oschina.net/happyBKs/blog/477557)

構造方法注入

經過構造方法注入Bean 的屬性值或依賴的對象,它保證了 Bean 實例在實例化後就可使用。

構造器注入在 <constructor-arg> 元素裏聲明屬性

咱們如今先構造一個Car的bean類

package com.happBKs.spring.iocaop.beans;

public class Car {
	String name;
	double price;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public Car() {
		super();
	}
	public Car(String name1, double price1) {
		super();
		this.name = name1;
		this.price = price1;
	}
	@Override
	public String toString() {
		return "Car [name=" + name + ", price=" + price + "]";
	}
}

這裏有三種方法,來實現構造器注入的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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="car_id" class="com.happBKs.spring.iocaop.beans.Car">
		<constructor-arg value="BM"></constructor-arg>
		<constructor-arg value="500000"></constructor-arg>
	</bean>

	<bean id="car_Index_id" class="com.happBKs.spring.iocaop.beans.Car">
		<constructor-arg value="400000" index="1"></constructor-arg>
		<constructor-arg value="BM" index="0"></constructor-arg>
	</bean>

	<bean id="car_name_id" class="com.happBKs.spring.iocaop.beans.Car">
		<constructor-arg name="price1" value="300000"></constructor-arg>
		<constructor-arg name="name1" value="BM"></constructor-arg>
	</bean>

</beans>

第一種方式:利用構造器的參數順序,依次排列各個construcctor-arg

第二種方式:用index屬性指定構造器參數的順序,從0開始

第三種方式:構造器參數的名字來指定是哪個參數賦值

測試方法:

@Test
	public void test6_1()
	{
		//建立spring IOC容器對象。ClassPathXmlApplicationContext表示配置文件在類路徑下,它是接口ApplicationContext的一個實現類。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//從IOC容器中獲取Bean實例
		Car cBean=(Car)ctx.getBean("car_id");
		//調用對象方法
		System.out.println(cBean);
	}
	
	@Test
	public void test6_2()
	{
		//建立spring IOC容器對象。ClassPathXmlApplicationContext表示配置文件在類路徑下,它是接口ApplicationContext的一個實現類。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//從IOC容器中獲取Bean實例
		Car cBean=(Car)ctx.getBean("car_Index_id");
		//調用對象方法
		System.out.println(cBean);
	}
	
	@Test
	public void test6_3()
	{
		//建立spring IOC容器對象。ClassPathXmlApplicationContext表示配置文件在類路徑下,它是接口ApplicationContext的一個實現類。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//從IOC容器中獲取Bean實例
		Car cBean=(Car)ctx.getBean("car_name_id");
		//調用對象方法
		System.out.println(cBean);
	}

運行結果1:

Car [name=BM, price=500000.0]

運行結果2:

Car [name=BM, price=400000.0]

運行結果3:

Car [name=BM, price=300000.0]

注意:方式三的constructor-arg的name屬性必須與bean的構造方法的形參名稱同樣,而不是和bean的屬性名同樣,須要特別注意!




構造方法的重載問題

好,如今咱們還說明一個問題。請看這樣一個例子。如今有一個bean, Car2的定義以下:

package com.happBKs.spring.iocaop.beans;

public class Car2 {
	String name;
	double price;
	int version;
	double speed;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public int getVersion() {
		return version;
	}
	public void setVersion(int version) {
		this.version = version;
	}
	public double getSpeed() {
		return speed;
	}
	public void setSpeed(double speed) {
		this.speed = speed;
	}
	public Car2() {
		super();
	}
	public Car2(String name, double price, int version) {
		super();
		this.name = name;
		this.price = price;
		this.version = version;
	}
	public Car2(String name, double price, double speed) {
		super();
		this.name = name;
		this.price = price;
		this.speed = speed;
	}
	public Car2(String name, double price, int version, double speed) {
		super();
		this.name = name;
		this.price = price;
		this.version = version;
		this.speed = speed;
	}
	@Override
	public String toString() {
		return "Car2 [name=" + name + ", price=" + price + ", version="
				+ version + ", speed=" + speed + "]";
	}
	
	

}

咱們的容器配製以下:applicationContext.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 id="car2_id" class="com.happBKs.spring.iocaop.beans.Car2">
		<constructor-arg value="BM" index="0"></constructor-arg>
		<constructor-arg value="500000" index="1"></constructor-arg>
		<constructor-arg value="1" index="2"></constructor-arg>
	</bean>

</beans>

如今咱們嘗試調用測試:使用賦予參數name、price、version的構造器:

@Test
	public void test7()
	{
		//建立spring IOC容器對象。ClassPathXmlApplicationContext表示配置文件在類路徑下,它是接口ApplicationContext的一個實現類。
		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
		//從IOC容器中獲取Bean實例
		Car2 c2Bean=(Car2)ctx.getBean("car2_id");
		//調用對象方法
		System.out.println(c2Bean);
	}

可是運行結果倒是:

Car2 [name=BM, price=500000.0, version=0, speed=1.0]


getBean返回的使容器中由構造器public Car2(String name, double price, double speed)返回的對象。這就是構造器重載可能帶來的問題。

解決方法是:

方法1:利用constructor-arg標籤中的type屬性,指定參數類型。

將剛纔的容器配製改成:

<bean id="car2_id" class="com.happBKs.spring.iocaop.beans.Car2">
		<constructor-arg value="BM" index="0"></constructor-arg>
		<constructor-arg value="500000" index="1"></constructor-arg>
		<constructor-arg value="1" index="2" type="int"></constructor-arg>
	</bean>


在運行test7測試方法:

Car2 [name=BM, price=500000.0, version=1, speed=0.0]


這樣就OK了。


方法2:利用constructor-arg標籤中的name屬性,指定參數名稱。

<bean id="car2_id" class="com.happBKs.spring.iocaop.beans.Car2">
		<constructor-arg value="BM" index="0"></constructor-arg>
		<constructor-arg value="500000" index="1"></constructor-arg>
		<constructor-arg value="1" index="2" name="version"></constructor-arg>
	</bean>

在運行test7測試方法:

Car2 [name=BM, price=500000.0, version=1, speed=0.0]


你們記住spring的容器對應於applicationContext.xml配製文件,容器構造對象時在加載該配製文件的時候,而不是實際getBean的時候。

相關文章
相關標籤/搜索