1. 程式人生 > >Spring Boot 註解之ObjectProvider原始碼追蹤

Spring Boot 註解之ObjectProvider原始碼追蹤

最近依舊在學習閱讀Spring Boot的原始碼,在此過程中涉及到很多在日常專案中比較少見的功能特性,對此深入研究一下,也挺有意思,這也是閱讀原始碼的魅力之一。這裡寫成文章,分享給大家。

自動配置中的ObjectProvider

在閱讀Spring Boot自動配置原始碼中關於Tomcat的配置時,看到這樣如下的自動配置配置原始碼。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({Servlet.class,Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
            ObjectProvider<TomcatContextCustomizer> contextCustomizers,
            ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
        // ...
    }
}

這就是一個常規的基於Java的配置類,那麼你是否發現它在用法與其他的有所不同?是的,那就是三個ObjectProvider的引數。這也是本文要講的內容。

Spring的注入

在介紹ObjectProvider的使用之前,我們先來回顧一下注入相關的知識。

在Spring的使用過程中,我們可以通過多種形式將一個類注入到另外一個類當中,比如通過@Autowired和@Resources註解。

而@Autowired又可以註解在不同的地方來達到注入的效果,比如註解在建構函式上:

@Service
public class FooService {
    private final FooRepository repository;
    @Autowired
    public FooService(FooRepository repository) {
        this.repository = repository
    }
}

註解在屬性上:

@Service
public class FooService {
    @Autowired
    private final FooRepository repository;
}

註解在setter方法上:

@Service
public class FooService {
    private final FooRepository repository;
    @Autowired
    public void setFooRepository(FooRepository repository) {
        this.repository = repository
    }
}

spring4.3新特性

上面是最常見的注入方式,如果忘記寫@Autowired註解,那麼在啟動的時候就會丟擲異常。

但在spring 4.3之後,引入了一個新特性:當構造方法的引數為單個構造引數時,可以不使用@Autowired進行註解。

因此,上面的程式碼可變為如下形式:

@Service
public class FooService {
    private final FooRepository repository;
    public FooService(FooRepository repository) {
        this.repository = repository
    }
}

使用此種形式便會顯得優雅一些。該特性,在Spring Boot的自動配置類中大量被使用。

依賴關係的改進

同樣是在Spring 4.3版本中,不僅隱式的注入了單構造引數的屬性。還引入了ObjectProvider介面。

ObjectProvider介面是ObjectFactory介面的擴充套件,專門為注入點設計的,可以讓注入變得更加寬鬆和更具有可選項。

那麼什麼時候使用ObjectProvider介面?

如果待注入引數的Bean為空或有多個時,便是ObjectProvider發揮作用的時候了。

如果注入例項為空時,使用ObjectProvider則避免了強依賴導致的依賴物件不存在異常;如果有多個例項,ObjectProvider的方法會根據Bean實現的Ordered介面或@Order註解指定的先後順序獲取一個Bean。從而了提供了一個更加寬鬆的依賴注入方式。

Spring 5.1之後提供了基於Stream的orderedStream方法來獲取有序的Stream的方法。

使用ObjectProvider之後,上面的程式碼便變為如下方式:

@Service
public class FooService {
    private final FooRepository repository;
    public FooService(ObjectProvider<FooRepository> repositoryProvider) {
        this.repository = repositoryProvider.getIfUnique();
    }
}

這樣的好處很顯然,當容器中不存在FooRepository或存在多個時,可以從容處理。但壞處也很明顯,如果FooRepository不能為null,則可能將異常從啟動階段轉移到業務執行階段。

ObjectProvider原始碼

ObjectProvider的原始碼及解析如下:

public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {

    // 返回指定型別的bean, 如果容器中不存在, 丟擲NoSuchBeanDefinitionException異常
    // 如果容器中有多個此型別的bean, 丟擲NoUniqueBeanDefinitionException異常
    T getObject(Object... args) throws BeansException;

    // 如果指定型別的bean註冊到容器中, 返回 bean 例項, 否則返回 null
    @Nullable
    T getIfAvailable() throws BeansException;

    // 如果返回物件不存在,則進行回撥,回撥物件由Supplier傳入
    default T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {
        T dependency = getIfAvailable();
        return (dependency != null ? dependency : defaultSupplier.get());
    }

     // 消費物件的一個例項(可能是共享的或獨立的),如果存在通過Consumer回撥消耗目標物件。
    default void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {
        T dependency = getIfAvailable();
        if (dependency != null) {
            dependencyConsumer.accept(dependency);
        }
    }

    // 如果不可用或不唯一(沒有指定primary)則返回null。否則,返回物件。
    @Nullable
    T getIfUnique() throws BeansException;

    // 如果存在唯一物件,則呼叫Supplier的回撥函式
    default T getIfUnique(Supplier<T> defaultSupplier) throws BeansException {
        T dependency = getIfUnique();
        return (dependency != null ? dependency : defaultSupplier.get());
    }

    // 如果存在唯一物件,則消耗掉該物件
    default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
        T dependency = getIfUnique();
        if (dependency != null) {
            dependencyConsumer.accept(dependency);
        }
    }

    // 返回符合條件的物件的Iterator,沒有特殊順序保證(一般為註冊順序)
    @Override
    default Iterator<T> iterator() {
        return stream().iterator();
    }

    // 返回符合條件物件的連續的Stream,沒有特殊順序保證(一般為註冊順序)
    default Stream<T> stream() {
        throw new UnsupportedOperationException("Multi element access not supported");
    }

    // 返回符合條件物件的連續的Stream。在標註Spring應用上下文中採用@Order註解或實現Order介面的順序
    default Stream<T> orderedStream() {
        throw new UnsupportedOperationException("Ordered element access not supported");
    }
}

其中,在BeanFactory中也使用了該介面來定義方法的返回值:

public interface BeanFactory {

    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
    ...
}

至此,關於ObjectProvider的使用和原始碼解析完成。

原文連結:《SPRING BOOT 註解之OBJECTPROVIDER原始碼追蹤》


程式新視界:精彩和成長都不容錯過

相關推薦

Spring Boot 註解ObjectProvider原始碼追蹤

最近依舊在學習閱讀Spring Boot的原始碼,在此過程中涉及到很多在日常專案中比較少見的功能特性,對此深入研究一下,也挺有意思,這也是閱讀原始碼的魅力之一。這裡寫成文章,分享給大家。 自動配置中的ObjectProvider 在閱讀Spring Boot自動配置原始碼中關於Tomcat的配置時,看到這樣如

Spring Boot 學習路——4 AOP註解方式實現列印日誌

前言:據XX統計,四分之一的程式碼都是日誌有關,日誌對於定位和解決問題尤為重要,以前公司的編碼規範中要求介面必須在日誌中記錄入參和返回值以及關鍵程式碼,引數部分完全可以用Spring的AOP——面向切面來實現。什麼叫AOP?百度:AOP(Aspect Oriented Pro

Spring Boot 學習路——4.1 AOP註解方式實現列印日誌 詳解

以下內容轉自:https://www.cnblogs.com/lixiang1993/p/7447853.html1.宣告一個切面類,並把這個切面類加入到IOC容器中@Component@Aspectpublic class LogAspect{    @Pointcut(v

Spring Boot註解方式集成Mybatis

face batis update pac conn src jar包 local code 一、無配置文件註解版 1.pom文件必要jar包的引入 1 <dependency> 2 <groupId>mysql</groupId>

spring boot 學習路3( 集成mybatis )

sys pat min lba asn ria [] system emp 下面就簡單來說一下spring boot 與mybatiis的整合問題,如果你還沒學習spring boot的註解的話,要先去看spring boot的註解 好了,現在讓我們來搞一下與mybat

Spring Boot筆記自定義啟動banner

bottom rule mage ack eight ooo manifest log blank 控制banner內容 Spring Boot啟動的時候默認的banner是spring的字樣,看多了覺得挺單調的,Spring Boot為我們提供了自定義banner的功

Spring Boot 成長路(一) 快速上手

啟動引導 pom.xml relative 技術 build sans hot clas 1.2 1.創建工程 利用IntelliJ IDEA新建一個Spring Boot項目的Web工程 2.查看初始化的spring boot項目 工程建好之後會出現如下的目錄

Spring Boot 入門持久層篇(三)

imp 配置文件 bat catch map ann 文件 save values 原文地址:Spring Boot 入門之持久層篇(三) 博客地址:http://www.extlight.com 一、前言 上一篇《Spring Boot 入門之 Web 篇(二)》介紹

Spring Boot實戰逐行釋義HelloWorld

runtime ica can pri source 訪問 exclude 這樣的 gradle 一、前言    研究Spring boot也有一小段時間了,最近會將研究東西整理一下給大家分享,大概會有10~20篇左右的博客,整個系列會以一個簡單的博客系統作為基礎,因為光

Spring Boot實戰數據庫操作

應該 element face 插入 sele run 方式 不同 pan   上篇文章中已經通過一個簡單的HelloWorld程序講解了Spring boot的基本原理和使用。本文主要講解如何通過spring boot來訪問數據庫,本文會演示三種方式來訪問數據庫,第一種是

spring boot 註解大全

生成策略 sequence urn basic in-memory 任務 轉發 col tom [springBoot系列]--springBoot註解大全 一、註解(annotations)列表 @SpringBootApplication:包含了@ComponentS

Java 小記 — Spring Boot 註解

控制 配置 scan 復雜 () 很好 查看源碼 回顧 www. 前言 本篇隨筆將對 Spring Boot 中的常用註解做一個簡單的整理歸檔,寫作順序將從啟動類開始並逐步向內外擴展,目的即為了分享也為了方便自己日後的回顧與查閱。 1. Application 啟動類示例

Spring Boot註解說明

掃描 目的 組成 OS resp red div app 使用 Spring Boot使用“習慣優於配置”的理念使項目快速運行起來,這些項目都是基於spring框架的,可以不用或者使用很少的Spring配置。 1、@SpringBootApplication:Spring

spring boot 系列三:spring boot 整合JdbcTemplate

closed com context boot pin pan url wired ace 前面兩篇文章我們講了兩件事情: 通過一個簡單實例進行spring boot 入門 修改spring boot 默認的服務端口號和默認context path 這篇文章我們來看下怎

spring boot 系列四:spring boot 整合JPA

rom prop pos output UNC actor href ali div 上一篇我們講了spring boot 整合JdbcTemplate來進行數據的持久化, 這篇我們來說下怎麽通過spring boot 整合JPA來實現數據的持久化。 一、代碼實現  修改

spring boot DAOHibernate

spring boot依賴 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-star

spring boot DAOjdbcTemplate

springboot依賴 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-start

spring boot DAOMybatis

spring boot依賴 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-star

spring boot入門——2.0新特性以及模塊化構建

獲取 所有 請求 get請求 異步編程 底層 framwork 編程 add 一、新特性依賴java 8+支持Kotlin語言,主要底層框架采用了(Spring Framwork 5.0X)支持全新特性:Web Flux(一種新的編程模型,是對傳統的Spring MVC做了

spring boot入門——熱部署

配置 xtend java類 throw not catch arr 直接 路徑 場景:本地調試(頻繁的啟動/停止服務器)線上發布(每次都需要啟動/停止服務器)優點:無論本地還是線上,都適用無需重啟服務器,提高開發、調試效率;提升發布、運維效率,降低運維成本java實現熱部