夯實Spring系列|第七章:IoC 依賴查找(專題)
文章目錄
本章說明
通過 第二章:IoC 依賴查找 ,我們已經簡單瞭解到 Spring 中依賴查找的基本使用;本章會更加全面和具體的討論 Spring IoC 依賴查找,並結合少量的源碼分析。
1.項目環境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模塊:dependency-lookup
2.依賴查找的前世今生
在 Spring 之前,其實 JavaBeans 和 JNDI 也有相關的實現
單一類型依賴查找
- JNDI - javax.naming.Context#lookup(javax.naming.Name)
- JavaBeans - java.beans.beancontext.BeanContext
集合類型依賴查找
- java.beans.beancontext.BeanContext
層次性依賴查找
- java.beans.beancontext.BeanContext
具體的源碼我們就不在此進行展開,Spring 中實現也是借鑑的他們的設計,而且比他們做的更好,我們將學習重點放在下面 Spring 中即可。
3.單一類型依賴查找
單一類型依賴查找接口 - BeanFactory
- 根據 Bean 名稱查找
- getBean(String)
- Spring 2.5- getBean(String name, Object… args) 覆蓋默認參數
- 根據 Bean 類型查找
- Bean 實時查找
- Spring 3.0 - getBean(Class requiredType)
- Spring 4.1 - getBean(Class requiredType, Object… args) 覆蓋默認參數
- Spring 5.1 Bean 延遲查找
- getBeanProvider(Class requiredType)
- getBeanProvider(ResolvableType requiredType)
- Bean 實時查找
- 根據 Bean 名稱 + 類型 查找:getBean(String name, Class requiredType)
覆蓋參數是什麼意思呢?
比如 getBean()
獲取這個 Bean 對象,通過覆蓋參數的形式可以在獲取對象的同時覆蓋掉對象的參數,建議大家不要去使用這種方式,比較危險。
如果 getBean()
返回的對象是一個單例對象,然後每調用一次,都會去覆蓋,這種方式非常不可取,獲得的對象的狀態不可控。
3.1 根據 Bean 名稱查找
第二章:IoC 依賴查找 4.3~4.4 小結
3.2 根據 Bean 類型查找
第二章:IoC 依賴查找 4.5~4.6 小結
3.3 根據 Bean 名稱 + 類型 查找
第二章:IoC 依賴查找 4.7 小結
3.4 Spring 5.1 Bean 延遲查找
在第二章裏面演示了 ObjectFactory
來進行延遲查找,我們這裏用 Spring 5.1 裏面的提供新 API
ObjectProvider
來演示。
注:示例中註釋掉代碼中是另一種寫法
/**
* 通過{@link org.springframework.beans.factory.ObjectProvider} 進行依賴查找
*/
public class ObjectProviderDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 將當前類作爲配置類{Configuration.class}
applicationContext.register(ObjectProviderDemo.class);
//啓動應用上下文
applicationContext.refresh();
lookupByObjectProvider(applicationContext);
applicationContext.close();
}
private static void lookupByStreamOps(AnnotationConfigApplicationContext applicationContext) {
ObjectProvider<String> beanProvider = applicationContext.getBeanProvider(String.class);
// Iterable<String> stringIterable = beanProvider;
// for (String str : stringIterable) {
// System.out.println(str);
// }
beanProvider.stream().forEach(System.out::println);
}
@Bean
//如果沒有定義name 那麼方法名就是 Bean 的名稱
public String helloWorld() {
return "Hello,World";
}
}
4.集合類型依賴查找
集合類型依賴查找接口 - ListableBeanFactory
- 根據 Bean 類型查找
- 獲取同類型 Bean 名稱列表
- getBeanNamesForType(Class)
- Spring 4.2 - getBeanNamesForType(ResolvableType)
- 主要應用於泛型上面的實現
- 獲取同類型 Bean 實例列表
- getBeansOfType(Class) 以及重載方法
- 獲取同類型 Bean 名稱列表
- 通過註解類型查找
- 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>)
- Spring 3.0 - 獲取標註類型 Bean 名稱列表
相關示例
第二章:IoC 依賴查找 4.6、4.8 小結
5.層次性依賴查找
層次性依賴查找接口 - HierarchicalBeanFactory
- 雙親 BeanFactory : getParentBeanFactory()
- 層次性查找
- 根據 Bean 名稱查找
- 基於 containsLocalBean 方法實現
- 根據 Bean 類型查找實例列表
- 單一類型:BeanFactoryUtils#beanOfType
- 集合類型:BeanFactoryUtils#beansOfTypeIncludingAncestors
- 根據 Java 註解查找名稱列表
- BeanFactoryUtils#beanNamesForTypeIncludingAncestors
- 根據 Bean 名稱查找
5.1 源碼分析
我們先看一個相關的類圖
結合部分源碼
HierarchicalBeanFactory#getParentBeanFactory() 方法表明了他具有層次性,其實是一種雙親委派模式的實現。
ConfigurableBeanFactory 繼承了 HierarchicalBeanFactory,從而也具有了層次性
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {...
ConfigurableListableBeanFactory 又繼承了 ConfigurableBeanFactory,通過這種組合的方式使得 ConfigurableListableBeanFactory
具有了可配置性(Configurable)、集合遍歷性(Listable)、層次性(Hierarchical)等等,下面我們用這個層次性實現類來演示相關代碼。
public interface ConfigurableListableBeanFactory
extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory {...
5.2 ParentBeanFactory 示例
設置當前 BeanFactory 的 Parent BeanFactory
/**
* 層次性的依賴查找示例
*/
public class HierarchicalDependencyLookupDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 將當前類作爲配置類{Configuration.class}
applicationContext.register(HierarchicalDependencyLookupDemo.class);
//1.獲取 HierarchicalBeanFactory <- ConfigurableBeanFatory <- ConfigurableListableBeanFactory
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
System.out.println("當前 BeanFactory 的 Parent BeanFactory" + beanFactory.getParentBeanFactory());
//設置 Parent BeanFactory
beanFactory.setParentBeanFactory(createParentBeanFactory());
System.out.println("當前 BeanFactory 的 Parent BeanFactory" + beanFactory.getParentBeanFactory());
//啓動應用上下文
applicationContext.refresh();
//關閉
applicationContext.close();
}
private static BeanFactory createParentBeanFactory() {
// 創建 BeanFactory 容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 加載配置
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
String location = "classpath:/META-INF/dependency-injection-context.xml";
reader.loadBeanDefinitions(location);
return beanFactory;
}
}
執行結果
當前 BeanFactory 的 Parent BeanFactory:null
當前 BeanFactory 的 Parent BeanFactory:org.springframework.beans.factory.support.DefaultListableBeanFactory@3427b02d: defining beans [user,superUser,objectFactory,userRepository]; root of factory hierarchy
第一次輸出的結果是 null ,因爲我們還沒有設置過當前 BeanFactory 的 Parent BeanFactory。
我們創建一個新的 BeanFactory,通過 beanFactory.setParentBeanFactory()
方法進行設置。
第二次輸出的就是我們設置的 BeanFactory。
5.3 LocalBean 示例
判斷當前 BeanFactory 是否包含這個 Bean
在上述例子中增加這個方法
private static void displayLocalBean(HierarchicalBeanFactory beanFactory, String beanName) {
System.out.printf("當前 BeanFactory [%s] 是否包含 bean[name:%s] \n 結果: %s\n", beanFactory, beanFactory,
beanFactory.containsLocalBean(beanName));
}
main()
方法中調用displayLocalBean(beanFactory, "user");
執行結果
當前 BeanFactory [org.springframework.beans.factory.support.DefaultListableBeanFactory@32eff876: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,hierarchicalDependencyLookupDemo]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@3427b02d] 是否包含 bean[name:org.springframework.beans.factory.support.DefaultListableBeanFactory@32eff876: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,hierarchicalDependencyLookupDemo]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@3427b02d]
結果: false
當然不存在 user 這個 Bean。因爲這個 Bean 存在父 BeanFactory 中
在main()
方法中調用displayLocalBean((HierarchicalBeanFactory) beanFactory.getParentBeanFactory(), "user");
結果爲 true;爲了更優雅的調用,我們可以採用遞歸的方式進行查詢。
private static void displayContainsBean(HierarchicalBeanFactory beanFactory, String beanName) {
System.out.printf("當前 BeanFactory [%s] 是否包含 bean[name:%s] \n 結果: %s\n", beanFactory, beanFactory,
containsBean(beanFactory,beanName));
}
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);
}
5.4 BeanFactoryUtils
Spring 中也有類似的實現,也是通過遞歸的方式。
org.springframework.beans.factory.BeanFactoryUtils#beanNamesForTypeIncludingAncestors()
public static String[] beanNamesForTypeIncludingAncestors(ListableBeanFactory lbf, ResolvableType type) {
Assert.notNull(lbf, "ListableBeanFactory must not be null");
String[] result = lbf.getBeanNamesForType(type);
if (lbf instanceof HierarchicalBeanFactory) {
HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
String[] parentResult = beanNamesForTypeIncludingAncestors(
(ListableBeanFactory) hbf.getParentBeanFactory(), type);
result = mergeNamesWithParent(result, parentResult, hbf);
}
}
return result;
}
6.延遲依賴查找
Bean 延遲依賴查找接口
- org.springframework.beans.factory.ObjectFactory
- org.springframework.beans.factory.ObjectProvider
- spring 5 對 Java 8 特性擴展
- 函數式接口
- getIfAvailable(Supplier)
- ifAvailable(Consumer)
- Stream 擴展 - stream()
- 函數式接口
- spring 5 對 Java 8 特性擴展
6.1 getIfAvailable
我們先在代碼中通過註解定義一個 User
@Bean
public User user() {
return User.createUser("ifAvailable-user");
}
下面方法中User::createUser
只是提供兜底實現(當獲取的對象爲空時),也可以不寫。
private static void lookupGetIfAvailable(AnnotationConfigApplicationContext applicationContext) {
ObjectProvider<User> beanProvider = applicationContext.getBeanProvider(User.class);
// User ifAvailable = beanProvider.getIfAvailable(()->User.createUser());
User ifAvailable = beanProvider.getIfAvailable(User::createUser);
System.out.println(ifAvailable);
}
6.2 ifAvailable
通過 Consumer 的方式消費掉,我們這裏直接打印
private static void lookupIfAvailable(AnnotationConfigApplicationContext applicationContext) {
ObjectProvider<User> beanProvider = applicationContext.getBeanProvider(User.class);
beanProvider.ifAvailable(System.out::println);
}
6.3 Stream 擴展
private static void lookupByStreamOps(AnnotationConfigApplicationContext applicationContext) {
ObjectProvider<String> beanProvider = applicationContext.getBeanProvider(String.class);
// Iterable<String> stringIterable = beanProvider;
// for (String str : stringIterable) {
// System.out.println(str);
// }
beanProvider.stream().forEach(System.out::println);
}
7.安全依賴查找
依賴查找安全性對比
依賴查找類型 | 代表實現 | 是否安全 |
---|---|---|
單一類型查找 | BeanFactory#getBean | 否 |
ObjectFactroy#getObject | 否 | |
ObjectProvider#getIfAvailable | 是 | |
集合類型查找 | ListableBeanFactory#getBeansOfType | 是 |
ObjectProvider#stream | 是 |
注意:層次性依賴查找的安全性取決於其擴展的單一或者集合類型的 BeanFactory 接口
完整示例 TypeSafetyDependencyLookupDemo
/**
* 類型安全 依賴查找示例
*/
public class TypeSafetyDependencyLookupDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 將當前類作爲配置類{Configuration.class}
applicationContext.register(TypeSafetyDependencyLookupDemo.class);
//啓動應用上下文
applicationContext.refresh();
//演示 BeanFactory#getBean 安全性
displayBeanFactoryGetBean(applicationContext);
//演示 ObjectFactory#getObject 安全性
displayObjectFactoryGetObject(applicationContext);
//演示 ObjectProvider#ifAvailable 安全性
displayObjectProviderIfAvailable(applicationContext);
//演示 ListableBeanFactory#getBeansOfType 安全性
displayListableBeanFactoryGetBeansOfType(applicationContext);
//演示 ObjectProvider#stream 安全性
displayObjectProviderStreamOps(applicationContext);
applicationContext.close();
}
private static void displayObjectProviderStreamOps(AnnotationConfigApplicationContext applicationContext) {
printBeansException("displayObjectProviderStreamOps",()->{
ObjectProvider<User> beanProvider = applicationContext.getBeanProvider(User.class);
beanProvider.stream().forEach(System.out::println);
});
}
private static void displayListableBeanFactoryGetBeansOfType(ListableBeanFactory applicationContext) {
printBeansException("displayListableBeanFactoryGetBeansOfType",()->{
applicationContext.getBeansOfType(User.class);
});
}
private static void displayObjectProviderIfAvailable(AnnotationConfigApplicationContext applicationContext) {
printBeansException("displayObjectProviderIfAvailable", () -> {
ObjectProvider<User> beanProvider = applicationContext.getBeanProvider(User.class);
System.out.println(beanProvider.getIfAvailable(User::createUser));
});
}
private static void displayObjectFactoryGetObject(AnnotationConfigApplicationContext applicationContext) {
printBeansException("displayObjectFactoryGetObject", () -> {
ObjectFactory<User> beanProvider = applicationContext.getBeanProvider(User.class);
beanProvider.getObject();
});
}
private static void displayBeanFactoryGetBean(BeanFactory beanFactory) {
printBeansException("displayBeanFactoryGetBean", () -> beanFactory.getBean(User.class));
}
private static void printBeansException(String source, Runnable runnable) {
System.err.println("===========================" );
System.err.println("Source from : " + source);
try {
runnable.run();
} catch (BeansException e) {
e.printStackTrace();
}
}
}
7.1 BeanFactory#getBean(不安全)
如果上下文中沒有 User 這個Bean類型的定義,會拋出NoSuchBeanDefinitionException
輸出結果:
Source from : displayBeanFactoryGetBean
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.huajie.thinking.in.spring.ioc.overview.domain.User' available
...
7.2 ObjectFactroy#getObject(不安全)
如果上下文中沒有 User 這個Bean類型的定義,會拋出NoSuchBeanDefinitionException
輸出結果:
Source from : displayObjectFactoryGetObject
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.huajie.thinking.in.spring.ioc.overview.domain.User' available
...
7.3 ObjectProvider#getIfAvailable(安全)
即使如果上下文中沒有 User 這個Bean類型的定義,也不會報錯
輸出結果:
Source from : displayObjectProviderIfAvailable
7.4 ListableBeanFactory#getBeansOfType(安全)
即使如果上下文中沒有 User 這個Bean類型的定義,也不會報錯
輸出結果:
Source from : displayListableBeanFactoryGetBeansOfType
7.5 ObjectProvider#stream(安全)
即使如果上下文中沒有 User 這個Bean類型的定義,也不會報錯
輸出結果:
Source from : displayObjectProviderStreamOps
8.內建可查找的依賴
本章只是做介紹,後續的章節會做詳細的討論。
AbstractApplicationContext 內建可查找的依賴
Bean 名稱 | Bean 實例 | 使用場景 |
---|---|---|
environment | Environment 對象 | 外部化配置以及 Profiles |
systemProperties | java.util.Properties 對象 | Java 系統屬性 |
systemEnvironment | java.util.Map 對象 | 操作系統環境變量 |
messageSource | MessageSource 對象 | 國際化文案 |
lifecycleProcessor | LifecycleProcessor 對象 | Lifecycle Bean 處理器 |
applicationEventMulticaster | ApplicationEventMulticaster 對象 | Spring 事件廣播 |
註解驅動 Spring 應用上下文內建可查找的依賴(部分)
相關細節:
org.springframework.context.annotation.AnnotationConfigUtils
- registerAnnotationConfigProcessors()
Bean 名稱 | Bean 實例 | 使用場景 |
---|---|---|
org.springframework.context.annotation. internalConfigurationAnnotationProcessor | ConfigurationClassPostProcessor 對象 | 處理Spring配置類 |
org.springframework.context.annotation. internalAutowiredAnnotationProcessor | AutowiredAnnotationBeanPostProcessor對象 | 處理 @Autowired 以及 @Value 註解 |
org.springframework.context.annotation. internalCommonAnnotationProcessor | CommonAnnotationBeanPostProcessor對象 | (條件激活) 處理 JSR - 250 註解,比如 @PostConstruct |
org.springframework.context.event. internalEventListenerProcessor | EventListenerMethodProcessor對象 | 處理標準 @EventListener 的 Spring 事件監聽方法 |
org.springframework.context.event. internalEventListenerFactory | DefaultEventlistenerFactory 對象 | @EvenListener 事件監聽方法適配爲 ApplicationListener |
org.springframework.context.annotation. internalPersistenceAnnotationProcessor | PersistenceAnnotationBeanPostProcessor 對象 | (條件激活)處理 JPA 註解場景 |
9.依賴查找中的經典異常
BeansException 子類型
異常類型 | 觸發條件 | 場景舉例 |
---|---|---|
NoSuchBeanDefinitionException | 當查找 Bean 不存在與 IOC 容器時 | BeanFactory#getBean ObjectFactory#getObject |
NoUniqueBeanDefinitionException | 類型依賴查找時,IOC 容器存在多個 Bean 實例 | BeanFactory#getBean(Class) |
BeanInstantiationException | 當 Bean 所對應的類型非具體類時 | BeanFactory#getBean |
BeanCreationException | 當 Bean 初始化過程中 | Bean 初始化方法執行異常時 |
BeanDefinitionStoreException | 當 BeanDefinition 配置元信息非法時 | XML 配置資源無法打開時 |
9.1 NoUniqueBeanDefinitionException
當前上下文中存在多個相同類型 Bean 的定義
/**
* {@link NoUniqueBeanDefinitionException}示例
*/
public class NoUniqueBeanDefinitionExceptionDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 將當前類作爲配置類{Configuration.class}
applicationContext.register(NoUniqueBeanDefinitionExceptionDemo.class);
//啓動應用上下文
applicationContext.refresh();
try {
applicationContext.getBean(String.class);
} catch (NoUniqueBeanDefinitionException e) {
System.err.printf(" Spring 應用上下文存在%d個 %s 類型的 Bean,具體原因:%s",
e.getNumberOfBeansFound(),
String.class.getName(),
e.getMessage());
}
applicationContext.close();
}
@Bean
private String bean1() {
return "1";
}
@Bean
private String bean2() {
return "2";
}
@Bean
private String bean3() {
return "3";
}
}
9.2 BeanInstantiationException
當 Bean 所對應的類型非具體類時,比如是一個接口CharSequence
/**
* {@link BeanInstantiationException}示例
*/
public class BeanInstantiationExceptionDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CharSequence.class);
applicationContext.registerBeanDefinition("exception-bean", beanDefinitionBuilder.getBeanDefinition());
//啓動應用上下文
applicationContext.refresh();
applicationContext.close();
}
}
9.4 BeanCreationException
Bean 創建過程中發生異常
/**
* {@link BeanCreationException}示例
*/
public class BeanCreationExceptionDemo {
public static void main(String[] args) {
// 創建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//在初始化的時候,拋出異常
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(POJO.class);
applicationContext.registerBeanDefinition("exception-bean", beanDefinitionBuilder.getBeanDefinition());
//啓動應用上下文
applicationContext.refresh();
applicationContext.close();
}
static class POJO implements InitializingBean{
@Override
public void afterPropertiesSet() throws Exception {
throw new Exception("For purposes...");
}
}
}
10.面試題
1.ObjectFactory 與 BeanFactory 區別?
兩者都是提供依賴查找的能力,ObjectFactory 是 Spring 早期的接口。
ObjectFactory 僅關注一個或者一種類型的 Bean 依賴查找,並且自身不具備依賴查找的能力,能力則由 BeanFactory 輸出,例如 ObjectFactoryCreatingFactoryBean 類 通過 setTargetBeanName 方法設置 beanName 再通過 getObject 方法,實際上還是通過 BeanFactory 來查找,通過這種方式實現一個延遲查找、間接查找。
public Object getObject() throws BeansException {
return this.beanFactory.getBean(this.targetBeanName);
}
BeanFactory 提供了單一類型,集合類型以及層次性等多種依賴查找方式。
2.BeanFactory.getBean 操作是否線程安全?
是線程安全,操作過程中會增加互斥鎖。
3.Spring 依賴查找與注入在來源上的區別?
這個問題後續章節會做解答,下一章將會討論依賴注入相關的內容。
11.參考
- 極客時間-小馬哥《小馬哥講Spring核心編程思想》