夯實Spring系列|第七章:IoC 依賴查找(專題)

夯實Spring系列|第七章:IoC 依賴查找(專題)

本章說明

通過 第二章:IoC 依賴查找 ,我們已經簡單瞭解到 Spring 中依賴查找的基本使用;本章會更加全面和具體的討論 Spring IoC 依賴查找,並結合少量的源碼分析。

1.項目環境

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 名稱 + 類型 查找: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) 以及重載方法
  • 通過註解類型查找
    • 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>)

相關示例

第二章:IoC 依賴查找 4.6、4.8 小結

5.層次性依賴查找

層次性依賴查找接口 - HierarchicalBeanFactory

  • 雙親 BeanFactory : getParentBeanFactory()
  • 層次性查找
    • 根據 Bean 名稱查找
      • 基於 containsLocalBean 方法實現
    • 根據 Bean 類型查找實例列表
      • 單一類型:BeanFactoryUtils#beanOfType
      • 集合類型:BeanFactoryUtils#beansOfTypeIncludingAncestors
    • 根據 Java 註解查找名稱列表
      • BeanFactoryUtils#beanNamesForTypeIncludingAncestors

5.1 源碼分析

我們先看一個相關的類圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fvxnoZvW-1587437109660)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200420211007120.png)]

結合部分源碼

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()

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核心編程思想》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章