原文部落格地址:pjmike的部落格
前言
在前面的文章 淺析Spring 的IoC和DI中簡述了 IOC和DI的基本概念和關係,總體上說,IOC 是一種可以幫助我們解耦各業務物件間依賴關係的物件繫結方式,那麼Spring 提供了兩種容器型別來提供支援 IOC方式。這兩種型別是:
- BeanFactory: 基礎型別的IOC容器,提供完整的IOC服務支援
- ApplicationContext: ApplicationContext是在 BeanFactory的基礎之上構建的,是相對高階的容器實現,除了擁有BeanFactory的所有支援,ApplicationContext提供了其他高階特性。
ApplicationContext 和 BeanFactory的繼承關係如下:
可以看到 ApplicationContext 間接繼承自 BeanFactory。
BeanFactory
BeanFactory的介紹
BeanFactory 是基礎型別IoC容器,提供完整的IoC服務支援。如果沒有特殊指定,預設採用延遲初始化策略(lazy-load)。只有當客戶端物件需要訪問容器中的某個受管物件的時候,才對該受管物件進行初始化以及依賴注入工作。
BeanFactory的物件註冊
BeanFactory,就是生產 Java Bean 的工廠,作為Spring 提供的基本的IoC容器,BeanFactory 幫助完成 業務物件的註冊和物件間依賴關係的繫結。
實際上,BeanFactory只是一個介面,它負責定義如何訪問容器內管理的Bean的方法,各個BeanFactory的具體實現類負責具體Bean的註冊以及管理工作。下面是BeanFactory的介面程式碼:
package org.springframework.beans.factory;
public interface BeanFactory {
/**
* 用來引用一個例項,或把它和工廠產生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory
*/
String FACTORY_BEAN_PREFIX = "&";
/*
* 四個不同形式的getBean方法,獲取例項
*/
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
boolean containsBean(String name); // bean是否存在
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否為單例項
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否為原型(多例項)
boolean isTypeMatch(String name, Class<?> targetType)
throws NoSuchBeanDefinitionException;// 名稱、型別是否匹配
Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 獲取型別
String[] getAliases(String name);// 根據例項的名字獲取例項的別名
}
複製程式碼
下面我們來測試下一般情況下 BeanFactory介面的具體實現類情況:
// 實體類
@Component
public class Demo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//Junit測試類
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private BeanFactory beanFactory;
@Test
public void test() {
System.out.println("concrete factory is: " + beanFactory.getClass());
Assert.assertTrue("Factory can't be null",beanFactory != null);
Demo demo = (Demo) beanFactory.getBean("demo");
System.out.println("Found the demo bean: "+demo.getClass());
}
}
複製程式碼
輸出結果如下:
concrete factory is: class org.springframework.beans.factory.support.DefaultListableBeanFactory
Found the demo bean: class com.pjmike.spring.Demo
複製程式碼
從結果可以看出,具體工廠是 org.springframework.beans.factory.support.DefaultListableBeanFactory
的例項。再來看看 BeanFactory
的繼承體現:
從上圖可以看出,BeanFactory有三個直接子類:
- ListableBeanFactory: 通過繼承該介面可以列出所有的Bean,也可以只列出與預期型別相對應的bean
- HierarchicalBeanFactory: 支援分層bean的管理,使BeanFactory支援雙親IOC容器的管理功能
- AutowireCapableBeanFactory: 可以填充不受Spring 控制的 Bean
而三個類的子類體系就更多,詳細的參考 Spring 原始碼。
再來看看之前提到的DefaultListableBeanFactory
,它也是上圖中最底層的實現類:
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
...
}
複製程式碼
這個類其實就是 BeanFactory
的預設實現類,一個比較通用的BeanFactory實現類,它除了間接實現 BeanFactory介面外,還實現了 BeanDefinitionRegistry介面,該介面才是BeanFactory實現中擔任 Bean註冊管理的角色,它抽象的定義了Bean註冊的邏輯,當然具體的是實現還是靠DefaultListableBeanFactory這等實現類。
ApplicationContext
ApplicationContext的介紹
ApplicationContext是在BeanFactory的基礎上構建的,是相對比較高階的容器實現,除了擁有 BeanFactory的所有支援,ApplicationContext還提供了其他高階特性,比如:
- 統一資源載入策略
- 國際化資訊支援
- 容器內部事件釋出機制
在ApplicationContext 容器啟動之後,預設全部初始化並繫結完成,所以,對於BeanFactory來說,ApplicationContext 往往要求更多的系統資源
ApplicationContext的實現
Spring 中的 Context
Spring 為基本的 BeanFactory 型別容器提供了 XmlBeanFactory 實現(繼承自DefaultListableBeanFactory),相應的,它也為 ApplicationContext 型別容器提供了以下幾個常用的實現:
org.springframework.context.support.FileSystemXmlApplicationContext
: 在預設情況下,從檔案系統載入 bean 定義以及相關資源的 ApplicationContext 實現org.springframework.context.support.ClassPathXmlApplicationContext
: 在預設情況下,從Classpath 載入bean 定義以及相關資源的 ApplicationContext 實現org.springframework.web.context.support.XmlWebApplicationContext
: Spring提供的用於 Web 應用程式的 ApplicationContext 實現。
在傳統的基於 XML的Spring專案中,經常會使用到上面的實現類
SpringBoot 中的 Context
在官方文件中給出對於一個 SpringBoot 應用它對應的Context的情況:
- 對於 web應用,context 是
AnnotationConfigServletWebServerApplicationContext
- 對於 響應式應用,context 是
AnnotationConfigReactiveWebServerApplicationContext
- 對於普通非 web應用,context 是
AnnotationConfigApplicationContext
以上的 context
實際上也是實現了 ApplicationContext
介面
ApplicationContext 的簡單實踐
我們都知道IOC容器一般有兩種物件注入方式:基於XML配置檔案 與 基於註解驅動的方式。下面就分別從這兩個角度來看如何使用 ApplicationContext
基於 XML 配置檔案
- 定義個實體類
public class User {
private Integer id;
private String username;
public User(Integer id, String username) {
this.id = id;
this.username = username;
}
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
複製程式碼
- 設定一個XML配置檔案,宣告 User Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.pjmike.spring.domain.User">
<constructor-arg name="id" value="1"/>
<constructor-arg name="username" value="pjmike"/>
</bean>
</beans>
複製程式碼
- 主程式
public class XmlBootStrap {
public static void main(String[] args) {
//構建一個 ApplicationContext 上下文
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
//設定此應用上下文的配置路徑
context.setConfigLocations("classpath:/META-INF/spring/context.xml");
//呼叫 refresh 方法,完成配置的解析、各種BeanFactoryPostProcessor和BeanPostProcessor的註冊、國際化配置的初始化、web內建容器的構造
context.refresh();
User user = context.getBean("user", User.class);
System.out.print("user.getName() = "+ user.getUsername());
}
}
複製程式碼
輸出結果
user.getName() = pjmike
複製程式碼
基於註解方式
- 宣告一個配置類
@Configuration
public class UserConfiguration {
@Bean(name = "user")
public User user() {
User user = new User();
user.setUsername("pj");
return user;
}
}
複製程式碼
- 主程式
public class AnnotationBootStrap {
public static void main(String[] args) {
// 構建一個 ApplicationContext 應用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//註冊一個配置 Bean
context.register(UserConfiguration.class);
// 呼叫 refresh 啟動容器
context.refresh();
User user = context.getBean("user", User.class);
System.out.println("user.getName() = "+user.getUsername());
}
}
複製程式碼
輸出結果
user.getName() = pj
複製程式碼
XML 與 Annotation 簡單對比
從上面的兩個例子可以看出基於XML和基於註解注入Bean 的方式是不一樣的,基於XML的應用上下文ClassPathXmlApplicationContext
需要設定配置路徑,基於註解的應用上下文AnnotationConfigApplicationContext
需要註冊一個配置Bean,但它們相同的一步就是必須要呼叫 refresh()
方法,該方法可以看做是IOC容器的啟動方法,它會做很多操作,比如完成配置的解析、各種BeanFactoryPostProcessor和BeanPostProcessor的註冊、國際化配置的初始化、web內建容器的構造等等,不呼叫它,容器就無法啟動。這裡只是簡要說明,更加詳細的介紹會在後面的文章介紹。
現在是springboot盛行的階段,基於XML配置檔案的方式已經逐步被基於註解的方式所取代,如今的專案中,更多的使用 註解的方式。 關於XML與註解更詳細的對比可以參閱開濤大神的文章: jinnianshilongnian.iteye.com/blog/187991…
小結
上面的文章比較簡單的總結了 BeanFactory 和 ApplicationContext,為後續分析Spring IOC詳細的初始化過程、Spring Bean的載入等做一個鋪墊