Spring入門詳解【基礎掃盲】
Spring基礎介紹
什麼是Spring、Strust、Hibernate
struts 是 web 框架(jsp/action/actionfrom)
hibernate 是 orm框架,處於持久層.
spring 是容器框架,用於配置bean,並維護bean之間關係的框架
Spring中重要概念:IOC / DI
IOC是什麼?
ioc(inverse of controll ) 控制反轉: 所謂控制反轉就是把建立物件(bean),和維護物件(bean)的關係的權利從程式中轉移到spring的容器(applicationContext.xml),而程式本身不再維護.
DI是什麼?
di(dependency injection) 依賴注入: 實際上di和ioc是同一個概念,spring設計者認為di更準確表示spring核心技術。
依賴注入(DI)背後的基本原理是物件之間的依賴關係(即一起工作的其它物件)只會通過以下幾種方式來實現:構造器的引數、工廠方法的引數,或給由建構函式或者工廠方法建立的物件設定屬性。因此,容器的工作就是建立bean時注入那些依賴關係。相對於由bean自己來控制其例項化、直接在構造器中指定依賴關係或者類似服務定位器(Service Locator)模式這3種自主控制依賴關係注入的方法來說,控制從根本上發生了倒轉,這也正是控制反轉(Inversion of Control, IoC)
應用DI原則後,程式碼將更加清晰。而且當bean自己不再擔心物件之間的依賴關係(甚至不知道依賴的定義指定地方和依賴的實際類)之後,實現更高層次的鬆耦合將易如反掌。DI主要有兩種注入方式,即Setter注入和構造器注入
AOP程式設計
容器和bean
什麼是bean
在Spring中,那些組成你應用程式的主體(backbone)及由Spring IoC*容器所管理的物件,被稱之為*bean。
簡單地講,bean就是由Spring*容器*初始化、裝配及管理的物件,除此之外,bean就與應用程式中的其他物件沒有什麼區別了。
而bean定義以及bean相互間的依賴
什麼是容器
org.springframework.beans.factory.BeanFactory
是Spring IoC*容器*的實際代表者,IoC容器負責容納此前所描述的bean,並對bean進行管理。
在Spring中,BeanFactory
是IoC容器的核心介面。 它的職責包括:例項化、定位、配置應用程式中的物件及建立這些物件間的依賴。
Spring為我們提供了許多易用的BeanFactory
實現, XmlBeanFactory
就是最常用的一個。該實現將以XML方式描述組成應用的物件 以及物件間的依賴關係。XmlBeanFactory
類將獲取此XML*配 置元資料*,並用它來構建一個完全可配置的系統或應用。
Bean的作用域
作用域 | 描述 |
---|---|
在每個Spring IoC容器中一個bean定義對應一個物件例項。 | |
一個bean定義對應多個物件例項。 | |
在一次HTTP請求中,一個bean定義對應一個例項;即每次HTTP請求將會有各自的bean例項, 它們依據某個bean定義建立而成。該作用域僅在基於web的Spring ApplicationContext 情形下有效。 |
|
在一個HTTP Session 中,一個bean定義對應一個例項。該作用域僅在基於web的Spring ApplicationContext 情形下有效。 |
|
在一個全域性的HTTP Session 中,一個bean定義對應一個例項。典型情況下,僅在使用portlet context的時候有效。該作用域僅在基於web的Spring ApplicationContext 情形下有效。 |
注意:儘量使用scop=”singleton“,避免使用propotype,以為propotype對效能影響比較大。
Bean的生命週期
Bean的例項化與銷燬
spring例項化bean或銷燬bean時,有時需要作一些處理工作,因此spring可以在建立和拆卸bean的時候呼叫bean的兩個生命週期方法。
<!-- 通過 配置init-method和destory-method,實現對bean的初始化,和銷燬bean時的相關操作-->
<bean class="Foo" init-method="init" destory-method="destroy">
初始化回撥
實現org.springframework.beans.factory.InitializingBean
介面允許容器在設定好bean的所有必要屬性後,執行初始化事宜。InitializingBean
介面僅指定了一個方法:
void afterPropertiesSet() throws Exception;
通常,要避免使用InitializingBean
介面並且不鼓勵使用該介面,因為這樣會將程式碼和Spring耦合起來,有一個可選的方案是,可以在Bean定義中指定一個普通的初始化方法,然後在XML配置檔案中通過指定init-method
屬性來完成。如下面的定義所示:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
…效果與下面完全一樣…
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
析構回撥
實現org.springframework.beans.factory.DisposableBean
介面的bean允許在容器銷燬該bean的時候獲得一次回撥。DisposableBean
介面也只規定了一個方法:
void destroy() throws Exception;
通常,要避免使用DisposableBean
標誌介面而且不鼓勵使用該介面,因為這樣會將程式碼與Spring耦合在一起,有一個可選的方案是,在bean定義中指定一個普通的析構方法,然後在XML配置檔案中通過指定destroy-method
屬性來完成。如下面的定義所示:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
…效果與下面完全一樣…
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
… 但是沒有將程式碼與Spring耦合在一起。
注意:
spring也提供了兩個介面來實現相同的功能:
InitializingBean和DisposableBean.
InitializingBean介面提供了一個afterPropertiesSet()方法。
DisposableBean介面提供了destroy().
不推薦使用該介面,它將你的bean和springAPI邦定在一起。
裝配Bean
在spring容器內拼湊bean叫做裝配。裝配bean的時候,需要告訴容器哪些bean 以及容器如何使用依賴注入將它們配合在一起。
使用XML裝配
xml是最常見的spring應用系統配置源。
幾種spring容器都支援使用xml裝配bean,包括:
- XmlBeanFactory:呼叫ClassPathResource載入上下文定義檔案(比如applicationContext.xml)。
- ClassPathXmlApplicationContext:從類路徑載入上下文定義檔案。
- XmlWebApplicationContext:從web應用上下文中載入定義檔案。
裝配方式
上下文定義檔案的根元素是.有多個子元素。每個元素定義了一個bean如何被裝配到spring容器中。
<beans>
<bean id="foo" class="...Foo"/>
<bean id="bar" class="...Bar"/>
</beans>
對bean的最基本的配置包括bean的ID和他的全稱類名。
繼承裝配
① 繼承
② 繼承配置
③ 覆蓋父 Bean配置
④ 可以設定 的abstract 屬性為 true, Spring 不會例項化該Bean
通過新增 parent 屬性
<bean id="graduate" parent="student" class="com.inherit.Graduate">
例項
父類
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
子類
public class Graduate extends Student {
private String degree;
public String getDegree() {
return degree;
}
public void setDegree(String degree) {
this.degree = degree;
}
}
beans.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 配置一個學生物件 -->
<bean id="student" class="com.inherit.Student">
<property name="name" value="Nick" />
<property name="age" value="25" />
</bean>
<!-- 配置子類 -->
<bean id="graduate" parent="student" class="com.inherit.Graduate">
<!-- 如果自己配置屬性name,age,可以覆蓋父類屬性 -->
<property name="degree" value="master" />
<property name="name" value="Tom" />
</bean>
</beans>
測試類
public class App {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("com/inherit/beans.xml");
Graduate graduate = (Graduate) ac.getBean("graduate");
System.out.println(graduate.getName());
System.out.println(graduate.getAge());
System.out.println(graduate.getDegree());
}
}
輸出:
Tom //注意這裡通過對子類屬性的配置覆蓋了父類屬性,java繼承的特性
25
master
自動裝配Bean的屬性值(重點)
Spring IoC容器可以自動裝配(autowire)相互協作bean之間的關聯關係。
* Autowiring modes*
模式 | 說明 |
---|---|
no | |
byName | 根據屬性名自動裝配。此選項將檢查容器並根據名字查詢與屬性完全一致的bean,並將其與屬性自動裝配。例如,在bean定義中將autowire設定為by name,而該bean包含master屬性(同時提供setMaster(..)方法),Spring就會查詢名為master 的bean定義,並用它來裝配給master屬性。 |
byType | 如果容器中存在一個與指定屬性型別相同的bean,那麼將與該屬性自動裝配。如果存在多個該型別的bean,那麼將會丟擲異常,並指出不能使用byType方式進行自動裝配。若沒有找到相匹配的bean,則什麼事都不發生,屬性也不會被設定。如果你不希望這樣,那麼可以通過設定dependency-check="objects" 讓Spring丟擲異常。 |
constructor | 與byType的方式類似,不同之處在於它應用於構造器引數。如果在容器中沒有找到與構造器引數型別一致的bean,那麼將會丟擲異常。 |
autodetect | 通過bean類的自省機制(introspection)來決定是使用constructor還是byType方式進行自動裝配。如果發現預設的構造器,那麼將使用byType方式。 |
如果直接使用property
和constructor-arg
注入依賴的話,那麼將總是
覆蓋自動裝配。而且目前也不支援簡單型別的自動裝配,這裡所說的簡單型別包括基本型別、String
、Class
以及簡單型別的陣列(這一點已經被設計,將考慮作為一個功能提供)。byType和constructor自動裝配模式也可用於陣列和指定型別的集合。在這種情況下容器中的所有匹配的自動裝配物件將被用於滿足各種依賴。
使用說明
- byName的用法:
<!-- 配置一個master物件 -->
<bean id="master"class="com.hsp.autowire.Master" autowire="byName">
<property name="name">
<value>順平</value>
</property>
</bean>
<!-- 配置dog物件 -->
<bean id="dog" class="com.hsp.autowire.Dog">
<property name="name"value="小黃"/>
<property name="age"value="3"/>
</bean>
byType: byType:尋找和屬性型別相同的bean,找不到,裝不上,找到多個拋異常。
constructor: autowire=”constructor”
說明 :查詢和bean的構造引數一致的一個或多個bean,若找不到或找到多個,拋異常。按照引數的型別裝配
- autodetect
說明 : autowire=”autodetect” (3)和(2)之間選一個方式。不確定性的處理與(3)和(2)一致。
- defualt
這個需要在
注入依賴
依賴注入(DI)背後的基本原理是物件之間的依賴關係(即一起工作的其它物件)只會通過以下幾種方式來實現:構造器的引數、工廠方法的引數,或給由建構函式或者工廠方法建立的物件設定屬性。也就是在建立Bean時通過容器向Bean中注入Bean與Bean之間的各種依賴關係
構造器注入
構造器引數解析
構造器引數解析根據引數型別進行匹配,如果bean的構造器引數型別定義非常明確,那麼在bean被例項化的時候,bean定義中構造器引數的定義順序就是這些引數的順序,依次進行匹配,比如下面的程式碼
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
上述例子中由於構造引數非常明確(這裡我們假定 Bar
和 Baz
之間不存在繼承關係)。因此下面的配置即使沒有明確指定構造引數順序(和型別),也會工作的很好。
<beans>
<bean name="foo" class="x.y.Foo">
<constructor-arg>
<bean class="x.y.Bar"/>
</constructor-arg>
<constructor-arg>
<bean class="x.y.Baz"/>
</constructor-arg>
</bean>
</beans>
我們再來看另一個bean,該bean的構造引數型別已知,匹配也沒有問題(跟前面的例子一樣)。但是當使用簡單型別時,比如<value>true<value>
,Spring將無法知道該值的型別。不借助其他幫助,他將無法僅僅根據引數型別進行匹配,比如下面的這個例子:
package examples;
public class ExampleBean {
// No. of years to the calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
構造器引數型別匹配
針對上面的場景可以通過使用'type'
屬性來顯式指定那些簡單型別的構造引數的型別,比如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
構造引數索引
我們還可以通過index
屬性來顯式指定構造引數的索引,比如下面的例子:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
通過使用索引屬性不但可以解決多個簡單屬性的混淆問題,還可以解決有可能有相同型別的2個構造引數的混淆問題了,注意index是從0開始。
Setter方法注入
略
對比兩種注入方式:
set注入的缺點是無法清晰表達哪些屬性是必須的,哪些是可選的,構造注入的優勢是通過構造強制依賴關係,不可能例項化不完全的或無法使用的bean。
對集合注入操作(set注入演示)
Bean類
public class Department {
private String name;
private String[] empName;
private List<Employee> emplist;
private Set<Employee> empSet;
private HashMap<Integer, Employee> empMaps;
public void setEmpMaps(HashMap<Integer, Employee> empMaps) {
this.empMaps = empMaps;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String[] getEmpName() {
return empName;
}
public void setEmpName(String[] empName) {
this.empName = empName;
}
public List<Employee> getEmplist() {
return emplist;
}
public void setEmplist(List<Employee> emplist) {
this.emplist = emplist;
}
public Set<Employee> getEmpSet() {
return empSet;
}
public void setEmpSet(Set<Employee> empSet) {
this.empSet = empSet;
}
public HashMap<Integer, Employee> getEmpMaps() {
return empMaps;
}
}
public class Employee {
private String name;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<bean id="department" class="com.collection.Department">
<property name="name" value="財務部"/>
<!-- 給陣列注入值 -->
<property name="empName">
<list>
<value>小李</value>
<value>小王</value>
<value>小文</value>
<value>小張</value>
</list>
</property>
<!-- 給list注入值 -->
<property name="emplist">
<list>
<ref bean="emp1"/>
<ref bean="emp2" />
<ref bean = "emp3"/>
</list>
</property>
<!-- 給set注入值 -->
<property name="empSet">
<!-- 測試set集合的不重複特性-->
<set>
<ref bean="emp1"/>
<ref bean="emp2" />
<ref bean = "emp3"/>
<ref bean="emp2" />
<ref bean = "emp3"/>
<ref bean="emp2" />
<ref bean = "emp3"/>
<ref bean="emp2" />
<ref bean = "emp3"/>
<ref bean="emp2" />
<ref bean = "emp3"/>
</set>
</property>
<!-- 給map注入值 -->
<property name="empMaps">
<map>
<entry key="1" value-ref="emp1"/>
<entry key="2" value-ref="emp2"/>
<entry key="3" value-ref="emp3"/>
</map>
</property>
</bean>
<bean id="emp1" class="com.collection.Employee">
<property name="name" value="北京" />
<property name="id" value="1" />
</bean>
<bean id="emp2" class="com.collection.Employee">
<property name="name" value="深圳" />
<property name="id" value="2" />
</bean>
<bean id="emp3" class="com.collection.Employee">
<property name="name" value="上海" />
<property name="id" value="3" />
</bean>
</beans>
測試類
package com.collection;
import java.util.Map.Entry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("com/collection/beans.xml");
Department department = (Department) ac.getBean("department");
System.out.println(department.getName());
System.out.println("僱員名稱");
for (String empName : department.getEmpName()) {
System.out.println(empName);
}
System.out.println("*******通過list集合取出資料******");
for (Employee employee : department.getEmplist()) {
System.out.println(employee.getName());
}
System.out.println("*******通過set集合取出資料******");
for (Employee employee : department.getEmpSet()) {
System.out.println(employee.getName());
}
System.out.println("*******通過map集合取出資料******");
for (Entry<Integer, Employee> entry : department.getEmpMaps().entrySet()) {
System.out.println("key: " + entry.getKey() + ", value = " + entry.getValue().getName());
}
}
}
輸出結果:
財務部
僱員名稱
小李
小王
小文
小張
*******通過list集合取出資料******
北京
深圳
上海
*******通過set集合取出資料******
北京
深圳
上海
*******通過map集合取出資料******
key: 1 vule = 北京
key: 2 vule = 深圳
key: 3 vule = 上海
使用Spring的特殊Bean
讓spring特殊對待這些bean。使它們可以:
通過配置後加工bean,涉及到Bean和Bean工廠生命週期。
2.改變依賴注入,將字串轉換成其它型別。
3.從屬性文字裝載資訊,包括資訊國際化。
4.監聽並處理其它bean及spring釋出的系統訊息。
5.知道自己在spring中的唯一表識。
對bean工廠進行後處理
分散配置(有兩種方式引入檔案)
將配置檔案分成幾個分散的配置檔案。如資料來源
<bean class="...PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>xx/yy/db.properties</value>
<value>xx/yy/db2.properties</value>
</list>
</property>
</bean>
<!– 名字空間配置(2.5) -->
<context:property-placeholder location="classpath:com/hsp/spring/dispatcher/db.properties"/>
感知其他bean
執行在spring容器中的bean不知道自己的註冊名,執行在哪裡。實現以下三個介面:
BeanNameAware:知道自己的名字。
無需為該介面的setBeanName()方法作任何處理,bean被載入時,容器會自動呼叫該方法,為其設定id或name的值。
BeanFactoryAware:所處的bean工廠。
ApplicationContextAware:所在上下文