淺析 Spring 的IOC容器

淺析 Spring 的IOC容器

前言

在前面的文章 淺析Spring 的IoC和DI中簡述了 IOC和DI的基本概念和關係,總體上說,IOC 是一種可以幫助我們解耦各業務對象間依賴關係的對象綁定方式,那麼Spring 提供了兩種容器類型來提供支持 IOC方式。這兩種類型是:

  • BeanFactory: 基礎類型的IOC容器,提供完整的IOC服務支持
  • ApplicationContext: ApplicationContext是在 BeanFactory的基礎之上構建的,是相對高級的容器實現,除了擁有BeanFactory的所有支持,ApplicationContext提供了其他高級特性。

ApplicationContext 和 BeanFactory的繼承關係如下:

淺析 Spring 的IOC容器

可以看到 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 getBean(String name, Class requiredType) throws BeansException;
T getBean(Class 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的繼承體現:

淺析 Spring 的IOC容器

從上圖可以看出,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的情況:

淺析 Spring 的IOC容器

  • 對於 web應用,context 是 AnnotationConfigServletWebServerApplicationContext
  • 對於 響應式應用,context 是 AnnotationConfigReactiveWebServerApplicationContext
  • 對於普通非 web應用,context 是 AnnotationConfigApplicationContext

以上的 context實際上也是實現了 ApplicationContext接口

ApplicationContext 的簡單實踐

我們都知道IOC容器一般有兩種對象注入方式:基於XML配置文件 與 基於註解驅動的方式。下面就分別從這兩個角度來看如何使用 ApplicationContext

基於 XML 配置文件

  1. 定義個實體類
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;
}
}
  1. 設置一個XML配置文件,聲明 User Bean

<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>
<constructor-arg>
<constructor-arg>
/<bean>
/<beans>
  1. 主程序
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

基於註解方式

  1. 聲明一個配置類
@Configuration
public class UserConfiguration {
@Bean(name = "user")
public User user() {
User user = new User();
user.setUsername("pj");
return user;
}
}
  1. 主程序
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的加載等做一個鋪墊


分享到:


相關文章: