前面已經介紹了 Spring IoC 的理念和設計,這一篇文章將介紹的是如何將本身開發的 Bean 裝配到 Spring IoC 容器中。html
大部分場景下,咱們都會使用 ApplicationContext 的具體實現類,由於對應的 Spring IoC 容器功能相對強大。java
而在 Spring 中提供了 3 種方法進行配置:git
在現實的工做中,這 3 種方式都會被用到,而且在學習和工做之中經常混合使用,因此這裏給出一些關於這 3 種優先級的建議:github
1.最優先:經過隱式 Bean 的發現機制和自動裝配的原則。
基於約定因爲配置的原則,這種方式應該是最優先的web
2.其次:Java 接口和類中配置實現配置
在沒有辦法使用自動裝配原則的狀況下應該優先考慮此類方法正則表達式
3.最後:XML 方式配置
在上述方法都沒法使用的狀況下,那麼也只能選擇 XML 配置的方式。spring
使用 XML 裝配 Bean 須要定義對應的 XML,這裏須要引入對應的 XML 模式(XSD)文件,這些文件會定義配置 Spring Bean 的一些元素,當咱們在 IDEA 中建立 XML 文件時,會有友好的提示:數組
一個簡單的 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"> </beans>
這就只是一個格式文件,引入了一個 beans 的定義,引入了 xsd 文件,它是一個根元素,這樣它所定義的元素將能夠定義對應的 Spring Bean微信
先來一個最簡單的裝配:
<bean id="c" class="pojo.Category"> <property name="name" value="測試" /> </bean>
簡單解釋一下:
id
屬性是 Spring 能找到當前 Bean 的一個依賴的編號,遵照 XML 語法的 ID 惟一性約束。必須以字母開頭,可使用字母、數字、連字符、下劃線、句號、冒號,不能以 /
開頭。id
屬性不是一個必需的屬性,name
屬性也能夠定義 bean 元素的名稱,能以逗號或空格隔開起多個別名,而且能夠使用不少的特殊字符,好比在 Spring 和 Spring MVC 的整合中,就得使用 name
屬性來定義 bean 的名稱,而且使用 /
開頭。id
屬性也能夠是 String 類型了,也就是說 id
屬性也可使用 /
開頭,而 bean 元素的 id 的惟一性由容器負責檢查。id
和 name
屬性都沒有聲明的話,那麼 Spring 將會採用 「全限定名#{number}」 的格式生成編號。 例如這裏,若是沒有聲明 「id="c"
」 的話,那麼 Spring 爲其生成的編號就是 「pojo.Category#0
」,當它第二次聲明沒有 id
屬性的 Bean 時,編號就是 「pojo.Category#1
」,以此類推。class
屬性顯然就是一個類的全限定名property
元素是定義類的屬性,其中的 name
屬性定義的是屬性的名稱,而 value
是它的值。這樣的定義很簡單,可是有時候須要注入一些自定義的類,好比以前飲品店的例子,JuickMaker 須要用戶提供原料信息才能完成 juice 的製做:
<!-- 配置 srouce 原料 --> <bean name="source" class="pojo.Source"> <property name="fruit" value="橙子"/> <property name="sugar" value="多糖"/> <property name="size" value="超大杯"/> </bean> <bean name="juickMaker" class="pojo.JuiceMaker"> <!-- 注入上面配置的id爲srouce的Srouce對象 --> <property name="source" ref="source"/> </bean>
這裏先定義了一個 name
爲 source 的 Bean,而後再製造器中經過 ref
屬性去引用對應的 Bean,而 source 正是以前定義的 Bean 的 name
,這樣就能夠相互引用了。
ref
屬性有些時候咱們須要裝配一些複雜的東西,好比 Set、Map、List、Array 和 Properties 等,爲此咱們在 Packge【pojo】下新建一個 ComplexAssembly 類:
package pojo; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; public class ComplexAssembly { private Long id; private List<String> list; private Map<String, String> map; private Properties properties; private Set<String> set; private String[] array; /* setter and getter */ }
這個 Bean 沒有任何的實際意義,知識爲了介紹如何裝配這些經常使用的集合類:
<bean id="complexAssembly" class="pojo.ComplexAssembly"> <!-- 裝配Long類型的id --> <property name="id" value="1"/> <!-- 裝配List類型的list --> <property name="list"> <list> <value>value-list-1</value> <value>value-list-2</value> <value>value-list-3</value> </list> </property> <!-- 裝配Map類型的map --> <property name="map"> <map> <entry key="key1" value="value-key-1"/> <entry key="key2" value="value-key-2"/> <entry key="key3" value="value-key-2"/> </map> </property> <!-- 裝配Properties類型的properties --> <property name="properties"> <props> <prop key="prop1">value-prop-1</prop> <prop key="prop2">value-prop-2</prop> <prop key="prop3">value-prop-3</prop> </props> </property> <!-- 裝配Set類型的set --> <property name="set"> <set> <value>value-set-1</value> <value>value-set-2</value> <value>value-set-3</value> </set> </property> <!-- 裝配String[]類型的array --> <property name="array"> <array> <value>value-array-1</value> <value>value-array-2</value> <value>value-array-3</value> </array> </property> </bean>
<list>
元素進行裝配,而後經過多個 <value>
元素設值<map>
元素進行裝配,而後經過多個 <entry>
元素設值,只是 entry
包含一個鍵值對(key-value)的設置<properties>
元素進行裝配,經過多個 <property>
元素設值,只是 properties
元素有一個必填屬性 key
,而後能夠設置值<set>
元素進行裝配,而後經過多個 <value>
元素設值<array>
設置值,而後經過多個 <value>
元素設值。上面看到了對簡單 String 類型的各個集合的裝載,可是有些時候可能須要更爲複雜的裝載,好比一個 List 能夠是一個系列類的對象,爲此須要定義注入的相關信息,其實跟上面的配置沒什麼兩樣,只不過加入了 ref
這一個屬性而已:
<list>
元素定義注入,使用多個 <ref>
元素的 Bean 屬性去引用以前定義好的 Bean<property name="list"> <list> <ref bean="bean1"/> <ref bean="bean2"/> </list> </property>
<map>
元素定義注入,使用多個 <entry>
元素的 key-ref
屬性去引用以前定義好的 Bean 做爲鍵,而用 value-ref
屬性引用以前定義好的 Bean 做爲值<property name="map"> <map> <entry key-ref="keyBean" value-ref="valueBean"/> </map> </property>
<set>
元素定義注入,使用多個 <ref>
元素的 bean
去引用以前定義好的 Bean<property name="set"> <set> <ref bean="bean"/> </set> </property>
除了上述的配置以外, Spring 還提供了對應的命名空間的定義,只是在使用命名空間的時候要先引入對應的命名空間和 XML 模式(XSD)文件。
c-命名空間是在 Spring 3.0 中引入的,它是在 XML 中更爲簡潔地描述構造器參數的方式,要使用它的話,必需要在 XML 的頂部聲明其模式:
如今假設咱們如今有這麼一個類:
package pojo; public class Student { int id; String name; public Student(int id, String name) { this.id = id; this.name = name; } // setter and getter }
在 c-命名空間和模式聲明以後,咱們就可使用它來聲明構造器參數了:
<!-- 引入 c-命名空間以前 --> <bean name="student1" class="pojo.Student"> <constructor-arg name="id" value="1" /> <constructor-arg name="name" value="學生1"/> </bean> <!-- 引入 c-命名空間以後 --> <bean name="student2" class="pojo.Student" c:id="2" c:name="學生2"/>
c-命名空間屬性名以 「c:
」 開頭,也就是命名空間的前綴。接下來就是要裝配的構造器參數名,在此以後若是須要注入對象的話則要跟上 -ref
(如c:card-ref="idCard1"
,則對 card 這個構造器參數注入以前配置的名爲 idCard1 的 bean)
很顯然,使用 c-命名空間屬性要比使用 <constructor-arg>
元素精簡,而且會直接引用構造器之中參數的名稱,這有利於咱們使用的安全性。
咱們有另一種替代方式:
<bean name="student2" class="pojo.Student" c:_0="3" c:_1="學生3"/>
咱們將參數的名稱替換成了 「0」 和 「1」 ,也就是參數的索引。由於在 XML 中不容許數字做爲屬性的第一個字符,所以必需要添加一個下劃線來做爲前綴。
c-命名空間經過構造器注入的方式來配置 bean,p-命名空間則是用setter的注入方式來配置 bean ,一樣的,咱們須要引入聲明:
而後咱們就能夠經過 p-命名空間來設置屬性:
<!-- 引入p-命名空間以前 --> <bean name="student1" class="pojo.Student"> <property name="id" value="1" /> <property name="name" value="學生1"/> </bean> <!-- 引入p-命名空間以後 --> <bean name="student2" class="pojo.Student" p:id="2" p:name="學生2"/>
咱們須要先刪掉 Student 類中的構造函數,否則 XML 約束會提示咱們配置 <constructor-arg>
元素。
一樣的,若是屬性須要注入其餘 Bean 的話也能夠在後面跟上 -ref
:
<bean name="student2" class="pojo.Student" p:id="2" p:name="學生2" p:cdCard-ref="cdCard1"/>
工具類的命名空間,能夠簡化集合類元素的配置,一樣的咱們須要引入其聲明(無需擔憂怎麼聲明的問題,IDEA會有很友好的提示):
咱們來看看引入先後的變化:
<!-- 引入util-命名空間以前 --> <property name="list"> <list> <ref bean="bean1"/> <ref bean="bean2"/> </list> </property> <!-- 引入util-命名空間以後 --> <util:list id="list"> <ref bean="bean1"/> <ref bean="bean2"/> </util:list>
<util:list>
只是 util-命名空間中的多個元素之一,下表提供了 util-命名空間提供的全部元素:
元素 | 描述 |
---|---|
<util:constant> |
引用某個類型的 public static 域,並將其暴露爲 bean |
<util:list> |
建立一個 java.util.List 類型的 bean,其中包含值或引用 |
<util:map> |
建立一個 java.util.map 類型的 bean,其中包含值或引用 |
<util:properties> |
建立一個 java.util.Properties 類型的 bean |
<util:property-path> |
引用一個 bean 的屬性(或內嵌屬性),並將其暴露爲 bean |
<util:set> |
建立一個 java.util.Set 類型的 bean,其中包含值或引用 |
在實際開發中,隨着應用程序規模的增長,系統中 <bean>
元素配置的數量也會大大增長,致使 applicationContext.xml 配置文件變得很是臃腫難以維護。
<import>
元素引入其餘配置文件1.在【src】文件下新建一個 bean.xml 文件,寫好基礎的約束,把 applicationContext.xml 文件中配置的 <bean>
元素複製進去
2.在 applicationContext.xml 文件中寫入:
<import resource="bean.xml" />
3.運行測試代碼,仍然能正確獲取到 bean:
上面,咱們已經瞭解瞭如何使用 XML 的方式去裝配 Bean,可是更多的時候已經再也不推薦使用 XML 的方式去裝配 Bean,更多的時候回考慮使用註解(annotation) 的方式去裝配 Bean。
在 Spring 中,它提供了兩種方式來讓 Spring IoC 容器發現 bean:
咱們把以前建立的 Student 類改一下:
package pojo; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component(value = "student1") public class Student { @Value("1") int id; @Value("student_name_1") String name; // getter and setter }
解釋一下:
value
屬性表明這個類在 Spring 中的 id
,這就至關於在 XML 中定義的 Bean 的 id:<bean id="student1" class="pojo.Student" />
,也能夠簡寫成 @Component("student1")
,甚至直接寫成 @Component
,對於不寫的,Spring IoC 容器就默認以類名來命名做爲 id
,只不過首字母小寫,配置到容器中。value
屬性是同樣的。這樣咱們就聲明好了咱們要建立的一個 Bean,就像在 XML 中寫下了這樣一句話:
<bean name="student1" class="pojo.Student"> <property name="id" value="1" /> <property name="name" value="student_name_1"/> </bean>
可是如今咱們聲明瞭這個類,並不能進行任何的測試,由於 Spring IoC 並不知道這個 Bean 的存在,這個時候咱們可使用一個 StudentConfig 類去告訴 Spring IoC :
package pojo; import org.springframework.context.annotation.ComponentScan; @ComponentScan public class StudentConfig { }
這個類十分簡單,沒有任何邏輯,可是須要說明兩點:
@Component
註解的 POJO。這樣一來,咱們就能夠經過 Spring 定義好的 Spring IoC 容器的實現類——AnnotationConfigApplicationContext 去生成 IoC 容器了:
ApplicationContext context = new AnnotationConfigApplicationContext(StudentConfig.class); Student student = (Student) context.getBean("student1", Student.class); student.printInformation();
這裏能夠看到使用了 AnnotationConfigApplicationContext 類去初始化 Spring IoC 容器,它的配置項是 StudentConfig 類,這樣 Spring IoC 就會根據註解的配置去解析對應的資源,來生成 IoC 容器了。
@ComponentScan
註解,它只是掃描所在包的 Java 類,可是更多的時候咱們但願的是能夠掃描咱們指定的類@Value
註解並不能注入對象@Component
註解存在着兩個配置項:
咱們來試着重構以前寫的 StudentConfig 類來驗證上面兩個配置項:
package pojo; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages = "pojo") public class StudentConfig { } // —————————————————— 【 宇宙超級無敵分割線】—————————————————— package pojo; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackageClasses = pojo.Student.class) public class StudentConfig { }
驗證都能經過,bingo!
上面提到的兩個弊端之一就是沒有辦法注入對象,經過自動裝配咱們將解決這個問題。
所謂自動裝配技術是一種由 Spring 本身發現對應的 Bean,自動完成裝配工做的方式,它會應用到一個十分經常使用的註解 @Autowired
上,這個時候 Spring 會根據類型去尋找定義的 Bean 而後將其注入,聽起來很神奇,讓咱們實際來看一看:
1.先在 Package【service】下建立一個 StudentService 接口:
package service; public interface StudentService { public void printStudentInfo(); }
使用接口是 Spring 推薦的方式,這樣能夠更爲靈活,能夠將定義和實現分離
2.爲上面的接口建立一個 StudentServiceImp 實現類:
package service; import org.springframework.beans.factory.annotation.Autowired; import pojo.Student; @Component("studentService") public class StudentServiceImp implements StudentService { @Autowired private Student student = null; // getter and setter public void printStudentInfo() { System.out.println("學生的 id 爲:" + student.getName()); System.out.println("學生的 name 爲:" + student.getName()); } }
該實現類實現了接口的 printStudentInfo() 方法,打印出成員對象 student 的相關信息,這裏的 @Autowired
註解,表示在 Spring IoC 定位全部的 Bean 後,這個字段須要按類型注入,這樣 IoC 容器就會尋找資源,而後將其注入。
3.編寫測試類:
// 第一步:修改 StudentConfig 類,告訴 Spring IoC 在哪裏去掃描它: package pojo; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages = {"pojo", "service"}) public class StudentConfig { } // 或者也能夠在 XML 文件中聲明去哪裏作掃描 <context:component-scan base-package="pojo" /> <context:component-scan base-package="service" /> // 第二步:編寫測試類: package test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import pojo.StudentConfig; import service.StudentService; import service.StudentServiceImp; public class TestSpring { public static void main(String[] args) { // 經過註解的方式初始化 Spring IoC 容器 ApplicationContext context = new AnnotationConfigApplicationContext(StudentConfig.class); StudentService studentService = context.getBean("studentService", StudentServiceImp.class); studentService.printStudentInfo(); } }
運行代碼:
@Autowired
註解表示在 Spring IoC 定位全部的 Bean 後,再根據類型尋找資源,而後將其注入。required
來改變,好比 @Autowired(required = false)
@Autowired
註解不只僅能配置在屬性之上,還容許方法配置,常見的 Bean 的 setter 方法也可使用它來完成注入,總之一切須要 Spring IoC 去尋找 Bean 資源的地方均可以用到,例如:
/* 包名和import */ public class JuiceMaker { ...... @Autowired public void setSource(Source source) { this.source = source; } }
在大部分的配置中都推薦使用這樣的自動注入來完成,這是 Spring IoC 幫助咱們自動裝配完成的,這樣使得配置大幅度減小,知足約定優於配置的原則,加強程序的健壯性。
在上面的例子中咱們使用 @Autowired
註解來自動注入一個 Source 類型的 Bean 資源,但若是咱們如今有兩個 Srouce 類型的資源,Spring IoC 就會不知所措,不知道究竟該引入哪個 Bean:
<bean name="source1" class="pojo.Source"> <property name="fruit" value="橙子"/> <property name="sugar" value="多糖"/> <property name="size" value="超大杯"/> </bean> <bean name="source2" class="pojo.Source"> <property name="fruit" value="橙子"/> <property name="sugar" value="少糖"/> <property name="size" value="小杯"/> </bean>
咱們能夠會想到 Spring IoC 最底層的容器接口——BeanFactory 的定義,它存在一個按照類型獲取 Bean 的方法,顯然經過 Source.class 做爲參數沒法判斷使用哪一個類實例進行返回,這就是自動裝配的歧義性。
爲了消除歧義性,Spring 提供了兩個註解:
/* 包名和import */ public class JuiceMaker { ...... @Autowired @Qualifier("source1") public void setSource(Source source) { this.source = source; } }
@Component
註解來裝配 Bean ,而且只能註解在類上,當你須要引用第三方包的(jar 文件),並且每每並無這些包的源碼,這時候將沒法爲這些包的類加入 @Component
註解,讓它們變成開發環境中的 Bean 資源。@Component
註解,但這樣很 low@Bean
註解,註解到方法之上,使其成爲 Spring 中返回對象爲 Spring 的 Bean 資源。咱們在 Package【pojo】 下新建一個用來測試 @Bean
註解的類:
package pojo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BeanTester { @Bean(name = "testBean") public String test() { String str = "測試@Bean註解"; return str; } }
@Configuration
註解至關於 XML 文件的根元素,必需要,有了才能解析其中的 @Bean
註解而後咱們在測試類中編寫代碼,從 Spring IoC 容器中獲取到這個 Bean :
// 在 pojo 包下掃描 ApplicationContext context = new AnnotationConfigApplicationContext("pojo"); // 由於這裏獲取到的 Bean 就是 String 類型因此直接輸出 System.out.println(context.getBean("testBean"));
@Bean
的配置項中包含 4 個配置項:
使用 @Bean
註解的好處就是可以動態獲取一個 Bean 對象,可以根據環境不一樣獲得不一樣的 Bean 對象。或者說將 Spring 和其餘組件分離(其餘組件不依賴 Spring,可是又想 Spring 管理生成的 Bean)
在默認的狀況下,Spring IoC 容器只會對一個 Bean 建立一個實例,但有時候,咱們但願可以經過 Spring IoC 容器獲取多個實例,咱們能夠經過 @Scope
註解或者 <bean>
元素中的 scope
屬性來設置,例如:
// XML 中設置做用域 <bean id="" class="" scope="prototype" /> // 使用註解設置做用域 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Spring 提供了 5 種做用域,它會根據狀況來決定是否生成新的對象:
做用域類別 | 描述 |
---|---|
singleton(單例) | 在Spring IoC容器中僅存在一個Bean實例 (默認的scope) |
prototype(多例) | 每次從容器中調用Bean時,都返回一個新的實例,即每次調用getBean()時 ,至關於執行new XxxBean():不會在容器啓動時建立對象 |
request(請求) | 用於web開發,將Bean放入request範圍 ,request.setAttribute("xxx") , 在同一個request 得到同一個Bean |
session(會話) | 用於web開發,將Bean 放入Session範圍,在同一個Session 得到同一個Bean |
globalSession(全局會話) | 通常用於 Porlet 應用環境 , 分佈式系統存在全局 session 概念(單點登陸),若是不是 porlet 環境,globalSession 等同於 Session |
在開發中主要使用 scope="singleton"
、scope="prototype"
,對於MVC中的Action使用prototype類型,其餘使用singleton,Spring容器會管理 Action 對象的建立,此時把 Action 的做用域設置爲 prototype.
擴展閱讀:@Profile 註解 、 條件化裝配 Bean
Spring 還提供了更靈活的注入方式,那就是 Spring 表達式,實際上 Spring EL 遠比以上注入方式都要強大,它擁有不少功能:
咱們來看一個簡單的使用 Spring 表達式的例子:
package pojo; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component("elBean") public class ElBean { // 經過 beanName 獲取 bean,而後注入 @Value("#{role}") private Role role; // 獲取 bean 的屬性 id @Value("#{role.id}") private Long id; // 調用 bean 的 getNote 方法 @Value("#{role.getNote().toString()}") private String note; /* getter and setter */ }
與屬性文件中讀取使用的 「$
」 不一樣,在 Spring EL 中則使用 「#
」
擴展閱讀: Spring 表達式語言
歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz_javaweb 分享本身的Java Web學習之路以及各類Java學習資料