推薦使用裝配的順序:自動裝配》Java代碼》XML裝配
一,自動裝配
1、Spring從兩個角度實現自動化裝配:
- 組件掃描(component scanning):Spring會自動發現應用上下文中所創建的bean。
- 自動裝配(auto wiring):Spring自動滿足bean之間的依賴。
組件掃描和自動裝配組合在一起就可以發揮強大的威力,它們能將你的顯示配置降到最低。以光盤和播放器爲例,具體看以下代碼:
//先定義一個CompactDisc 接口
public interface CompactDisc {
//具體內容不重要
public void play();
}
//定義一個CompactDisc實現類
@Component("popularMusic")
public class PopularMusic implements CompactDisc {
@Override
public void play() {
System.out.println("播放流行樂");
}
}
//
//配置類
@Configuration
@ComponentScan
public class CDPlayerConfig {
//什麼都不用寫
}
到目前爲止,上述僅僅是一個孤零零的類,什麼也沒做。那麼我們應該如何使用呢?我們有最一般的方法就是new一個對象出來,然後再調用該對象的play()方法即可。但是用new創建對象的方法在實際的業務代碼中增加耦合,難以維護,因此纔有了Spring。接下來看一下如何用自動裝配的方式裝配bean(和普通的Java類,Spring中的特殊稱謂),繼而使用bean的方法。
//單元測試
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void test(){
cd.play();
}
}
//測試結果:
播放流行樂
看完所有代碼會發現,上述代碼和平時自己寫的代碼沒啥區別,僅僅只多了幾個註解(帶@符號的),但就是這幾個註解起到了作用。你自己並沒手動創建一個CompactDisc 對象,而事實的確存在,而且成功運行,這一切都由Spring容器完成。
2、幾個常用的註解:
@Component: 註解用來說名該類作爲一個組件類,並告知Spring要爲該類創建bean
@Configuration: 該註解用來說名該類是一個配置類
@ComponentScan: 啓用Spring組件掃描(默認不啓用),默認掃描與配置類在同一包帶 有@ComPonent註解的類。有兩個屬性:
(1)basePackages:用來設置要進行組件掃描的基礎包,值爲數組,數組屬性爲字符串
(2)basePackageClasses: 設置的數組中包含了類,這些類所在的包將作爲組件掃描的基礎包
@RunWith(SpringJUnit4ClassRunner.class):在測試之前自動生成Spring的應用上下文
@ContextConfiguration(classes = CDPlayerConfig.class):加載配置類。
所以上述代碼大致的執行原理如下:
首先Spring容器首先加載配置類,根據配置類中的設置再去掃描指定包中的帶有@Component的類;然後Spring容器生成掃描到的所有類的對象(默認是單例的),然後根據需要(哪裏有@Autowired)自動裝配所需要的bean,至於bean的生命週期則由Spring管理。
二、Java代碼裝配
要用Java代碼來裝配bean,那麼配置類肯定也是少不了的,一個很簡單的配置類定義如下:
@Configuration
public class JavaConfig {
}
這裏和上面的配置類不同的地方是這裏刪除了@ComponentScan註解,因爲該註解使用進行設置組件掃描的,而在這裏的bean注入是通過Java代碼配置的,因此不需要。如果刪除之後,再去運行上述的CDPlayerTest的話會報BeanCreationException異常。因爲測試期間需要注入的CompactDisc 還未被創建。接下來看一個完整的配置類:
@Configuration
public class JavaConfig {
/*
* 聲明一個簡單的bean
* */
@Bean
public ClassicalMusic classicalMusic(){
return new ClassicalMusic();
}
/*
* 聲明一個帶有依賴對象的bean
* 在這裏通過使用構造器的方式來注入,當spring調用該方法時,它會自動裝配一個CompactDisc導配置方法中
* */
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
@Bean註解告訴Spring這個方法將會返回一個對象,該對象將會註冊爲Spring應用上下文中的bean。默認情況下bean的ID和方法名一致。
上述代碼中生命了兩個bean,即classicalMusic和cdPlayer,前者是一個簡單的bean,而後者是一個帶有依賴對象的bean,當spring調用該方法時,它會自動裝配一個CompactDisc導配置方法中。
在這裏需要以下三個需要注意的地方:
- 在聲明帶有依賴對象的bean時,必須首先聲明被依賴的bean,即這裏的classicalMusic(ClassicalMusic 實現了CompactDisc )。
- 這裏生成的bean是單例的,若你再聲明一個實現了CompactDisc 接口的bean時,則會報錯。
- 帶有@Bean註解的方法可以採用任何必要的Java功能來產生bean實例(構造器和Setter方法是@Bean方法的簡單樣例)。
既然上述代碼已經配置好了,接下來接看一下如何使用已經配置好的bean,代碼如下:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
CompactDisc compactDisc = (ClassicalMusic)configApplicationContext.getBean("classicalMusic");
compactDisc.play();
}
}
//結果
播放古典音樂
上述代碼大致的執行原理如下:
通過創建AnnotationConfigApplicationContext 對象來配置後的Spring上下文,然後利用上下文來獲取對應的bean,繼而使用對應的方法。
三、XML裝配
1、藉助構造器注入初始化bean
兩種基本的配置方式:
- constructor-arg 元素
- 使用Spring3.0中引入的c-命名空間
兩者的區別在很大程度上就是是否繁瑣以及c-命名空間不能注入集合。
下面來看一下一課程-老師的例子做個簡單說明:
//課程類
public class Course {
String courseName;
double mark;
public Course(String courseName, double mark) {
this.courseName = courseName;
this.mark = mark;
}
}
//老師類
public class Teacher{
private Course course;
public Teacher(Course course){
this.course = course;
}
public void teach() {
System.out.println("我教的課程:"+course.courseName+" 績點爲:"+course.mark);
}
}
//配置文件
<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">
<!--Course bean-->
<bean id = "course" class="com.tyf.day4.Course">
<constructor-arg value="數學" />
<constructor-arg value="4.0" />
</bean>
<!--Teacher bean-->
<bean id = "teacher" class="com.tyf.day4.Teacher">
<constructor-arg ref="course" />
</bean>
</beans>
//測試類
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("com\\tyf\\day4\\ config.xml");
Teacher teacher = (Teacher) context.getBean("teacher");
teacher.teach();
}
}
//測試結果
我教的課程:數學 績點爲:4.0
上述代碼僅僅說明了一個使用元素進行構造器注入初始化bean的一個很簡單的方法,接下來再看一下如何使用c-命名空間來初始化bean(僅展示xml配置文件)。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--Course bean-->
<bean id = "course" class="com.tyf.day4.Course">
<constructor-arg value="數學" />
<constructor-arg value="4.0" />
</bean>
<!--Teacher bean-->
<bean id = "teacher" class="com.tyf.day4.Teacher" c:_0-ref = "course"/>
</beans>
可以看出使用c:-命名空間使得xml配置很簡潔,易於讀懂。_0說的是構造器中第一個參數,同理_1說的是構造器中第二個參數,一次類推。
那麼如果構造器參數列表中有集合類型,則不能使用c:-命名空間,只能使用元素。假如課程中有一個選課學生名字並且使用構造器來注入該屬性:
//課程類
public class Course {
String courseName;
double mark;
List<String> students;//選課學生姓名
public Course(String courseName, double mark,List<String> studenets) {
this.courseName = courseName;
this.mark = mark;
this.students = studenets;
}
}
//教師類
public class Teacher{
private Course course;
public Teacher(Course course){
this.course = course;
}
public void teach() {
System.out.println("我教的課程:"+course.courseName+" 績點爲:"+course.mark);
System.out.println("選課學生:");
for(String name:course.students){
System.out.println(name);
}
}
}
//測試類不變
//xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--Course bean-->
<bean id = "course" class="com.tyf.day4.Course">
<constructor-arg value="數學" />
<constructor-arg value="4.0" />
<constructor-arg>
<list>
<value>Yafeng</value>
<value>Xiaoshuang</value>
</list>
</constructor-arg>
</bean>
<!--Teacher bean-->
<bean id = "teacher" class="com.tyf.day4.Teacher" c:_0-ref = "course"/>
</beans>
//結果
我教的課程:數學 績點爲:4.0
選課學生:
Yafeng
Xiaoshuang
從上述代碼可以看出,list 是作爲Constructor的一個子元素來進行工作的,list中又有value子元素用來給List中的屬性賦值。
2、通過Setter方法注入屬性
將Teacher類改爲如下(其他不變):
//老師類
public class Teacher implements People {
private Course course;
public void setCourse(Course course){
this.course = course;
}
@Override
public void teach() {
System.out.println("我教的課程:"+course.courseName+" 績點爲:"+course.mark);
System.out.println("選課學生:");
for(String name:course.students){
System.out.println(name);
}
}
}
//XML文件中改變的部分
<!--Teacher bean-->
<bean id = "teacher" class="com.tyf.day4.Teacher">
<property name="course" ref="course" />
</bean>
這裏注入屬性的時候用的是property,其它基本和constructor基本一樣。而構造器注入有c-命名空間,而Sertter屬性注入方法也有一個p-命名空間,用法和c-命名空間一致。