IoC 容器是 Spring 的核心,Spring 經過 IoC 容器來管理對象的實例化和初始化(這些對象就是 Spring Bean),以及對象從建立到銷燬的整個生命週期。也就是管理對象和依賴,以及依賴的注入等等。html
Spring 提供 2 種不一樣類型的 IoC 容器:BeanFactory 和 ApplicationContext 容器。java
BeanFactory 是一個管理 Bean 的工廠,它主要負責初始化各類 Bean, 並調用它們的生命週期方法。BeanFactory 是最簡單的 Bean 容器,它由 org.springframework.beans.factory.BeanFactory 接口定義實現。提供了容器最基本的功能。web
目前 BeanFactory 沒多少人用,主要是爲了可以兼容 Spring 集成的第三方框架,因此目前仍然保留了該接口。下面是官網的解釋spring
The BeanFactory
and related interfaces, such as BeanFactoryAware
, InitializingBean
, DisposableBean
, are still present in Spring for the purposes of backward compatibility with the large number of third-party frameworks that integrate with Spring.api
Beanfactory 是 org.springframework.beans 的頂級接口。數組
ApplicationContext 容器幾乎涵蓋全部 BeanFactory 容器的功能,它繼承了 BeanFactory 接口,由org.springframework.context.ApplicationContext 接口定義實現。在 BeanFactory 的基礎上增長了AOP、事務支持等等功能。如今Spring 實際開發中基本上使用的是 ApplicationContext 容器。緩存
ApplicationContext 是 org.springframework.context 的頂級接口。安全
ApplicationContext 有兩個經常使用的實現類,分別是 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 。websocket
看名字就知道它是從類路徑 ClassPath 中尋找 XML 配置文件,來完成 ApplicationContext 實例化工做:session
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("configlocation"); //configlocation 是指定 Spring 配置文件(XML)的名稱和位置,好比 Beans.xml
該類是從文件系統中尋找 XML 配置文件:
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("configlocation"); //configlocation 是從非類路徑外中獲取 XML 的名稱和位置,好比 」F:/workCode/Beans.xml「
它們都是經過 XML 配置文件來加載 Bean 的。
看官網定義:
In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.
Bean 是由 Spring IoC 容器管理的對象,容器就能經過反射的形式將容器中準備好的對象注入(這裏使用的是反射給屬性賦值)到需求的組件中去,簡單來講,Spring IoC 容器能夠看做是一個工廠,Bean 至關於工廠的產品。 Spring 配置文件則告訴容器須要哪些 Bean,以及須要哪一種方式來裝配 Bean。
Bean 其實就是一個 Java 對象,它是根據 bean 規範編寫出來的類,而且由容器生成的對象就是一個 bean。
Bean 規範:
它和 POJO 實際上是同樣的,只不過是遵循 Bean 規範的 POJO 。
spring 配置文件主要支持兩種格式:XML 和 Properties 格式
通常來講,Spring 的配置文件使用 XML 格式。 XML 配置文件的根元素是
屬性名稱 | 描述 |
---|---|
id | Bean 的惟一標識符,Spring 容器對 Bean 的配置和管理都經過該屬性完成。id 的值必須以字母開始,可使用字母、數字、下劃線等符號。 |
name | name 屬性中能夠爲 Bean 指定多個名稱,每一個名稱之間用逗號或分號隔開。Spring 容器能夠經過 name 屬性配置和管理容器中的 Bean。 |
class | 該屬性指定了 Bean 的具體實現類,它必須是一個完整的類名,即類的全限定名。 |
scope | 用於設定 Bean 實例的做用域,屬性值能夠爲 singleton(單例)、prototype(原型)、request、session 和 global Session。其默認值是 singleton |
constructor-arg |
|
property |
|
ref |
|
value |
|
list | 用於封裝 List 或數組類型的依賴注入 |
set | 用於封裝 Set 類型的依賴注入 |
map | 用於封裝 Map 類型的依賴注入 |
entry | |
init-method | 容器加載 Bean 時調用該方法,相似於 Servlet 中的 init() 方法 |
destroy-method | 容器刪除 Bean 時調用該方法,相似於 Servlet 中的 destroy() 方法。該方法只在 scope=singleton 時有效 |
lazy-init | 懶加載,值爲 true,容器在首次請求時纔會建立 Bean 實例;值爲 false,容器在啓動時建立 Bean 實例。該方法只在 scope=singleton 時有效 |
Spring 容器在初始化一個 Bean 實例時,同時會指定該實例的做用域。Spring 5 支持 6 種做用域。
默認的做用域,單例模式。表示在 Spring 容器中只有一個 Bean 實例,Bean 以單例的方式存在。在容器啓動前就建立好了對象,任什麼時候間獲取都是以前建立好的那個對象。配置方式能夠缺省,由於是默認值。<bean class="..."></bean>
原型做用域,多實例模式。每次調用 Bean 時都會建立一個新實例。Bean 以多實例的方式存在。容器啓動默認不會建立多實例 bean,每次獲取都會建立一個新的實例 bean 。配置方式爲 <bean class="..." scope="prototype"></bean>
在 web 環境下,每次 HTTP 請求都會建立一個 Bean 實例,該做用域只在當前 HTTP Request 內有效。 配置方式爲 <bean class="..." scope="request"></bean>
在 web 環境下,每次 HTTP 會話共享一個 Bean 實例,不一樣的 Session 使用不一樣的 Bean 實例。該做用域只在當前 HTTP Session 內有效。配置方式爲 <bean class="..." scope="session"></bean>
在web 環境下,同一個 web application 共享一個 Bean 實例,該做用域在當前 ServletContext 內有效。
在web 環境下,同一個 websocket 共享一個 Bean 實例,該做用域在整個 websocket 中有效。
Bean 的初始化主要分爲兩個過程:Bean 的註冊和 Bean 的實例化。Bean 的註冊主要是指 Spring 經過讀取配置文件獲取各個 bean 的聲明信息,而且對這些信息進行註冊的過程。
在 XML 中配置好後進行註冊
<bean id="person" class="org.springframework.beans.Person"> <property name="id" value="1"/> <property name="name" value="Java"/> </bean>
可使用 @Component 或 @Configuration + @Bean 來註冊 Bean
@Component public class Person { private Integer id; private String name // 忽略其餘方法 }
@Configuration //能夠理解爲 XML 配置文件中的 <beans> 標籤 public class Person { @Bean //能夠理解爲 XML 配置文件中的 <bean> 標籤 public Person person(){ return new Person(); } // 忽略其餘方法 }
使用 BeanDefinitionRegistry.registerBeanDefinition() 方法來註冊 Bean, 代碼以下:
public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { RootBeanDefinition personBean = new RootBeanDefinition(Person.class); // 新增 Bean registry.registerBeanDefinition("person", personBean); } }
Spring 中的 Bean 的生命週期比較複雜,能夠表示爲: Bean 的定義 -> Bean 的初始化 -> Bean 的使用 -> Bean 的銷燬
Spring 是根據 Bean 的做用域來管理,對於單實例 singleton 做用域的 Bean, Spring 可以精確地知道 這個 Bean 的完整生命週期;而對於 prototype 做用域的 Bean, Spring 只負責建立, 當容器建立了 Bean 的實例後,Bean 的實例就交給客戶端管理,Spring 容器將再也不跟蹤其生命週期。
首先,從上面幾節中看到,關於 Bean 的定義和初始化中的註冊都在配置文件中或者其餘方式提早寫好。下面咱們直接從 Bean 初始化中的實例化開始看,通常會有如下幾個過程:
Spring 啓動, 查找並加載須要被 Spring 管理的 Bean , 並實例化 Bean ,實例化就是經過容器生成一個 Bean。實例化 Bean 方式主要有三種:類的無參構造方法、靜態工廠、實例工廠。
在配置文件 XML 中配置 bean, 默認使用了無參構造器建立 bean
<bean id="bean" class="com.spring.demo.Bean"></bean>
而後再經過 getBean() 方法來獲取實例化的 bean
ApplicationContext context = new ClasspathXmlApplicationContext("Bean.xml"); Bean b = (Bean)context.getBean("Bean.xml");
一樣也是須要在 XML 中配置 bean :
<bean id="bean" class="com.spring.demo.BeanFactory" factory-method="getBean"></bean>
id 和 class 都定位到某個工廠類,factory-method 表示調用到該類 BeanFactory 下的方法來建立對象,並且這個 getBean 方法必須是靜態方法。
一樣是用 getBean() 方法獲取實例化的 bean ,就不贅餘了。
一樣的,配置 XML 文件
<bean id="beanfactory" class="com.spring.demo.BeanFactory"></bean> <bean id="bean" factory-bean="beanfactory" factory-method="getbean"></bean>
實例工廠和靜態工廠的區別在於,該實例化方式工廠方法不須要是靜態的,須要先建立對象(在工廠類中新建一個對象),而後再經過對象調用其方法建立 bean。
這個階段須要利用依賴注入完成 Bean 中的全部屬性值的配置注入。容器的注入方法主要有構造方法和 Setter 方法注入。
注入方式是使用
<bean id="" class=""> <constructor-arg index="0" value=""></constructor-arg> <constructor-arg index="1" ref=""></constructor-arg> </bean>
Setter 方法注入的方式是目前 Spring 主流的注入方式,它能夠利用 Java Bean 規範所定義的 Setter/Getter 方法來完成注入,可讀性和靈活性都很高,它不須要使用聲明式構造方法,而是使用 Setter 注入直接設置相關的值。
<bean id="person" class="org.springframework.beans.Person"> <property name="id" value="1"/> <property name="name" value="Java"/> </bean>
在 Spring 實例化 Bean 的過程當中,首先會調用默認的構造方法實例化 Bean 的對象,而後經過 Java 的反射機制調用 set 方法進行屬性的注入。所以,setter 注入要求 Bean 的對應類必須知足一下要求:
若是 Bean 實現了 BeanNameAware 接口,則 Spring 調用 Bean 的 setBeanName() 方法傳入當前 Bean 的 id 值。
若是 Bean 實現了 BeanFactoryAware 接口,則 Spring 調用 setBeanFactory() 方法傳入當前工廠實例的引用。
若是 Bean 實現了 ApplicationContextAware 接口,則 Spring 調用 setApplicationContext() 方法傳入當前 ApplicationContext 實例的引用。
若是 Bean 實現了 BeanPostProcessor 接口,則 Spring 調用該接口的預初始化方法 postProcessBeforeInitialzation() 對 Bean 進行加工操做,此處很是重要,Spring 的 AOP 就是利用它實現的。
InitializingBean 是一個接口,它有一個 afterPropertiesSet() 方法,在 Bean 初始化時會判斷當前 Bean 是否實現了 InitializingBean,若是實現了則調用 afterPropertiesSet() 方法,進行初始化工做;而後再檢查是否也指定了 init-method,若是指定了則經過反射機制調用指定的 init-method 方法,它的實現源碼以下:
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd) throws Throwable { // 判斷當前 Bean 是否實現了 InitializingBean,若是是的話須要調用 afterPropertiesSet() boolean isInitializingBean = (bean instanceof InitializingBean); if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { if (logger.isTraceEnabled()) { logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); } if (System.getSecurityManager() != null) { // 安全模式 try { AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> { ((InitializingBean) bean).afterPropertiesSet(); // 屬性初始化 return null; }, getAccessControlContext()); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { ((InitializingBean) bean).afterPropertiesSet(); // 屬性初始化 } } // 判斷是否指定了 init-method() if (mbd != null && bean.getClass() != NullBean.class) { String initMethodName = mbd.getInitMethodName(); if (StringUtils.hasLength(initMethodName) && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { // 利用反射機制執行指定方法 invokeCustomInitMethod(beanName, bean, mbd); } } }
若是 BeanPostProcessor 和 Bean 關聯,則 Spring 將調用該接口的初始化方法 postProcessAfterInitialization()。此時,Bean 已經能夠被應用系統使用了。
若是在
在 Spring 容器關閉時會執行銷燬方法,可是 Spring 容器不會自動去調用銷燬方法,而是須要咱們主動的調用。
若是是 BeanFactory 容器,那麼咱們須要主動調用 destroySingletons() 方法,通知 BeanFactory 容器去執行相應的銷燬方法;若是是 ApplicationContext 容器,那麼咱們須要主動調用 registerShutdownHook() 方法,告知 ApplicationContext 容器執行相應的銷燬方法。
通常狀況下,會在 Bean 被初始化後和被銷燬前執行一些相關操做。
Spring 官方提供了 3 種方法實現初始化回調和銷燬回調:
不建議使用接口和註解,這會讓 pojo 類和 Spring 框架緊耦合。
Bean 的裝配能夠理解爲依賴關係注入,Bean 的裝配方式也就是 Bean 的依賴注入方式。Spring 容器支持多種裝配 Bean 的方式,如基於 XML 的 Bean 裝配、基於 Annotation 的 Bean 裝配和自動裝配等。基於 XML 的裝配方式主要分爲兩種,在 5.2 設置屬性值 中提到的過。
自動裝配就是指 Spring 容器在不使用
名稱 | 說明 |
---|---|
no | 默認值,表示不使用自動裝配,Bean 依賴必須經過 ref 元素定義。 |
byName | 根據 Property 的 name 自動裝配,若是一個 Bean 的 name 和另外一個 Bean 中的 Property 的 name 相同,則自動裝配這個 Bean 到 Property 中。 |
byType | 根據 Property 的數據類型(Type)自動裝配,若是一個 Bean 的數據類型兼容另外一個 Bean 中 Property 的數據類型,則自動裝配。 |
constructor | 相似於 byType,根據構造方法參數的數據類型,進行 byType 模式的自動裝配。 |
autodetect(3.0版本不支持) | 若是 Bean 中有默認的構造方法,則用 constructor 模式,不然用 byType 模式。 |
儘管可使用 XML 來裝配 Bean , 可是若是應用中 Bean 數量過多,會致使 XML 配置文件過於臃腫,對後期維護帶來必定的困難
Java 從 JDK 5.0 之後,提供了 Annotation(註解)功能,Spring 2.5 版本開始也提供了對 Annotation 技術的全面支持,咱們可使用註解來配置依賴注入。Spring 默認不使用註解裝配 Bean,所以須要在配置文件中添加 < context:annotation-config >,啓用註解。
Spring 中經常使用的註解以下。
可使用此註解描述 Spring 中的 Bean,但它是一個泛化的概念,僅僅表示一個組件(Bean),而且能夠做用在任何層次。使用時只需將該註解標註在相應類上便可。
用於將數據訪問層(DAO層)的類標識爲 Spring 中的 Bean,其功能與 @Component 相同。
一般做用在業務層(Service 層),用於將業務層的類標識爲 Spring 中的 Bean,其功能與 @Component 相同。
一般做用在控制層(如 Struts2 的 Action、SpringMVC 的 Controller),用於將控制層的類標識爲 Spring 中的 Bean,其功能與 @Component 相同。
能夠應用到 Bean 的屬性變量、屬性的 setter 方法、非 setter 方法及構造函數等,配合對應的註解處理器完成 Bean 的自動配置工做。默認按照 Bean 的類型進行裝配。
做用與 Autowired 相同,區別在於 @Autowired 默認按照 Bean 類型裝配,而 @Resource 默認按照 Bean 實例名稱進行裝配。
@Resource 中有兩個重要屬性:name 和 type。
Spring 將 name 屬性解析爲 Bean 的實例名稱,type 屬性解析爲 Bean 的實例類型。若是指定 name 屬性,則按實例名稱進行裝配;若是指定 type 屬性,則按 Bean 類型進行裝配。若是都不指定,則先按 Bean 實例名稱裝配,若是不能匹配,則再按照 Bean 類型進行裝配;若是都沒法匹配,則拋出 NoSuchBeanDefinitionException 異常。
與 @Autowired 註解配合使用,會將默認的按 Bean 類型裝配修改成按 Bean 的實例名稱裝配,Bean 的實例名稱由 @Qualifier 註解的參數指定。