一 概念
IOC(Inversion of Control)控制反轉:所謂控制反轉,就是把原先我們代碼裏面需要實現的對象創 建、依賴的代碼,反轉給容器來幫忙實現。那麼必然的我們需要創建一個容器,同時需要一種描述來讓 容器知道需要創建的對象與對象的關係。這個描述最具體表現就是我們可配置的文件。
DI(Dependency Injection)依賴注入:就是指對象是被動接受依賴類而不是自己主動去找,換句話說 就是指對象不是從容器中查找它依賴的類,而是在容器實例化對象的時候主動將它依賴的類注入給它。
依賴查找(DependencyLookup) : 主動根據 spring 提供的api查詢相應的bean.
二、 核心容器
Spring Bean 的創建是典型的工廠模式,這一系列的 Bean 工廠,也即 IOC 容器爲開發者管理對象間的依賴關係提供了很多便利和基礎服務,在 Spring 中有許多的 IOC 容器的實現供用戶選擇和使用, 其相互關係如下:
2.1、 BeanFactory
BeanFactory 作爲最頂層的一個接口類,它定義了 IOC 容器的基本功能規範。
BeanFactory 有三個子類:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。 但是從上圖中我們可以發現最終的默認實現類是 DefaultListableBeanFactory,他實現了所有的接 口。那爲何要定義這麼多層次的接口呢?查閱這些接口的源碼和說明發現,每個接口都有他使用的場合, 它主要是爲了區分在 Spring 內部在操作過程中對象的傳遞和轉化過程中,對象的數據訪問所做的限制。
ListableBeanFactory: 接口表示這些 Bean 是可列表的,依賴查找 bean的集合。
HierarchicalBeanFactory :有層次的bean,父容器類似雙親委派機制。表示的是這些Bean是有繼承關係的,也就是每個Bean有可能有父Bean。
AutowireCapableBeanFactory:接口定義 Bean 的自動裝配規則。
DefaultListableBeanFactory : 默認實現的
DefaultListableBeanFactory 實現的設計模式有:
抽象工廠(BeanFactory 接口實現)
組合模式(組合 AutowireCandidateResolver 等實例)
單例模式(Bean Scope)
原型模式(Bean Scope)
模板模式(模板方法定義:AbstractBeanFactory)
適配器模式(適配 BeanDefinitionRegistry 接口)
策略模式(Bean 實例化)
代理模式(ObjectProvider 代理依賴查找)
2.2、 ApplicationContext
如果說BeanFactory 是容器中的屌絲,ApplicationContext 應該算容器中的高帥富。 ApplicationContext 是具備應用特性的BeanFactory 超集。
ApplicationContext 是 Spring 提供的一個高級的IOC 容器, ApplicationContext 除了IoC 容器角色,還有提供:
- 面向切面(AOP)
- 配置元信息(Configuration Metadata)
- 資源管理(Resources)
- 事件(Events)
- 國際化(i18n)
- 註解(Annotations)
- Environment 抽象(Environment Abstraction)
2.3、 具體實現
BeanFactory 裏只對 IOC 容器的基本行爲作了定義,根本不關心你的 Bean 是如何定義怎樣加載的。 正如我們只關心工廠裏得到什麼的產品對象,至於工廠是怎麼生產這些對象的,這個基本的接口不關心。
public interface BeanFactory {
//對FactoryBean的轉義定義,因爲如果使用bean的名字檢索FactoryBean得到的對象是工廠生成的對象,
//如果需要得到工廠本身,需要轉義
String FACTORY_BEAN_PREFIX = "&";
//根據bean的名字,獲取在IOC容器中得到bean實例
Object getBean(String name) throws BeansException;
//根據bean的名字和Class類型來得到bean實例,增加了類型安全驗證機制。
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
//提供對bean的檢索,看看是否在IOC容器有這個名字的bean
boolean containsBean(String name);
//根據bean名字得到bean實例,並同時判斷這個bean是不是單例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
//得到bean實例的Class類型
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
//得到bean的別名,如果根據別名檢索,那麼其原名也會被檢索出來
String[] getAliases(String name);
}
而要知道工廠是如何產生對象的,我們需要看具體的 IOC 容器實現,Spring 提供了許多 IOC 容器 的實現。比如 XmlBeanFactory,ClasspathXmlApplicationContext 、AnnotationConfigApplicationContext等。其中 XmlBeanFactory 就 是針對最基本的 IOC 容器的實現,這個 IOC 容器可以讀取 XML 文件定義的 BeanDefinition(XML 文件中對 bean 的描述),如果說 XmlBeanFactory 是容器中的屌絲,ClasspathXmlApplicationContext 應該算容器中 的高帥富.。
2.4 、具體代碼
1、BeanFactory 作爲 IoC 容器示例
/**
* {@link BeanFactory} 作爲 IoC 容器示例
*
* @author
* @since
*/
public class BeanFactoryAsIoCContainerDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
// XML 配置文件 ClassPath 路徑
String location = "classpath:/META-INF/dependency-lookup-context.xml";
// 加載配置
int beanDefinitionsCount = reader.loadBeanDefinitions(location);
System.out.println("Bean 定義加載的數量:" + beanDefinitionsCount);
// 依賴查找集合對象
lookupCollectionByType(beanFactory);
}
private static void lookupCollectionByType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
System.out.println("查找到的所有的 User 集合對象:" + users);
}
}
}
2、ApplicationContext 作爲 IoC 容器示例
/**
* 註解能力 {@link ApplicationContext} 作爲 IoC 容器示例
*
* @author
* @since
*/
@Configuration
public class AnnotationApplicationContextAsIoCContainerDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 將當前類 AnnotationApplicationContextAsIoCContainerDemo 作爲配置類(Configuration Class)
applicationContext.register(AnnotationApplicationContextAsIoCContainerDemo.class);
// 啓動應用上下文
applicationContext.refresh();
// 依賴查找集合對象
lookupCollectionByType(applicationContext);
// 關閉應用上下文
applicationContext.close();
}
/**
* 通過 Java 註解的方式,定義了一個 Bean
*/
@Bean
public User user() {
User user = new User();
user.setId(1L);
user.setName("坤仔");
return user;
}
private static void lookupCollectionByType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
System.out.println("查找到的所有的 User 集合對象:" + users);
}
}
}
其中 下面兩段代碼相等
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AnnotationApplicationContextAsIoCContainerDemo.class);
applicationContext.refresh();
AnnotationConfigApplicationContext applicationContext1 =
new AnnotationConfigApplicationContext(AnnotationApplicationContextAsIoCContainerDemo.class);
器底層實現:
public AnnotationConfigApplicationContext(Class... componentClasses) {
this();
this.register(componentClasses);
this.refresh();
}
2.5、容器生命週期
1 啓動 ——> 運行 ——> 停止
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
// 將當前類 AnnotationApplicationContextAsIoCContainerDemo 作爲配置類(Configuration Class)
applicationContext.register(AnnotationApplicationContextAsIoCContainerDemo.class);
// 啓動應用上下文
applicationContext.refresh();
// 關閉應用上下文
applicationContext.close();
三 、依賴查找 DependencyLookup
應用程序裏面還是要調用容器的bean查找接口查找bean實例。 缺點:還是有侵入性,性能低。
注意: 延遲查找的意思: 延遲是指非一次性獲得
代碼示例:
/**
* 依賴查找示例
* 1. 通過名稱的方式來查找
*
* @author
* @since
*/
public class DependencyLookupDemo {
public static void main(String[] args) {
// 配置 XML 配置文件
// 啓動 Spring 應用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-lookup-context.xml");
// 按照名字查找
lookupByName(beanFactory);
// 按照類型查找
lookupByType(beanFactory);
// 按照類型查找集合對象
lookupCollectionByType(beanFactory);
// 通過註解查找對象
lookupByAnnotationType(beanFactory);
// 使用 ObjectFactory實現延遲查找
lookupInLazy(beanFactory);
}
//按照名字查找
private static void lookupByName(BeanFactory beanFactory) {
User user = (User)beanFactory.getBean("user");
System.out.println("按照名字實時查找:" + user);
}
//按照類型查找
private static void lookupByType(BeanFactory beanFactory) {
User user = beanFactory.getBean(User.class);
System.out.println("按照類型實時查找:" + user);
}
/**
* 按照類型查找集合對象
* 根據 User能查詢出來兩個bean
* 使用 ListableBeanFactory
* @param beanFactory
*/
private static void lookupCollectionByType(BeanFactory beanFactory) {
if(beanFactory instanceof ListableBeanFactory ){
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
System.out.println("查找到的所有的 User 集合對象:" + users);
}
}
/**
* 根據註解查找 查找標註註解爲@Super 的bean
* @param beanFactory
*/
private static void lookupByAnnotationType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = (Map) listableBeanFactory.getBeansWithAnnotation(Super.class);
System.out.println("查找標註 @Super 所有的 User 集合對象:" + users);
}
}
/**
* 使用 ObjectFactory實現延遲查找
* @param beanFactory
*/
private static void lookupInLazy(BeanFactory beanFactory) {
ObjectFactory<User> objectFactory = (ObjectFactory<User>) beanFactory.getBean("objectFactory");
User user = objectFactory.getObject();
System.out.println("延遲查找:" + user);
}
}
3.1 單一類型查找 BeanFactory
單一類型依賴查找接口-BeanFactory
• 根據Bean 名稱查找
• getBean(String)
• Spring 2.5 覆蓋默認參數:getBean(String,Object...)
• 根據Bean 類型查找
• Bean 實時查找
• Spring 3.0 getBean(Class)
• Spring 4.1 覆蓋默認參數:getBean(Class,Object...)
• Spring 5.1 Bean 延遲查找
• getBeanProvider(Class)
• getBeanProvider(ResolvableType)
• 根據Bean 名稱+ 類型查找:getBean(String,Class)
代碼示例 其他在 上面已經放入代碼了。
3.2、集合類型依賴查找接口-ListableBeanFactory
集合類型依賴查找接口-ListableBeanFactory
• 根據Bean 類型查找
• 獲取同類型Bean 名稱列表
• getBeanNamesForType(Class)
• Spring 4.2 getBeanNamesForType(ResolvableType)
• 獲取同類型Bean 實例列表
• getBeansOfType(Class) 以及重載方法
• 通過註解類型查找
• Spring 3.0 獲取標註類型Bean 名稱列表
• getBeanNamesForAnnotation(Class<? extends Annotation>)
• Spring 3.0 獲取標註類型Bean 實例列表
• getBeansWithAnnotation(Class<? extends Annotation>)
• Spring 3.0 獲取指定名稱+ 標註類型Bean 實例
• findAnnotationOnBean(String,Class<? extends Annotation>)
接口方法:
獲取同類型Bean 名稱列表 :getBeanNamesForType(Class) 根據類型查找beanName,其實是從BeanDefinition 查找類型去匹配,BeanDefinition特點bean的定義原信息並不是bean已經被初始化了。
獲取同類型Bean 實例列表 :getBeansOfType(Class) ,可能會導致bean提前初始化,導致信息不全出現問題。
所以一般在判斷時候首先要根據名字去判斷,在根據類型去判斷。
@Configuration
public class ObjectProviderDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 註冊 BeanDefinition 將當前類 ObjectProviderDemo 作爲配置類(Configuration Class)
applicationContext.register(ObjectProviderDemo.class);
//啓動 容器上下文
applicationContext.refresh();
// 按照類型查找集合對象
lookupCollectionByType(applicationContext);
//關閉
applicationContext.close();
}
private static void lookupCollectionByType(AnnotationConfigApplicationContext applicationContext) {
if(applicationContext instanceof ListableBeanFactory){
ListableBeanFactory listableBeanFactory = applicationContext;
String[] resultString = listableBeanFactory.getBeanNamesForType(String.class);
Map<String, String> result = listableBeanFactory.getBeansOfType(String.class);
Arrays.asList(resultString).forEach(System.out::println);
System.out.println("查找到的所有的 String 集合對象:" + result);
}
}
@Bean
public String helloWorld() { // 方法名就是 Bean 名稱 = "helloWorld"
return "Hello,World";
}
@Bean
public String message() {
return "Message";
}
}
3.3、層次性依賴查找接口-HierarchicalBeanFactory
HierarchicalBeanFactory接口提供 獲取父容器的方法, 這個機制類似與雙親委派機制。只有繼承他的都要層次結構。
子接口:ConfigurableBeanFactory:可配置的接口。
ConfigurableListableBeanFactory接口 是使用組合模塊。他具有 集合、層次、自動注入的功能。
AbstractBeanFactory中containsBean也有對父容器的檢查,遞歸的實現。
HierarchicalBeanFactory <- ConfigurableBeanFactory <- ConfigurableListableBeanFactory
public interface ConfigurableListableBeanFactory extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory
實例代碼
/**
* 層次性依賴查找示例
* Created by dukun on 2020/7/2.
*/
@Configuration
public class HierarchicalDependencyLookupDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 將當前類 ObjectProviderDemo 作爲配置類(Configuration Class)
applicationContext.register(ObjectProviderDemo.class);
// 1. 獲取 HierarchicalBeanFactory <- ConfigurableBeanFactory <- ConfigurableListableBeanFactory
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
// 2. 設置 Parent BeanFactory
HierarchicalBeanFactory parentBeanFactory = createParentBeanFactory();
beanFactory.setParentBeanFactory(parentBeanFactory);
//判斷本地容器是否包含 bean
displayContainsLocalBean(beanFactory, "user");//這個應該是不包含
displayContainsLocalBean(parentBeanFactory, "user");//這個本地容器包含
System.out.println("==============================================");
displayContainsBean(beanFactory, "user");
}
//判斷本地容器是否包含 bean
private static void displayContainsLocalBean(HierarchicalBeanFactory beanFactory, String beanName) {
System.out.printf("當前 BeanFactory[%s] 是否包含 Local Bean[name : %s] : %s\n", beanFactory, beanName,
beanFactory.containsLocalBean(beanName));
}
//判斷是否bean
private static void displayContainsBean(HierarchicalBeanFactory beanFactory, String beanName) {
System.out.printf("當前 BeanFactory[%s] 是否包含 Bean[name : %s] : %s\n", beanFactory, beanName,
containsBean(beanFactory, beanName));
}
//遞歸判斷父容器或者本地容器是否包含bean
private static boolean containsBean(HierarchicalBeanFactory beanFactory, String beanName) {
BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();
if (parentBeanFactory instanceof HierarchicalBeanFactory) {
HierarchicalBeanFactory parentHierarchicalBeanFactory = HierarchicalBeanFactory.class.cast(parentBeanFactory);
if (containsBean(parentHierarchicalBeanFactory, beanName)) {
return true;
}
}
return beanFactory.containsLocalBean(beanName);
}
//創建父容器
private static HierarchicalBeanFactory createParentBeanFactory() {
// 創建 BeanFactory 容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
// XML 配置文件 ClassPath 路徑
String location = "classpath:/META-INF/dependency-lookup-context.xml";
// 加載配置
reader.loadBeanDefinitions(location);
return beanFactory;
}
}
問題:基本都是單一查找,依賴查找可以在LB這種場景應用,但這個分層查找有啥用?主要複用bean
比如 Spring MVC 中,Biz 組件放在 Root ApplicationContext,而 Web 組件放在 DispatcherServlet 的 ApplicationContext,後者是前者的子 ApplicationContext,所以,子 ApplicationContext 可以讀取父 ApplicationContext。
容器的層次關係主要的目的是實現 Bean 複用,假設一個應用存在一個 Root ApplicationContext,內部的 Bean 來自於一個 jar 包,那麼,這個jar 包可能被兩個不同的 Servlet 應用使用,這時,ServletContext A 和 ServletContext B 同時複用了這個 parent ApplicationContext,而它自己也有 ApplicationContext,這也是 Spring Web MVC 所涉及的應用上下文架構。
3.4、Bean 延遲依賴查找接口
延遲是指非一次性獲得
• org.springframework.beans.factory.ObjectFactory
• org.springframework.beans.factory.ObjectProvider
• Spring 5 對Java 8 特性擴展
• 函數式接口
• getIfAvailable(Supplier)
• ifAvailable(Consumer)
• Stream 擴展-stream()
getBeanProvider方法 爲ObjectProvider 中的 其又繼承 ObjectFactory。
延遲查找(延遲是指非一次性獲得 ):主要用於暫時性地獲取某個 Bean Holder 對象,如果過早的加載,可能會引起未知的狀態,比如,當 A bean 依賴 B bean 時,如果過早地初始化 A,那麼 B bean 裏面的狀態可能是中間狀態,這樣容易導致一些錯誤。
ObjectProvider 對象獲取時,並沒有獲取實際 Bean 的對象,而是在 getObject() 或其他獲取方法時才獲取。
/**
* 通過 {@link ObjectProvider} 進行依賴查找
* Created by dukun on 2020/7/2.
*/
@Configuration
public class ObjectProviderDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 註冊 BeanDefinition 將當前類 ObjectProviderDemo 作爲配置類(Configuration Class)
applicationContext.register(ObjectProviderDemo.class);
//啓動 容器上下文
applicationContext.refresh();
//單一類型的延遲查找
lookupByObjectProvider(applicationContext);
//關閉
applicationContext.close();
}
//ObjectProvider 繼承 ObjectFactory
private static void lookupByObjectProvider(AnnotationConfigApplicationContext applicationContext) {
ObjectProvider<String> objectProvider = applicationContext.getBeanProvider(String.class);
System.out.println(objectProvider.getObject());
}
private static void lookupByStreamOps(AnnotationConfigApplicationContext applicationContext) {
ObjectProvider<String> objectProvider = applicationContext.getBeanProvider(String.class);
objectProvider.stream().forEach(System.out::println);
}
@Bean
public String helloWorld() { // 方法名就是 Bean 名稱 = "helloWorld"
return "Hello,World";
}
}
3.6 依賴查找中的經典異常
BeansException 子類型
異常類型 | 觸發條件(舉例) | 場景舉例 |
---|---|---|
NoSuchBeanDefinitionException | 當查找Bean 不存在於IoC 容器時 | BeanFactory#getBean ObjectFactory#getObject |
NoUniqueBeanDefinitionException |
類型依賴查找時,IoC 容器存在多 個Bean 實例 | BeanFactory#getBean(Class) |
BeanInstantiationException | 當Bean 所對應的類型非具體類時 | BeanFactory#getBean |
BeanCreationException | 當Bean 初始化過程中 | Bean 初始化方法執行異常 時 |
BeanDefinitionStoreException | 當BeanDefinition 配置元信息非法 時 | XML 配置資源無法打開時 |
安全依賴查找(這裏安全是指是否會報異常)
內建可查找的依賴
• AbstractApplicationContext 內建可查找的依賴
• 註解驅動Spring 應用上下文內建可查找的依賴(部分)
註解驅動Spring 應用上下文內建可查找的依賴(續)
四、Spring IoC 依賴注入
直接在容器啓動時通過構造器,getter setter等形式注入依賴。
優點:性能高,侵入小
兩者區別:區別在於依賴的對象是否爲主動獲取,是的話,就是依賴查找,否則就是依賴注入,由框架綁定完成。