上文講了基於構造器進行依賴注入,這裡講解基於Setter方法進行注入。在Java世界中有個約定(Convention),那就是屬性的設定和獲取的方法名一般是:set+屬性名(引數)及get+屬性名()的方式。boolean型別稍有不同,可以使用is+屬性名()方式來獲取。
以下是一個示例。
MessageHandler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MessageHandler {
private MessageService messageService ;
public MessageService getMessageService () {
return messageService ;
}
public void setMessageService ( MessageService messageService ) {
this . messageService = messageService ;
}
public String handle () {
return messageService . sendService ();
}
}
使用Setter方法注入如下所示。
1
2
3
4
<bean id= "messageService" class= "huangbowen.net.DependecyInjection.ConstructorInjection.SimpleMessageService" />
<bean id= "messageHandler" class= "huangbowen.net.DependecyInjection.SetterInjection.MessageHandler" >
<property name= "messageService" ref= "messageService" />
</bean>
如果property的name為messageService,那麼必須在類中有個叫做setMessageService
的方法,這樣才能完成注入。如果將MessageHandler.java中的setMessageService
方法改為setMessageService1
,那麼注入就會失敗,失敗message如下所示。
1
2
3
4
5
6
7
...
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageHandler' defined in class path resource [spring-context.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'messageService' of bean class [huangbowen.net.DependecyInjection.SetterInjection.MessageHandler]: Bean property 'messageService' is not writable or has an invalid setter method. Did you mean 'messageService1'?
...
當然可以同時使用構造器注入和setter方法注入。
Person.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {
private String name ;
public Person ( String name ) {
this . name = name ;
}
private int age ;
public int getAge () {
return age ;
}
public void setAge ( int age ) {
this . age = age ;
}
public String getName () {
return name ;
}
}
bean定義如下:
1
2
3
4
<bean id= "person" class= "huangbowen.net.DependecyInjection.SetterInjection.Person" >
<constructor-arg value= "Tom" />
<property name= "age" value= "20" />
</bean>
要實現一個bean,即可以使用構造器注入,也可以使用setter注入,甚至可以在一個bean中綜合使用這兩種方式。那麼在真正開發中應該作何取捨那?一般來說,使用構造器注入的依賴必須是強制的依賴,而使用setter注入的依賴則是可選的依賴。使用構造器注入生成的物件是完全初始化了的,使用者可以直接拿來用,但是相比於setter方法而言使用者也就失去了定製化的能力。如果你發現構造器引數過多,那麼很可能說明該類承擔的職責過多,應該從設計解耦的角度對類的職責進行拆分。使用setter注入的物件好處是,使用者可以按需重新注入新的屬性。
另外在進行依賴注入時,可以將某些屬性抽出來成為一個元素,或者將元素內聯成為一個屬性。比如ref這個屬性。
1
2
3
<bean id= "messageHandler" class= "huangbowen.net.DependecyInjection.SetterInjection.MessageHandler" >
<property name= "messageService" ref= "messageService" />
</bean>
它與以下xml配置完全等價。
1
2
3
4
5
<bean id= "messageHandler" class= "huangbowen.net.DependecyInjection.SetterInjection.MessageHandler" >
<property name= "messageService" >
<ref bean= "messageService" />
</property>
</bean>
value屬性也可以獨立為元素。
1
2
3
4
<bean id= "person" class= "huangbowen.net.DependecyInjection.SetterInjection.Person" >
<constructor-arg value= "Tom" />
<property name= "age" value= "20" />
</bean>
其等價於:
1
2
3
4
5
6
<bean id= "person" class= "huangbowen.net.DependecyInjection.SetterInjection.Person" >
<constructor-arg value= "Tom" />
<property name= "age" >
<value> 20</value>
</property>
</bean>
也可以顯式指定value的型別。
1
2
3
4
5
6
<bean id= "person" class= "huangbowen.net.DependecyInjection.SetterInjection.Person" >
<constructor-arg value= "Tom" />
<property name= "age" >
<value type= "int" > 20</value>
</property>
</bean>
比如有一個屬性是個boolean值,如果想將其注入為true的話,不指定具體型別的話,Spring可能會將其作為字串true對待。當然Spring會嘗試將傳入的字串轉換為setter方法希望的型別,但這種自動轉換有時候並不是你期望的,這種情況下你就需要顯式指定其型別。
本例中的原始碼請在我的GitHub 上自行下載。