文章目錄
Dependency Injection(依賴注入)
對於一個application 來說,需要組織很多的class,讓他們相互獨立,便於重用、方便測試。依賴注入就可以實現。
SpellChecker 是一個空class。
public class TextEditor {
private SpellChecker sc;
public TextEditor() {
sc = new SpellChecker();
}
}
從代碼中,我們可以看出TextEditor 是依賴於SpellChecker ,TextEditor 在創建的時候,必須要有SpellChecker ,這時是實際上,是通過TextEditor 去控制SpellChecker ,寫死在TextEditor 構造器中,假如這時我們要依賴一個其他的class,就要手動該代碼,這很痛苦,那麼,我們看下面這種:
public class TextEditor {
private SpellChecker sc;
public TextEditor(SpellChecker sc) {
this.sc = sc;
}
}
在TextEditor 的構造器中,傳入SpellChecker 對象,然後在賦值。這時,就很靈活,就不是由TextEditor 去控制死了,而是交給Spring 容器去控制。這時如果傳入不同對象,我們只需要讓它繼承共同的父類,或者實現共同的接口即可。
依賴注入的兩種形式
- 通過構造器
在前面的例子,我們是通過set方法來演示了依賴注入,下面我們看看如何通過構造器去注入:
public class SpellChecker {
public void spellCheck() {
System.out.println("i am the inner texteditor");
}
}
public class TextEditor {
private SpellChecker sc;
private String name;
public TextEditor(SpellChecker sc, String name) {
super();
this.sc = sc;
this.name = name;
}
public void spellCheck() {
sc.spellCheck();
}
}
<bean name="texteditor" class="test.TextEditor">
<constructor-arg name="name" value="hello world"></constructor-arg>//通過構造器傳參,達到注入
<constructor-arg name="sc" ref="spellchecker"></constructor-arg>//注入的是一個引用對象
</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>
使用構造器傳參設置的時候,和你Bean 裏面提供的帶參的構造器參數順序,無所謂。
- 通過set方法
注入內部Bean
內部Bean 簡而言之就和Java 的內部類差不多。就是在一個外部Bean中在依賴一個Bean。
public class SpellChecker {
public void spellCheck() {
System.out.println("i am the inner texteditor");
}
}
public class TextEditor {
private SpellChecker sc;
public SpellChecker getSc() {
return sc;
}
public void setSc(SpellChecker sc) {
this.sc = sc;
}
public void spellCheck() {
sc.spellCheck();
}
}
這裏通過set 把SpellChecker 對象注入到TextEditor 。
<bean name="texteditor" class="test.TextEditor">
<property name="sc">
<bean name="spellchecker" class="test.SpellChecker"></bean>
</property>
</bean>
這裏實際和之前配置property 差不多,只不過這時不是簡單的屬性值,而是一個Bean。
運行之後,會調用SpellChecker 的方法。
注入集合
之前無論是注入一個基本屬性,還是一個Bean,注入的value 都是一個,如果是多個值,比如Java 的集合類型。Spring 提供了四種集合注入的屬性。
Element | Description |
---|---|
list | 允許注入一系列list 的值,可以重複 |
set | 允許注入一系列set的值,不可以重複 |
map | 允許注入鍵值對的集合,鍵和值可以是任何類型 |
Property | 允許注入鍵值對集合,但是必須鍵和值都是String 類型 |
public class InjectCollection {
private List list;
private Set set;
private Map<Integer,String> map;
private Properties prop;
}
通過set注入,set、get省略,自行補充。
<bean name="injectcollection" class="test.InjectCollection">
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="set">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<property name="map">
<map>
<entry key="1" value="map1"></entry>
<entry key="2" value="map2"></entry>
</map>
</property>
<property name="prop">
<props>
<prop key="one">one</prop>
<prop key="two">two</prop>
</props>
</property>
</bean>
這裏的集合配置,實際上是作爲屬性的value,去配置的。
這配置沒啥好說,就是規定你按照寫即可。
上述的配置,集合的值全部是基本類型,假如集合的值全部是引用類型,我們又該如何處理,加個引用唄!
<bean name="injectcollection" class="test.InjectCollection">
<property name="list">
<list>
<ref bean="address1"></ref>//List 的直接引用Bean 的id
</list>
</property>
<property name="set">
<set>
<ref bean="address1"></ref>
</set>
</property>
<property name="map">
<map>
<entry key-ref="address1" value-ref="address2"></entry>//map 的鍵和值都可以是引用類型
</map>
</property>
</bean>
爲了方便,這裏引用的Bean ,我就不定義了。properties 因爲規定了鍵和值類型,就不存在引用類型。
注入空和null值
<property name="message" value="" ></property>
等同於setXXX("")
<property name="message" ><null></null></property>
等同於setXXX(null)
自動裝載
自動裝載,裝載的對象是引用類型對象,不是普通類型的對象。
知道我們的依賴注入,都是通過set、構造函數,去手動裝載,現在,我們可以通過自動裝載減少相應的代碼。
但是自動也也有不好的,之前手動注入,你設置注入,最多沒有值,但是現在,它會跟着相應的策略去自己尋找,自動裝載。
自動裝載的模式
Mode | Description |
---|---|
no | 該Bean 不採用任何裝配模式,你顯式定義你的裝配 |
byName | 根據你的屬性名去自動裝配,它會根據你xml定義的屬性名和你Bean 定義的屬性名,一致自動裝配 |
byType | 根據的屬性的類型去自動裝配,它會根據XML配置文件中查看屬性,然後,如果屬性的類型與配置文件中的一個bean名稱匹配,則會嘗試匹配並連接屬性。 如果存在多個這樣的bean,則拋出異常。 |
constructor | 應用於構造函數,根據構造函數參數的數據類型,進行byType模式的自動裝配。 |
autodetect | 第一次採用constructor 去自動裝載,如果不行,採用 byType |
- byname
spring 容器會根據xml 配置自動裝載的屬性,到相應的Bean 中找名字一致的屬性,如果找到,就裝配成功;否則,無法注入該屬性。
public class SpellChecker {
public void spellCheck() {
System.out.println("i am the inner texteditor");
}
}
public class TextEditor {
private SpellChecker sc;
public void spellCheck() {
sc.spellCheck();
}
}
基於屬性注入,set、get方法省略,自行補充
<bean name="texteditor" class="test.TextEditor" autowire="byName">//同時,你這裏
<property name="sc" ref="spellchecker">//這裏我們不是採用在texteditor Bean 內部配置SpellCheckerBean,而是直接讓使用去引用另外的Bean。
</property>
</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>
運行結果和之前的一致。
- bytype
如果設置該自動裝載模式,Spring 容器會通過Bean 中該屬性的類型,去自動加載。但是你還是可以在xml 配置給基本類型去注入值。
public class SpellChecker {
public void spellCheck() {
System.out.println("i am the inner texteditor");
}
}
public class TextEditor {
private SpellChecker sc;
private String name;//屬性注入,set、get 方法省略,自行補充。
public void spellCheck() {
sc.spellCheck();
}
}
<bean name="texteditor" class="test.TextEditor" autowire="byType">//這裏由於設置裝載模式byType 會自動掃描該Bean 的中的依賴類型,並自動裝載。
<property name="name" value="hello"></property>
//<property name="sc" ></property>引用對象的屬性就沒有必要顯式設置。
</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>
- constructor
使用該模式,一定注意你的基於構造注入,纔有效,如果你是set注入,那還搞個毛毛。
該模式和bytype 很類似,但是應用於構造器上的,它會根據構造器的參數的類型,去自動裝配。
public class SpellChecker {
public void spellCheck() {
System.out.println("i am the inner texteditor");
}
}
public class TextEditor {
private SpellChecker sc;
private String name;
public TextEditor(SpellChecker sc, String name) {
super();
this.sc = sc;
this.name = name;
}
public void spellCheck() {
sc.spellCheck();
}
}
<bean name="texteditor" class="test.TextEditor" autowire="constructor">
<constructor-arg name="name" value="hello world"></constructor-arg>//這裏和butype 一致,會自動裝載SpellChecker 類型,無需你顯式設置,是需要傳入基本類型的name和value。
</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>
- autodetect
自動裝載的缺點
- 自動裝載的設置會被 and 重寫。
- 只能裝載基礎類型。
- 自動裝載沒有顯式裝載準確,自動裝載匹配會出現異常。
依賴注入註解
註解注入的好處,在於會覆蓋在xml 裝載的設置,爲Spring 自動裝配,我們需要在Beans.xml添加如下設置:
<context:annotation-config/>
如果在你的Beans,沒有該標籤,請在xml 頭添加:
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
Annotation | Description |
---|---|
@Required | 該註解主要用於在屬性的set方法,它表明受影響的 bean 屬性在配置時必須放在 XML 配置文件中,否則容器就會拋出一個 BeanInitializationException 異常 |
@Autowired | 該註解主要用在set、構造器、屬性和其他任何方法上的自動裝載 |
@Qualifier | 配合@Autowired使用,使裝載Bean 更準確,具體看例子 |
JSR-250 Annotations | Spring 對 JSR-250 Annotations的支持 |
- @Required
該註解是應用在Bean 的屬性的set方法上的。
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
}
<bean name="student" class="test.Student">
<property name="name" value="lao"></property>
<property name="age" value="11"></property>
</bean>
一旦在Bean 的屬性中,設置了該註解,相應的必須在Beans.xml 中設置屬性,否則會出現如下的錯誤:
Caused by: org.springframework.beans.factory.BeanInitializationException: Property ‘xxx’ is required for bean 'student’
Student s = (Student) context.getBean("student");
System.out.println(s.getAge()+s.getName());
- @Autowired
該註解可以用於在set 方法、構造函數、等任意的方法上,均可以實現自動裝載。它實現是根據byType 這種方法去自動注入的。
@Autowired on Setter Methods
public class SpellChecker {
public void spellCheck() {
System.out.println("i am the inner texteditor");
}
}
public class TextEditor {
private SpellChecker sc;
private String name;
public TextEditor() {
super();
// TODO Auto-generated constructor stub
}
public SpellChecker getSc() {
return sc;
}
@Autowired
public void setSc(SpellChecker sc) {
this.sc = sc;
}
public String getName() {
return name;
}
@Autowired
public void setName(String name) {
this.name = name;
}
public void spellCheck() {
sc.spellCheck();
}
}
<bean name="texteditor" class="test.TextEditor">
<property name="name" value="122"></property>
</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>
這裏有一點,你需要注意,你在前面的Bean 中給name 也設置該註解,如果你在不通過標籤去給它設置相應的值,它就會默認去自動注入,去找String 類型,然後矇蔽找不到,就報錯了。
@Autowired on Properties
該註解設置在屬性上,可以省去一系列的set方法,所以基於這種方法,我們不可以在配置文件中,設置,它會提示你沒有set方法,這一步,容器會自動幫我們設置值去處理。
public class TextEditor {
@Autowired
private SpellChecker sc;
private String name;
public TextEditor() {
super();
// TODO Auto-generated constructor stub
}
public SpellChecker getSc() {
return sc;
}
public String getName() {
return name;
}
public void spellCheck() {
sc.spellCheck();
}
}
SpellChecker類不變。
<bean name="texteditor" class="test.TextEditor">
</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>
@Autowired on Constructors
該註解應用於構造器上
public class TextEditor {
private SpellChecker sc;
private String name;
@Autowired
public TextEditor(SpellChecker sc, String name) {
super();
this.sc = sc;
this.name = name;
}
public void spellCheck() {
sc.spellCheck();
}
}
SpellChecker 類不變。
<bean name="texteditor" class="test.TextEditor">
<constructor-arg name="name" value="lao"/>
</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>
這裏需要注意一下,因爲該註解的構造器,包含兩個屬性,由於name 是String 類型,不需要自動裝載了,所以,你在這配置 文件 給出相應的值,由於該注入是基於構造器注入,所以用標籤。(上面是基於set方法注入,所以用)
@Autowired with (required = false) option
該註解有一個參數,可以設置是否強制自動裝載。
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
@Autowired
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Autowired(required=false)
public void setAge(int age) {
this.age = age;
}
}
SpellChecker 類不變。
<bean name="texteditor" class="test.TextEditor">
</bean>
<bean name="spellchecker" class="test.SpellChecker"></bean>
```
如果這裏沒有設置age 上註解爲false,這裏會報錯。這樣就可以控制@Autowired 是否裝載。該註解的required 默認值是false。
- @Qualifier
該註解一般和@Autowired 一起,使用,實現自動裝載,但是該屬性,可以指定如果有多個複合的Bean類型,可以指定裝載的Bean 的name,使得裝載更精確。
public class Student {
private String name;
private int age;
//set、get方法省略,請自行補充
}
public class ProFile {
@Autowired
@Qualifier("stu1")
private Student student;
public void getInformation() {
System.out.println(student.getAge()+student.getName());
}
}
<bean name="student" class="test.Student">
<property name="name" value="lao"></property>
<property name="age" value="11"></property>
</bean>
<bean name="stu1" class="test.Student">
<property name="name" value="lao11"></property>
<property name="age" value="1111"></property>
</bean>
<bean name="profile" class="test.ProFile"></bean>
``
4. JSR-250 Annotations
這裏面主要涉及到這三個註解@PostConstruct, @PreDestroy and @Resource。
public class Student {
private String name;
private int age;
//set、get方法自行補充
}
public class ProFile {
@Autowired
@Resource(name=“student”)
private Student student;
public void getInformation() {
System.out.println(student.getAge()+student.getName());
}
@PostConstruct
public void init1() {
System.out.println(“bean is start lalalal”);
}
@PreDestroy
public void destory1() {
System.out.println(“bean is over kukukuk”);
}
}
```
通過運行,我們可以看出@PostConstruct 是和我們之前說的,在Bean 實例初始化之前,執行的方法;@PreDestroy,在Bean 銷燬之前,執行的方法。@Resource 和 @Qualifier很類似,可以指定某一個Bean 類型,達到精確裝載。
Spring 常用註解
其實是用Spring 註解配置Bean 和在xml 中配置,是起相同的作用,你只需要二者取其一即可,另外,在學習的時候,也要對比着學習。當然,藉助註解,你可省去對xml 的操作。
@Configuration
該註解應用於某一個類,則說明該類是被Spring 容器認爲是Bean 定義的源頭。
@Bean
該註解應用於某一個方法,則說明該方法返回的是一個對象bean,並該bean 會被註冊到Spring context 中,以便使用。
使用例子
public class HelloWord {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@Configurable
public class BeanConfig {
@Bean
public HelloWord getHelloWorld() {
return new HelloWord();
}
}
這一步就很類似於xml中:
<bean id="helloworld" class="test.HelloWord" >
<property name="message" ><null></null></property>
</bean>
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);//這裏要注意獲取Spring 上下文的時候,和之前的不一樣了,是通過AnnotationConfigApplicationContext
HelloWord h1 = (HelloWord) context.getBean(HelloWord.class);//因爲我們在那個Bean 類中獲取Bean,是根據返回類型,沒有像xml 中有name,可以直接根據name。
h1.setMessage("hello annotation ");
System.out.println(h1.getMessage());
通過註解的方法去注入Bean 依賴
我們上面的例子只是簡單示例,如何通過註解的方式,注入一個Bean,但是如果Bean 中還依賴其他Bean,那該如何處理:
public class HelloWord {
private String message;
private Student s;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public HelloWord(Student s) {
super();
this.s = s;
}
public Student getS() {
return s;
}
public void setS(Student s) {
this.s = s;
}
public void showStudent() {
System.out.println("student "+s.getName()+s.getAge());
}
}
public class Student {
private String name;
private int age;
//自行補充相應的set、get方法
}
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
HelloWord h1 = (HelloWord) context.getBean(HelloWord.class);
h1.setMessage("hello annotation ");
System.out.println(h1.getMessage());
h1.getS().setAge(11);
h1.getS().setName("lao");
h1.showStudent();
@Configurable
public class BeanConfig {
@Bean
public HelloWord getHelloWorld() {
return new HelloWord(getStudent());//**這裏實際上是通過構造器注入的Student 對象**
}
@Bean
public Student getStudent() {
return new Student();
}
}
上面我們是通過構造器去注入,那麼如何使用set注入,一點小變化:
在HelloWord 中,提供Student 的set、get方法。
@Configurable
public class BeanConfig {
@Bean
public HelloWord getHelloWorld() {
HelloWord hw = new HelloWord();
hw.setS(getStudent());
return hw;
}
@Bean
public Student getStudent() {
return new Student();
}
}
@import
該註解主要用於在一個Bean 定義的配置文件中,引入其他Bean 定義的class,可以在配置中,直擊獲取其他Bean 配置的Bean 的實例。
@Bean
該註解中,可以指定Bean 在初始化之前執行的方法和在銷燬之前執行的方法。
@Bean(initMethod="init",destroyMethod="destory")//這裏填入對應Bean 中的方法名,方法必須申明在對應的Bean 類中。
@Scope
該註解可以指定Bean 的模式,默認是單例模式。該註解和@Bean 一起使用
@Scope("prototype")