Spring 學習筆記(三):Spring Bean
1 Bean
配置
Spring
可以看做是一個管理Bean
的工廠,開發者需要將Bean
配置在XML
或者Properties
配置檔案中。實際開發中常使用XML
的格式,其中<bean>
中的屬性或子元素如下:
id
:Bean
在BeanFactory
中的唯一標識,在程式碼中通過BeanFactory
獲取Bean
的例項時候需要以此作為索引class
:Bean
的具體實體類,使用包名+類名
的形式指定scope
:指定Bean
例項的作用域<constructor-arg>
:使用構造方法注入,指定構造方法的引數,index
表示序號,ref
指定對BeanFactory
中其他Bean
的引用關係,type
value
指定引數常量值<property>
:用於設定一個屬性,表示使用setter
注入,name
指定屬性的名字,value
指定要注入的值,ref
指定注入的某個Bean
的id
<list>
:用於封裝List
或者陣列型別的依賴注入<map>
:封裝Map
型別的依賴注入<set>
:封裝Set
型別的依賴注入<entry>
:<map>
的子元素,用於設定一個鍵值對
2 Bean
例項化
Spring
例項化Bean
有三種方式:
- 構造方法例項化
- 靜態工廠例項化
- 例項工廠例項化
下面進行簡單的演示。
2.1 構造方法例項化
Spring
可以呼叫Bean
對應的類的無參構造方法進行例項化,比如:
public class TestBean {
public TestBean()
{
System.out.println("構造方法例項化");
}
}
配置檔案如下:
<bean id="testBean" class="TestBean"/>
則會呼叫無參構造方法初始化。
其實就是隻寫一個<bean>
就可以了,預設的話會呼叫無參構造方法初始化。
2.2 靜態工廠例項化
靜態工廠例項化需要在工廠類中配置一個靜態方法來建立Bean
factory-method
元素,首先建立工廠類:
public class TestBeanFactory {
private static final TestBean testBean = new TestBean();
public static TestBean getInstance()
{
return testBean;
}
}
接著配置檔案通過class
指定該工廠類,通過factory-method
指定獲取例項的方法:
<bean id="testBeanFactory" class="TestBeanFactory" factory-method="getInstance"/>
這樣就可以通過id
獲取了:
TestBean test = (TestBean) context.getBean("testBeanFactory");
2.3 例項工廠例項化
例項工廠例項化與靜態工廠例項化類似,不過是非靜態方法,然後加上一個factory-bean
元素,同樣首先建立工廠類:
public class TestBeanFactory {
public TestBean getInstance()
{
return new TestBean();
}
}
在配置檔案需要新增兩個Bean
,一個指定工廠類,一個指定使用哪一個工廠類以及使用工廠類的哪一個方法:
<bean id="factory" class="TestBeanFactory" /> <!--指定工廠類-->
<bean id="testBeanFactory" factory-bean="factory" factory-method="getInstance" /> <!--指定工廠Bean以及哪一個工廠方法-->
獲取:
TestBean test = (TestBean) context.getBean("testBeanFactory");
3 Bean
作用域
3.1 分類
<bean>
中的scope
可以指定的作用域如下:
singleton
:預設作用域,在Spring
容器只有一個Bean
例項prototype
:每次獲取Bean
都會返回一個新的例項request
:在一次HTTP
請求中只返回一個Bean
例項,不同HTTP
請求返回不同的Bean
例項,僅在Spring Web
應用程式上下文使用session
:在一個HTTP Session
中,容器將返回同一個Bean
例項,僅在Spring Web
應用程式上下文中使用application
:為每個ServletContext
物件建立一個例項,即同一個應用共享一個Bean
例項,僅在Spring Web
應用程式上下文使用websocket
:為每個WebSocket
物件建立一個Bean
例項,僅在Spring Web
應用程式上下文使用
下面具體說一下最常用的兩個:singleton
和prototype
。
3.2 singleton
scope
設定為singleton
時,Spring IoC
僅生成和管理一個Bean
例項,使用id
/name
獲取Bean
例項時,IoC
容器返回共享的Bean
例項。設定方式如下:
<bean id="testBean" class="TestBean"/>
<bean id="testBean" class="TestBean" scope="singleton"/>
因為這是預設的作用域,設定的話IDE
也智慧提示是多餘的:
所以通過不需要加上scope
,測試例子:
TestBean test1 = (TestBean) context.getBean("testBean");
TestBean test2 = (TestBean) context.getBean("testBean");
System.out.println(test1 == test2);
輸入的結果為True
。
3.3 prototype
每次獲取Bean
時都會建立一個新的例項,例子如下:
<bean id="testBean" class="TestBean" scope="prototype"/>
TestBean test1 = (TestBean) context.getBean("testBean");
TestBean test2 = (TestBean) context.getBean("testBean");
System.out.println(test1 == test2);
測試結果為False
。
4 Bean
生命週期
Spring
可以管理作用域為singleton
的生命週期,在此作用域下Spring
能精確知道Bean
何時被建立,何時初始化完成以及何時被摧毀。Bean
的整個生命週期如下:
- 例項化
Bean
- 進行依賴注入
- 如果
Bean
實現了BeanNameAware
,呼叫setBeanName
- 如果
Bean
實現了BeanFactoryAware
,呼叫setBeanFactory
- 如果
Bean
實現了ApplicationContextAware
,呼叫setApplicationContext
- 如果
Bean
實現了BeanPostProcessor
,呼叫postProcessBeforeInitialization
- 如果
Bean
實現了InitializingBean
,呼叫afterPropertiesSet
- 如果配置檔案配置了
init-method
屬性,呼叫該方法 - 如果實現了
BeanPostProcessor
,呼叫postProcessAfterInitialization
,注意介面與上面的相同但是方法不一樣 - 不需要時進入銷燬階段
- 如果
Bean
實現了DisposableBean
,呼叫destroy
- 如果配置檔案配置了
destroy-method
,呼叫該方法
下面用程式碼進行演示:
public class TestBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, BeanPostProcessor, InitializingBean, DisposableBean {
public TestBean()
{
System.out.println("呼叫構造方法");
}
@Override
public void setBeanName(String s) {
System.out.println("呼叫BeanNameAware的setBeanName");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("呼叫BeanFactoryAware的setBeanFactory");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("呼叫ApplicationContextAware的setApplicationContext");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("呼叫BeanPostProcessor的postProcessBeforeInitialization");
return null;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("呼叫InitializingBean的afterPropertiesSet");
}
public void initMethod()
{
System.out.println("呼叫XML配置的init-method");
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("呼叫BeanPostProcessor的postProcessAfterInitialization");
return null;
}
@Override
public void destroy() throws Exception {
System.out.println("呼叫DisposableBean的destroy");
}
public void destroyMethod()
{
System.out.println("呼叫XML配置的destroy-method");
}
}
配置檔案如下,指定了init-method
以及destroy-method
:
<bean id="testBean" class="TestBean" init-method="initMethod" destroy-method="destroyMethod"/>
測試:
public static void main(String[] args) {
ConfigurableApplicationContext context = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
TestBean test = (TestBean) context.getBean("testBean");
((BeanDefinitionRegistry) context.getBeanFactory()).removeBeanDefinition("testBean");
}
輸出如下:
如果沒有最後一行的手動刪除Bean
定義是不會看見最後兩行的輸出的,另外,這裡沒有呼叫BeanPostProcessor
介面的兩個方法,如果把scope
改為prototype
,輸出如下:
可以看到首先對Bean
進行一次初始化,並且再次生成一個新的例項,而且呼叫了BeanPostProcessor
的兩個方法。但是需要注意Spring
不會管理scope
為prototype
的銷燬,所以圖中沒有看到呼叫銷燬的方法。
5 Bean
裝配方式
Spring
支援以下兩種裝配方式:
- 基於
XML
裝配 - 基於註解裝配
- 顯式
Bean
裝配
Bean
的裝配方式也就是Bean
的依賴注入方式,下面分別進行闡述。
5.1 基於XML
裝配
基於XML
裝配也就是在XML
檔案中指定使用構造方法注入或者setter
注入,比如:
public class TestBean {
private final List<String> stringList;
private String s;
public TestBean(List<String> stringList) {
this.stringList = stringList;
}
public void setS(String s)
{
this.s = s;
}
@Override
public String toString() {
return stringList.toString() + "\n" + s + "\n";
}
}
該Bean
有一個帶引數的構造方法以及一個setter
,接著在XML
中指定相應的值即可:
<bean id="testBean" class="TestBean">
<constructor-arg index="0">
<list>
<value>1</value>
<value>2</value>
</list>
</constructor-arg>
<property name="s" value="444" />
</bean>
測試:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(context.getBean("testBean"));
5.2 基於註解裝配
儘管XML
方式可以簡單地裝配Bean
,但是一旦Bean
過多就會造成XML
檔案過於龐大,不方便以後的升級和維護,因此推薦使用基於註解的裝配方式,先來看一下常用的註解:
@Autowired
:自動裝配,預設按照Bean
的型別進行裝配,這是Spring
的註解@Resource
:與@Autowired
類似,但是是按名稱進行裝配,當找不到與名稱匹配的Bean
時才按照型別進行裝配,這是JDK
的註解@Qualifier
:與@Autowired
配合使用,因為@Autowired
預設按Bean
型別進行裝配,使用@Qualifier
可以按名稱進行裝配@Bean
:方法上的註解,用於產生一個Bean
,然後交由Spring
管理@Component
:表示一個元件物件,加上了該註解就能實現自動裝配,預設的Bean
的id
為使用小駝峰命名法的類@Repository
/@Service
/@Controller
:實際上是@Component
的別名,只不過是專門用於持久層/業務層/控制層的,從原始碼可以看出三個註解的定義除了名字不一樣其他都一致,並且都是@Component
的別名:
官方文件也提到相比起使用@Component
,使用@Repository
/@Service
/@Controller
在持久層/業務層/控制層更加合適,而不是統一使用@Component
:
5.3 註解使用示例
5.3.1 @Bean
@Bean
示例如下:
public class TestBean implements BeanNameAware{
@Override
public void setBeanName(String s) {
System.out.println("setBeanName");
}
}
@Configuration
public class Config {
@Bean
public TestBean getBean()
{
return new TestBean();
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
context.getBean("getBean");
}
}
注意通過@Bean
自動產生的Bean
的id
為方法名,而不是Bean
的類名的小駝峰形式。
5.3.2 其他
@Autowired
/@Resource
/@Qualifier
/@Repository
/@Service
/@Controller
綜合示例,首先建立如下包以及檔案:
@Controller
public class TestController {
@Resource
private TestService service;
public void save()
{
System.out.println("controller save");
service.save();
}
}
@Service
public class TestService {
@Autowired
@Qualifier("testRepository1")
private TestInterface repository1;
@Autowired
@Qualifier("testRepository2")
private TestInterface repository2;
public void save()
{
System.out.println("service save");
repository1.save();
repository2.save();
}
}
@Repository
public class TestRepository1 implements TestInterface{
@Override
public void save() {
System.out.println("repository1 save");
}
}
@Repository
public class TestRepository2 implements TestInterface{
@Override
public void save() {
System.out.println("repository2 save");
}
}
public interface TestInterface {
void save();
}
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
((TestController)context.getBean("testController")).save();
}
}
配置檔案:
<context:component-scan base-package="bean" />
在TestService
中,使用了@Qualifier
:
@Autowired
@Qualifier("testRepository1")
private TestInterface repository1;
@Autowired
@Qualifier("testRepository2")
private TestInterface repository2;
因為TestInterface
有兩個實現類,@Autowired
不知道是選擇TestRepository1
還是TestRepository2
,因此需要加上@Qualifier
,指定需要注入的Bean
的id
,或者使用@Resouce
:
@Resource
private TestInterface testRepository1;
@Resource
private TestInterface testRepository2;
但是要注意這樣默認了成員的名字就是Bean
的id
,可以看到這裡的名字是testRepository1
與testRepository2
而不是repository1
和repository2
。