精通Spring+4.x++企業開發與實踐之IoC容器中裝配Bean
Spring配置概述
Spring屬性注入
JavaBean關於屬性命名的特殊規範
Spring配置檔案中的<property></property>元素所指定的屬性名和Bean實現類的Setter方法滿足Sun JavaBean的屬性命名規範:xxx的屬性對應的setXxx()方法。 注意:需要滿足:"變數的前兩個字母要全大寫,要麼全小寫"。否則會出現具有誤導性的錯誤。
建構函式注入
1.需要提供一個有參建構函式,可以按型別匹配注入,按所以匹配注入,聯合型別和所以匹配注入,通過自身型別反射匹配注入 2.建構函式注入的問題,可能出現迴圈依賴的問題,導致程式無法啟動,只要改成屬性注入即可解決這個問題。 例子: ConstructorInject.java
public class ConstructorInject {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
Person person = (Person) ctx.getBean("p2");
System.out.println(person.toString());
}
}
iocdemo.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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"> <bean id="person" class="com.flexible.beans.Person"> <property name="userName"><value>zhangsan</value></property> <property name="userAge"><value>20</value></property> </bean> <bean id="p2" class="com.flexible.beans.Person"> <constructor-arg type="java.lang.String"><value>zhangsan</value></constructor-arg> <constructor-arg type="java.lang.Integer"><value>28</value></constructor-arg> </bean> </beans>
Person.java
public class Person { public String userName; public Integer userAge; public Person() { } public Person(String userName, Integer userAge) { this.userName = userName; this.userAge = userAge; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Integer getUserAge() { return userAge; } public void setUserAge(Integer userAge) { this.userAge = userAge; } [@Override](https://my.oschina.net/u/1162528) public String toString() { return "Person{" + "userName='" + userName + '\'' + ", userAge=" + userAge + '}'; } }
工廠方法注入
1.非靜態的工廠
xml配置如下:
<bean id="factory" class="com.flexible.factorymethod.NonStaticFactoryMethod"></bean>
<bean id="p3" factory-bean="factory" factory-method="createPerson"></bean>
2.工廠程式碼如下:
/**
* 使用非靜態的工廠
*/
public class NonStaticFactoryMethod {
/**
* 工廠的建立方法
* [@return](https://my.oschina.net/u/556800)
*/
public Person createPerson(){
Person p = new Person();
p.setUserName("lisi");
p.setUserAge(20);
return p;
}
}
測試方法如下:
//非靜態的方式
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
Person person = (Person) ctx.getBean("p3");
System.out.println(person.toString());
靜態方法: xml配置如下: <bean id="p4" class="com.flexible.factorymethod.StaticMethod" factory-method="createPerson"></bean> 工廠類如下: public class StaticMethod {
/**
* 工廠的建立方法
*
* [@return](https://my.oschina.net/u/556800)
*/
public static Person createPerson() {
Person p = new Person();
p.setUserName("lisi");
p.setUserAge(20);
return p;
}
}
測試方法: //靜態的方式 Person person2 = (Person) ctx.getBean("p3"); System.out.println(person2.toString());
注入引數
Spring不但可以將String,int等字面值注入Bean中,還可以將集合,Map等型別的資料注入Bean中,還可以注入配置檔案的其他Bean。
字面值
字面值標識可以使用字串表示的值,這些值可以通過<value></value>元素注入。xml中包含5個特殊字元,分別是&,<,>,`,'。如果包含這些這些特殊字元,就需要使用<![CDATA[]]特殊標籤。
注意: 一般情況下,XML解析器會忽略元素標籤內部字串的前後空格,但是Spring卻不會忽略元素標籤內部字串的字串的前後空格,如果通過以下配置為屬性注入值<property name="xx"><value> xxx xx</value></property>,那麼前後,中間的空格將會被一起賦值給xx屬性。
引入其他的Bean
xml檔案如下:
<bean id="p5" class="com.flexible.beans.Person">
<property name="userName" value="wangwu"></property>
<property name="userAge" value="20"></property>
</bean>
<bean id="h1" class="com.flexible.beans.House">
<property name="length" value="100"></property>
<property name="width" value="80"></property>
<property name="height" value="60"></property>
<property name="person"><ref bean="p5"></ref></property>
</bean>
<ref></ref>元素可以通過以下3個屬性引用容器中的其他Bean。 1.bean:通過該屬性可以引用同一容器或者父容器中的Bean,這是最常見的形式 2.local:通過該屬性只能引用同一配置檔案中定義的Bean,他可以利用XML解析器自動檢驗引用的合法性,以便開發者再編寫配置時能夠幾時發現並糾正配置的錯誤。 3.parent:引用父容器中的Bean,如<ref parent="car">的配置說明car的Bean是父容器中的Bean.
Bean類 public class House {
private Double length;
private Double width;
private Double height;
private Person person;
public House() {
}
public Double getLength() {
return length;
}
public void setLength(Double length) {
this.length = length;
}
public Double getWidth() {
return width;
}
public void setWidth(Double width) {
this.width = width;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
[@Override](https://my.oschina.net/u/1162528)
public String toString() {
return "House{" +
"length=" + length +
", width=" + width +
", height=" + height +
", person=" + person +
'}';
}
}
測試例子:
public class BeanInjectDemo {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
House house = (House) ctx.getBean("h1");
System.out.println(house.toString());
}
}
內部Bean
內部Bean和Java的匿名內部類相似,既沒有名字,也不能被其他的Bean引用,只能在宣告處為外部Bean提供例項注入。 內部Bean幾時提供了id,name,scope屬性,也會被忽略。scope預設prototype型別。
<bean id="h1" class="com.flexible.beans.House">
<property name="length" value="100"></property>
<property name="width" value="80"></property>
<property name="height" value="60"></property>
<property name="person">
<bean id="p5" class="com.flexible.beans.Person">
<property name="userName" value="wangwu"></property>
<property name="userAge" value="20"></property>
</bean>
</property>
</bean>
null值
如果希望給Bean的某個屬性賦值一個null,如果使用<property name="xx"><value></value></property>這樣是達不到預期的效果的,但是可以使用<property name="xx"><null/></property>
級聯屬性
javaBean程式碼
public class Car {
Person person = new Person();
public Car() {
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
[@Override](https://my.oschina.net/u/1162528)
public String toString() {
return "Car{" +
"person=" + person +
'}';
}
}
xml配置如下:
<bean id="car" class="com.flexible.beans.Car">
<property name="person.userName" value="zhaoliu"></property>
<property name="person.userAge" value="20"></property>
</bean>
測試程式碼:
public class CascadeDemo {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
Car car = (Car) ctx.getBean("car");
System.out.println(car.toString());
}
}
集合屬性注入
Favorite.java
public class Favorite {
/**
* List集合
*/
private List<String> favoties = new ArrayList<>();
/**
* map集合
*/
private Map<String,String> map = new HashMap<>();
/**
*property,看作是特殊的map
*/
private Properties properties = new Properties();
public Favorite() {
}
public List<String> getFavoties() {
return favoties;
}
public void setFavoties(List<String> favoties) {
this.favoties = favoties;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "Favorite{" +
"favoties=" + favoties +
", map=" + map +
", properties=" + properties +
'}';
}
}
xml配置如下:
<bean id="favorities" class="com.flexible.beans.Favorite">
<property name="favoties">
<list>
<value>游泳</value>
<value>足球</value>
<value>籃球</value>
<value>棒球</value>
</list>
</property>
<property name="map">
<map>
<entry>
<key><value>k1</value></key>
<value>運動</value>
</entry>
<entry>
<key><value>k2</value></key>
<value>上班</value>
</entry>
<entry>
<key><value>k3</value></key>
<value>約會</value>
</entry>
</map>
</property>
<property name="properties">
<props>
<prop key="k1">value1</prop>
<prop key="k2">value2</prop>
<prop key="k3">value3</prop>
</props>
</property>
</bean>
<!--集合的合併-->
<bean id="childFavorities" parent="favorities">
<property name="favoties">
<list merge="true">
<value>賽車</value>
</list>
</property>
<property name="map">
<map merge="true">
<entry>
<key><value>k12</value></key>
<value>運動2</value>
</entry>
<entry>
<key><value>k22</value></key>
<value>上班2</value>
</entry>
<entry>
<key><value>k32</value></key>
<value>約會2</value>
</entry>
</map>
</property>
<property name="properties">
<props merge="true">
<prop key="k12">value12</prop>
<prop key="k22">value22</prop>
<prop key="k32">value32</prop>
</props>
</property>
</bean>
測試程式碼:
public class CollectionAttributeInject {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
Favorite favorite = (Favorite) ctx.getBean("favorities");
System.out.println(favorite.toString());
Favorite childFavorities = (Favorite) ctx.getBean("childFavorities");
System.out.println(childFavorities.toString());
}
}
方法替換
Spring支援使用一個Bean的方法取替換另一個Bean的方法,替換別的Bean的Bean需要實現org.springframework.beans.factory.support.MethodReplacer介面,Spring利用該介面去替換目標Bean的方法.
被替換的類的方法:
public class Boss {
public House getHouse(){
House house = new House();
house.setHeight(1.0);
house.setLength(2.0);
house.setLength(3.0);
house.setWidth(4.0);
return house;
}
}
替換的類需要實現org.springframework.beans.factory.support.MethodReplacer介面。
public class Boss2 implements MethodReplacer{
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
House house = new House();
house.setHeight(1.01);
house.setLength(2.01);
house.setLength(3.01);
house.setWidth(4.01);
return house;
}
}
xml配置檔案如下:
<bean id="boss1" class="com.flexible.inject.methodreplace.Boss">
<replaced-method name="getHouse" replacer="boss2"></replaced-method>
</bean>
<bean id="boss2" class="com.flexible.inject.methodreplace.Boss2"></bean>
<bean></bean>之間的關係
繼承
父<bean></bean>主要功能是簡化子<bean></bean>的配置,所以一般宣告未abstract="true",表示這個bean不例項化為一個對應的Bean。
例子:
xml配置如下:
<!--這裡將這個bean宣告為abstract,表明它不會被例項化為bean-->
<bean id="parent" class="com.flexible.beans.Parent" abstract="true">
<property name="height" value="1.70"></property>
<property name="weight" value="130"></property>
</bean>
<bean id="child" parent="parent">
<property name="height" value="1.80"></property>
</bean>
Parent.java
public class Parent {
private String height;
private String weight;
public Parent() {
}
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Parent{" +
"height='" + height + '\'' +
", weight='" + weight + '\'' +
'}';
}
}
測試程式碼:
public class RelationBean {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
Parent parent = (Parent) ctx.getBean("child");
System.out.println(parent.toString());
}
}
依賴
在配置中如果當前的Bean的初始化需要前置條件(即這些前置條件都得初始化完全了才能例項化這個Bean)滿足的時候,這個時候就需要使用到依賴 depends-on=""
引用
如果需要引用到另一個bean的id,需要使用<idref bean="xx"></idref>的方式使用,如果在同一個配置檔案可以使用<idref local="xx"></idref> 這樣在開發的時期就可以辨別出錯誤。
整合多哥配置檔案。
匯入其他配置檔案可以是import,例如:
<import resource="classpath:iocdemo2.xml"></import>
Bean作用域
低版本的Spring值支援兩種作用域,所以才有singleton="true|false"的配置方式。Spring為了向後相容,依然支援這種配置方式。推薦使用的配置方式:scope="<作用域型別>"
除了以上5種Bean作用域之外,Spring支援自定義Bean的作用域。通過org.springframework.beans.factory.config.Scope介面定義作用域,再通過org.springframework.beans.factory.config.CustomScopeConfigurer這個BeanFactoryPostprocessor註冊自定義的Bean作用域。
singleton
Spring將Bean預設的作用域都是singleton,並且將例項化的Bean快取再ApplicationContext的容器中,如果不希望例項化以開始就進行,可以使用lazy-init="true"實現懶載入,但是某些特殊的情況依然會提前載入。
prototype
scope="prototype"非單例作用域。它會將bean交給呼叫者,由呼叫者來管理bean的生命週期。
與Web應用環境相關的Bean作用域
<!--從類路徑下載入Spring配置檔案,classpath關鍵字特指類路徑下載入-->
<!--這裡可以多個配置檔案,建議採用逗號隔開的方式-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:flexible-context.xml</param-value>
</context-param>
<!--負責啟動Spring容器的監聽器,他將引用上面的上下檔案引數獲取Spring配置檔案的地址-->
<!--該監聽器在web容器啟動時自動執行,它會根據contextConfigLocation
Web容器引數獲取Spring配置檔案,並且啟動Spring容器。-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
正常的情況我們只需要我們只需要通過ContextLoadListoner(或者ContextLoadServlet)將Web容器與Spring容器進行整合,但是如果需要使用request,session,globalSession這三個WEB相關的作用域,就需要也配置requestContextListiner(實現了ServletRequestListener監聽器介面),ContextLoadListener只是負責監聽容器的啟動和關閉。如果Spring需要request,session,globalSession的支援就需要獲取Web容器的HTTP請求事件,此時RequestContestListener就可以實現這個需求。
配置如下:
<!--需要三大web容器作用域時需要這個配置-->
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
request:請求結束後,例項就會銷燬。 session:橫跨整個HTTPSession ,Session的所有HTTP請求共享同一個例項,HTTPSession結束後,例項銷燬。 globalSession作用域:globalSession作用域累死與session作用域,只能再Portlet的Web應用種使用。Portlet規範定義了全域性Session的概念,它被組成的Portlet Web應用所有子Portlet共享,如果不在Portlet Web應用的環境下,那麼globalSession==session作用域。
作用域依賴問題
需要啟用<aop:scoped-proxy></aop:scoped-proxy>
FactoryBean
一般情況下,Spring通過反射機制利用<bean></bean>的class屬性指定實現類例項化Bean。在某些情況下,例項化Bean的過程比較複雜,如果按照傳統的方式,需要提供大量的配置資訊。配置方式收到了限制。Spring為此提供了一個org.springframework.beans.factory.FactoryBean工廠類結論,使用者可可以通過實現改工廠類介面定製例項化Bean的邏輯。 例子: HouseFactoryBean.java
public class HouseFactoryBean implements FactoryBean<House> {
Person person;
public Person getPerson() {
return person;
}
//接受引數
public void setPerson(Person person) {
this.person = person;
}
//例項化bean
@Override
public House getObject() throws Exception {
House house = new House();
house.setLength(1.0);
house.setWidth(2.0);
house.setHeight(3.0);
house.setPerson(person);
return house;
}
//返回Hourse型別
@Override
public Class<House> getObjectType() {
return House.class;
}
//比鬧事通FactoryBean返回的Bean是singleton
@Override
public boolean isSingleton() {
return false;
}
@Override
public String toString() {
return "HouseFactoryBean{" +
"person=" + person +
'}';
}
}
xml配置檔案
<bean id="hoursebean" class="com.flexible.beans.HouseFactoryBean">
<property name="person">
<bean id="personp" class="com.flexible.beans.Person">
<property name="userAge" value="20"></property>
<property name="userName" value="zhouqi"></property>
</bean>
</property>
</bean>
測試程式碼:
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
House house = (House) ctx.getBean("hoursebean");
System.out.println(house.toString());
//如果想拿到HouseFactoryBean例項,就需要 &hoursebean
HouseFactoryBean houseFactoryBean = (HouseFactoryBean) ctx.getBean("&hoursebean");
System.out.println(houseFactoryBean.toString());
House object = houseFactoryBean.getObject();
System.out.println(object.toString());
}
執行結果:
House{length=1.0, width=2.0, height=3.0, person=Person{userName='zhouqi', userAge=20}}
HouseFactoryBean{person=Person{userName='zhouqi', userAge=20}}
House{length=1.0, width=2.0, height=3.0, person=Person{userName='zhouqi', userAge=20}}
基於註解的配置
使用註解定義Bean
Spring提供了四個定義Bean的註解,分別是:
[email protected]:用於對DAO實現類進行標註
[email protected]:用於對Service實現類進行標註。
[email protected]:用於對Controller實現類進行標註
[email protected]可以代替上面任何一個,只不過上面這些標註有特殊的用途,能一目瞭然的看出來這個bean的用途。
掃描註解定義的Bean
Spring提供了一個Context名稱空間,它提供了通過掃面類包以應用註解定義Bean的方式。 例如:<context:component-scan base-package="com.flexible"></context:component-scan>
如果希望掃描的是特定的類而非基礎包下的類,可以使用resource-pattern熟悉過濾
<context:component-scan base-package="com.flexible" resource-pattern="xx/*.class"></context:component-scan>
如果還需要更加具體的過濾,如實現了xx介面的,
例如:
<context:component-scan base-package="com.flexible">
<context:include-filter type="aspectj" expression="com.flexible..*Controller+"></context:include-filter>
</context:component-scan>
自動裝配
使用 @Autoired
@Qualifier知道注入的Bean的名稱。
對標準註解的@Resource(需要提供一個Bean的名字)和@Inject的支援。
Bean作用範圍及生命過程的方法。
@Scope("prototype")
Bean作用範圍以及生命過程方法
在使用<bean></bean>進行配置時,可以通過init-method和destroy-method屬性指定Bean的初始化以及容器銷燬前執行的方法。Spring從2.5開始支援JSR-250中定義的@PostConstruct和@PreDestroy註解,在Spring中他們相當於init-method和destroy-method屬性的功能,不過在使用註解時,可以在一個Bean中定義多個@PostConstruct和@PreDestroy方法。
例子:
LifeCyleBea.java
@Component
public class LifeCyleBean {
private String info;
@PostConstruct
public void initMethod(){
System.out.println(".....this is initMethod....");
}
@PreDestroy
public void destroyMethod(){
System.out.println(".....this is destroyMethod....");
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
xml配置檔案開啟包掃描:
<context:component-scan base-package="com.flexible">
</context:component-scan>
測試程式碼:
public class LifeCycleDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:iocdemo.xml");
((ClassPathXmlApplicationContext)context).destroy();
}
}
執行結果: .....this is initMethod....
.....this is destroyMethod....
基於Java類的配置
使用Java類提供的Bean定義資訊
JavaConfig是Spring的一個子專案,旨在通過Java類的方式提供Bean的定義資訊,在Spring2.0開始釋出。普通的POJO只有標註@Configuration註解,就可以為Spring容器提供Bean定義的資訊。每個標註了@Bean得類方法都相當於提供了一個Bean的定義資訊。通過程式碼進行配置要靈活點,但是配置檔案配置要簡潔些。
AppConfig.java
//將一個POJO標註為定義Bean的配置類
@Configuration
public class AppConfig {
//可以給bean一個名字,還可以配置作用域
//@Bean(name = "car")
@Bean
public Car getCar(){
Car car = new Car();
car.setPerson(getPerson());
return car;
}
@Bean
public Person getPerson(){
Person person = new Person();
person.setUserName("王八");
person.setUserAge(28);
return person;
}
}
測試程式碼:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Car car = (Car) context.getBean(Car.class);
System.out.println(car.toString());
使用基於Java類的配置資訊啟動Spring容器
1.直接通過@Configuration類啟動Spring容器
Spring提供了一個AnnotationConfigApplicationContext類,它能夠直接通過標註¥Configuration的Java啟動Spring容器。 例如:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
AnnotationConfigApplicationContext還支援通過編碼的方式載入多個@Configuration配置類,然後重新整理容器應用的配置類。 例如:
ApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.register(AppConfig2.class);
context.refresh()
通過XML配置檔案引用@Configuration的配置
標註了@Configuration的配置類與標註了@Component的類一樣也是一個Bean,它可以被Spring的context:compent-scan</context:compent-scan>掃描到相應的配置類即可。 例如:
<context:component-scan base-package="com.flexible" resource-pattern="AppConfig.class">
通過@Configuration配置類引用XML配置資訊
在@Configuration配置類中可以通過@ImportResource引入XML配置檔案,在RefXmlConfig配置類中即可直接通過@Autowired引用XML配置檔案定義的Bean. RefXmlConfig.java
@Configuration
@ImportResource("classpath:iocdemo2.xml")
public class RefXmlConfig {
@Bean
@Autowired
public PenBox getPenBox(PenBox penBox){
return penBox;
}
}
PenBox.java
public class PenBox {
private String markName;
private String description;
public PenBox() {
}
public String getMarkName() {
return markName;
}
public void setMarkName(String markName) {
this.markName = markName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "PenBox{" +
"markName='" + markName + '\'' +
", description='" + description + '\'' +
'}';
}
}
測試程式碼:
public class RefXmlDemo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(RefXmlConfig.class);
PenBox penBox = (PenBox) context.getBean("penBox");
System.out.println(penBox.toString());
}
}
執行結果:
PenBox{markName='xxx', description='description'}