20201108 小馬哥講Spring核心編程思想 - 筆記 1-11


第一章:Spring Framework總覽(Overview)

Spring Framework 版本 Java 標准版 Java 企業版
1.x 1.3+ J2EE 1.3 +
2.x 1.4.2+ J2EE 1.3 +
3.x 5+ J2EE 1.4 和 Java EE 5
4.x 6+ Java EE 6 和 7
5.x 8+ Java EE 7

Spring 編程模型:

  • 面向對象編程

    • 契約接口:Aware、BeanPostProcessor ...

    • 設計模式:觀察者模式、組合模式、模板模式 ...

    • 對象繼承:Abstract* 類

  • 面向切面編程

    • 動態代理:JdkDynamicAopProxy
    • 字節碼提升:ASM、CGLib、AspectJ...
  • 面向元編程

    • 注解:模式注解(@Component、@Service、@Respository ...)
    • 配置:Environment 抽象、PropertySources、BeanDefinition ...
    • 泛型:GenericTypeResolver、ResolvableType ...
  • 函數驅動

    • 函數接口:ApplicationEventPublisher
    • Reactive:Spring WebFlux
  • 模塊驅動

    • Maven Artifacts
    • OSGI Bundles
    • Java 9 Automatic Modules
    • Spring @Enable*

第二章:重新認識 IoC

IoC 容器的職責:

  • 通用職責
    • 依賴處理
      • 依賴查找
      • 依賴注入
    • 生命周期管理
      • 容器
      • 托管的資源(Java Beans 或其他資源)
    • 配置
      • 容器
      • 外部化配置
      • 托管的資源(Java Beans 或其他資源)

Spring 作為 IoC 容器的優勢:

  • 典型的 IoC 管理,依賴查找和依賴注入
  • AOP 抽象
  • 事務抽象
  • 事件機制
  • SPI 擴展
  • 強大的第三方整合
  • 易測試性
  • 更好的面向對象

第三章:Spring IoC 容器概述

Spring IoC 依賴查找:

  • 根據 Bean 名稱查找
    • 實時查找
    • 延遲查找
  • 根據 Bean 類型查找
    • 單個 Bean 對象
    • 集合 Bean 對象
  • 根據 Bean 名稱 + 類型查找
  • 根據 Java 注解查找
    • 單個 Bean 對象
    • 集合 Bean 對象

Spring IoC 依賴注入:

  • 根據 Bean 名稱注入
  • 根據 Bean 類型注入
    • 單個 Bean 對象
    • 集合 Bean 對象
  • 注入容器內建 Bean 對象
  • 注入非 Bean 對象
  • 注入類型
    • 實時注入
    • 延遲注入

延遲查找和延遲注入,使用到接口 org.springframework.beans.factory.ObjectProviderorg.springframework.beans.factory.ObjectFactoryObjectProvider 繼承 ObjectFactory

BeanFactoryApplicationContext 誰才是 Spring IoC 容器?

  • BeanFactory 是 Spring 底層 IoC 容器
  • ApplicationContext 是具備應用特性的 BeanFactory 超集
  • BeanFactory 是基本的 IoC 容器,ApplicationContext 實現 BeanFactory 接口,並在內部使用 ConfigurableListableBeanFactory 實現接口方法。

ApplicationContext 除了 IoC 容器角色,還有提供:

  • 面向切面(AOP)
  • 配置元信息(Configuration Metadata)
  • 資源管理(Resources)
  • 事件(Events)
  • 國際化(i18n)
  • 注解(Annotations)
  • Environment 抽象(Environment Abstraction)

第四章:Spring Bean 基礎

什么是 BeanDefinition

  • org.springframework.beans.factory.config.BeanDefinition
  • BeanDefinition 是 Spring Framework 中定義 Bean 的配置元信息接口,包含:
    • Bean 的類名
    • Bean 行為配置元素,如作用域、自動綁定的模式,生命周期回調等
    • 其他 Bean 引用,又可稱作合作者(collaborators)或者依賴(dependencies)
    • 配置設置,比如 Bean 屬性(Properties)
  • BeanDefinition 構建
    • 通過 BeanDefinitionBuilder
    • 通過 AbstractBeanDefinition 以及派生類

Bean 名稱生成器:org.springframework.beans.factory.support.BeanNameGenerator

注冊 Spring Bean:

  • BeanDefinition 注冊
    • XML 配置元信息
      • <bean name="..." ... />
    • Java 注解配置元信息
      • @Bean
      • @Component
      • @Import
    • Java API 配置元信息
      • 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
      • 非命名方式:BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,Be
        anDefinitionRegistry)
      • 配置類方式:AnnotatedBeanDefinitionReader#register(Class...)
  • 外部單例對象注冊
    • Java API 配置元信息
      • SingletonBeanRegistry#registerSingleton

Bean 實例化(Instantiation)

  • 常規方式
    • 通過構造器(配置元信息:XML、Java 注解和 Java API )
    • 通過靜態工廠方法(配置元信息:XML 和 Java API )
    • 通過 Bean 工廠方法(配置元信息:XML和 Java API )
    • 通過 FactoryBean(配置元信息:XML、Java 注解和 Java API )
  • 特殊方式
    • 通過 ServiceLoaderFactoryBean(配置元信息:XML、Java 注解和 Java API )
    • 通過 AutowireCapableBeanFactory#createBean(java.lang.Class, int, boolean)
    • 通過 BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)

Bean 初始化(Initialization),同時存在時,順序從上到下:

  • @PostConstruct 標注方法
  • 實現 InitializingBean 接口的 afterPropertiesSet() 方法
  • 自定義初始化方法(BeanDefinition
    • XML 配置:<bean init-method=”init” ... />
    • Java 注解:@Bean(initMethod=”init”)
    • Java API:AbstractBeanDefinition#setInitMethodName(String)

Bean 延遲初始化(Lazy Initialization)

  • XML 配置:<bean lazy-init=”true” ... />
  • Java 注解:@Lazy(true)

Bean 銷毀(Destroy),同時存在時,順序從上到下:

  • @PreDestroy 標注方法
  • 實現 DisposableBean 接口的 destroy() 方法
  • 自定義銷毀方法
    • XML 配置:<bean destroy=”destroy” ... />
    • Java 注解:@Bean(destroy=”destroy”)
    • Java API:AbstractBeanDefinition#setDestroyMethodName(String)

第五章:Spring IoC 依賴查找

單一類型依賴查找接口 - 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)

集合類型依賴查找接口 - 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>)

層次性依賴查找接口 - HierarchicalBeanFactory

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

Bean 延遲依賴查找接口

  • org.springframework.beans.factory.ObjectFactory
  • org.springframework.beans.factory.ObjectProvider
    • Spring 5 對 Java 8 特性擴展
      • 函數式接口
        • getIfAvailable(Supplier)
        • ifAvailable(Consumer)
      • Stream 擴展 - stream()

依賴查找安全性對比:

依賴查找類型 代表實現 是否安全
單一類型查找 BeanFactory#getBean
ObjectFactory#getObject
ObjectProvider#getIfAvailable
集合類型查找 ListableBeanFactory#getBeansOfType
ObjectProvider#stream

注意:層次性依賴查找的安全性取決於其擴展的單一或集合類型的 BeanFactory 接口

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 應用上下文內建可查找的依賴

Bean 名稱 Bean 實例 使用場景
org.springframework.context.annotation.internalConfigurationAnnotationProcessor ConfigurationClassPostProcesso 處理 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 對象 @EventListener 事件監聽方法適配為 ApplicationListener
org.springframework.context.annotation.internalPersistenceAnnotationProcessor PersistenceAnnotationBeanPostProcessor 對象 (條件激活)處理 JPA 注解場景

依賴查找中的經典異常,BeansException 子類型

異常類型 觸發條件(舉例) 場景舉例
NoSuchBeanDefinitionException 當查找 Bean 不存在於 IoC 容器時 BeanFactory#getBean ObjectFactory#getObject
NoUniqueBeanDefinitionException 類型依賴查找時,IoC 容器存在多個 Bean 實例 BeanFactory#getBean(Class)
BeanInstantiationException 當 Bean 所對應的類型非具體類時 BeanFactory#getBean
BeanCreationException 當 Bean 初始化過程中 Bean 初始化方法執行異常時
BeanDefinitionStoreException 當 BeanDefinition 配置元信息非法時 XML 配置資源無法打開時

BeanFactory.getBean 方法的執行是線程安全的,超過過程中會增加互斥鎖

第六章:Spring IoC依賴注入(Dependency Injection)

依賴注入的模式和類型

  • 手動模式 - 配置或者編程的方式,提前安排注入規則
    • XML 資源配置元信息
    • Java 注解配置元信息
    • API 配置元信息
  • 自動模式 - 實現方提供依賴自動關聯的方式,按照內建的注入規則
    • Autowiring(自動綁定)

依賴注入類型

依賴注入類型 配置元數據舉例
Setter 方法 <proeprty name="user" ref="userBean"/>
構造器 <constructor-arg name="user" ref="userBean" />
字段 @Autowired User user;
方法 @Autowired public void user(User user) { ... }
接口回調 class MyBean implements BeanFactoryAware { ... }

自動綁定(Autowiring)模式,Autowiring modes

參考枚舉:org.springframework.beans.factory.annotation.Autowire

模式 說明
no 默認值,未激活 Autowiring,需要手動指定依賴注入對象。
byName 根據被注入屬性的名稱作為 Bean 名稱進行依賴查找,並將對象設置到該屬性。
byType 根據被注入屬性的類型作為依賴類型進行查找,並將對象設置到該屬性。
constructor 特殊 byType 類型,用於構造器參數。

Java 注解配置元信息

  • @Autowired

  • @Resource

  • @Inject

    可選,需要環境中存在 JSR-330 依賴

    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
    
  • @Bean

Aware 系列接口回調

內建接口 說明
BeanFactoryAware 獲取 IoC 容器 - BeanFactory
ApplicationContextAware 獲取 Spring 應用上下文 - ApplicationContext 對象
EnvironmentAware 獲取 Environment 對象
ResourceLoaderAware 獲取資源加載器 對象 - ResourceLoader
BeanClassLoaderAware 獲取加載當前 Bean Class 的 ClassLoader
BeanNameAware 獲取當前 Bean 的名稱
MessageSourceAware 獲取 MessageSource 對象,用於 Spring 國際化
ApplicationEventPublisherAware 獲取 ApplicationEventPublishAware 對象,用於 Spring 事件
EmbeddedValueResolverAware 獲取 StringValueResolver 對象,用於占位符處理

依賴注入類型選擇

  • 低依賴:構造器注入
  • 多依賴:Setter 方法注入
  • 便利性:字段注入
  • 聲明類:方法注入

各種類型注入:

  • 基礎類型

    • 原生類型(Primitive):boolean、byte、char、short、int、float、long、double
    • 標量類型(Scalar):Number、Character、Boolean、Enum、Locale、Charset、Currency、Properties、UUID
    • 常規類型(General):Object、String、TimeZone、Calendar、Optional 等
    • Spring 類型:Resource、InputSource、Formatter 等
  • 集合類型

    • 數組類型(Array):原生類型、標量類型、常規類型、Spring 類型
    • 集合類型(Collection)
      • Collection:List、Set(SortedSet、NavigableSet、EnumSet)
      • Map:Properties
  • 限定注入

    • 使用注解 @Qualifier 限定
      • 通過 Bean 名稱限定
      • 通過分組限定
    • 基於注解 @Qualifier 擴展限定
      • 自定義注解,如 Spring Cloud @LoadBalanced
  • 延遲依賴注入

  • 使用 API ObjectFactory 延遲注入

    • 單一類型
    • 集合類型
  • 使用 API ObjectProvider 延遲注入(推薦)

    • 單一類型
    • 集合類型

依賴處理過程

  • 入口 - DefaultListableBeanFactory#resolveDependency
  • 依賴描述符 - DependencyDescriptor
  • 自定綁定候選對象處理器 - AutowireCandidateResolver

@Autowired、@Inject 注入,參考 AutowiredAnnotationBeanPostProcessor

Java通用注解注入原理:

  • CommonAnnotationBeanPostProcessor
  • 注入注解
    • javax.xml.ws.WebServiceRef
    • javax.ejb.EJB
    • javax.annotation.Resource
  • 生命周期注解
    • javax.annotation.PostConstruct
    • javax.annotation.PreDestroy

自定義依賴注入注解

  • 基於 AutowiredAnnotationBeanPostProcessor 實現

  • 自定義實現

    • 生命周期處理
      • InstantiationAwareBeanPostProcessor
      • MergedBeanDefinitionPostProcessor
    • 元數據
      • InjectedElement
      • InjectionMetadata
  • org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor

    • org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
      • @Autowired
      • @Value
      • @Inject
    • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
      • @PostConstruct
      • @PreDestroy

初始化 Bean 時,AutowiredAnnotationBeanPostProcessor 先解析 Bean 中的依賴(@Autowire@Value),然后 CommonAnnotationBeanPostProcessor 調用初始化方法 @@PostConstruct

@Bean 方法設置為 static ,可以讓 Bean 提前初始化。

  • 依賴查找:ApplicationContext#getBean

  • 依賴處理過程:org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency

第七章:Spring IoC依賴來源(Dependency Sources)

Spring IoC依賴來源

來源 配置元數據 注冊API Spring Bean 對象 生命周期管理 配置元信息 使用場景
Spring BeanDefinition <bean id="user" class="org.geekbang...User">
@Bean public User user(){...}
BeanDefinitionBuilder
BeanDefinitionRegistry#registerBeanDefinition 依賴查找、依賴注入
單例對象 API 實現 SingletonBeanRegistry#registerSingleton 依賴查找、依賴注入
非 Spring 容器管理對象 Resolvable Dependency ConfigurableListableBeanFactory#registerResolvableDependency 依賴注入
外部化配置 @Value Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); 依賴注入

依賴注入比依賴查找多一個來源,Resolvable Dependency。也就是說,可以通過注入的方式獲取這類對象,但不能通過 BeanFactory#getBean 方法從容器中獲取。

Spring 內建 BeanDefintion

在使用 AnnotationConfigApplicationContext 或者在 XML 配置中配置了注解驅動 <context:annotation-config/> ,或組件掃描 <context:component-scan base-package="org.acme" /> ,會觸發org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors 注入一些 Spring 內建的 Bean:

  • org.springframework.context.annotation.ConfigurationClassPostProcessor

  • org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

  • org.springframework.context.annotation.CommonAnnotationBeanPostProcessor

  • org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor

    需要環境中有 JPA 依賴

  • org.springframework.context.event.EventListenerMethodProcessor

  • org.springframework.context.event.DefaultEventListenerFactory

Spring 內建單例對象

Spring 啟動時,refresh() 方法會調用 org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory ,會注入一些單例對象,名稱為:

  • environment
  • systemProperties
  • systemEnvironment

單例對象由 org.springframework.beans.factory.config.SingletonBeanRegistry 注冊,org.springframework.beans.factory.support.AbstractBeanFactory 實現了這個接口,從容器中獲取 Bean 的方法 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 中,會先從單例對象中查找,如果查找到,直接返回;查找不到,則從 Spring BeanDefinition 中獲取,並執行生命周期函數

Resolvable Dependency / 非 Spring 容器管理對象 / 可解析依賴

org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory

// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);

后三個實際上都是同一個 ApplicationContext

order 值越大,優先級越小

第八章:Spring Bean 作用域

作用域 說明
singleton 默認 Spring Bean 作用域,一個 BeanFactory 有且僅有一個實例
prototype 原型作用域,每次依賴查找和依賴注入生成新 Bean 對象
request 將 Spring Bean 存儲在 ServletRequest 上下文中
session 將 Spring Bean 存儲在 HttpSession 中
application 將 Spring Bean 存儲在 ServletContext 中

注意事項

  • Spring 容器沒有辦法管理 prototype Bean 的完整生命周期,也沒有辦法記錄示例的存
    在。銷毀回調方法將不會執行,可以利用 BeanPostProcessor 進行清掃工作。
  • 無論是 Singleton 還是 Prototype Bean 均會執行初始化方法回調,不過僅 Singleton Bean 會執行銷毀方法回調

@Scope 注解定義原型 Bean :

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static User prototypeUser() {
	return createUser();
}

"request" Bean 作用域

  • XML - <bean class= "..." scope = "request" />
  • Java 注解 - @RequestScope@Scope(WebApplicationContext.SCOPE_REQUEST)

每次使用的 CGLIB 代理對象是同一個,但是被代理的對象每次都會重新生成。

使用 IDEA 進行遠程調試:

  • 在 Edit Configurations 中新增一個 Remote ,使用命令啟動 jar 時,在啟動命令中增加 Remote 里的內容,啟動 jar 以及 Remote,打斷點進行調試。

實現 API

  • @RequestScope
  • RequestScope

"session" Bean 作用域

配置

  • XML - <bean class= "..." scope = "session" />
  • Java 注解 - @RequestScope@Scope(WebApplicationContext.SCOPE_REQUEST)

實現 API

  • @SessionScope
  • SessionScope

"application" Bean 作用域

配置

  • XML - <bean class= "..." scope = "application" />
  • Java 注解 - @ApplicationScope@Scope(WebApplicationContext.SCOPE_APPLICATION)

實現 API

  • @ApplicationScope
  • ServletContextScope

實現方式與 request 和 session 不同,這里直接將 Bean 放入 ServletContext

自定義 Bean 作用域

實現 Scope

  • org.springframework.beans.factory.config.Scope

注冊 Scope

  • API

    org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope

  • 配置

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="...">
                </entry>
            </map>
        </property>
    </bean>
    

第九章:Spring Bean生命周期(Bean Lifecycle)

Spring Bean 元信息配置階段

BeanDefinition 配置

  • 面向資源
    • XML 配置
    • Properties 資源配置
  • 面向注解
  • 面向 API

Spring Bean 元信息解析階段

BeanDefinition 解析

  • 面向資源 BeanDefinition 解析 - BeanDefinitionReader
    • XML 解析器 - XmlBeanDefinitionReader
    • Properties 解析器 - PropertiesBeanDefinitionReader
  • 面向注解 BeanDefinition 解析 - AnnotatedBeanDefinitionReader

Spring Bean 注冊階段

BeanDefinition 注冊接口

BeanDefinitionRegistry

Spring BeanDefinition 合並階段

BeanDefinition 合並

父子 BeanDefinition 合並

  • 當前 BeanFactory 查找
  • 層次性 BeanFactory 查找
<bean id="user" class="org.geekbang.thinking.in.spring.ioc.overview.domain.User">
    <property name="id" value="1"/>
    ...
</bean>

<bean id="superUser" class="org.geekbang.thinking.in.spring.ioc.overview.domain.SuperUser" parent="user"
      primary="true">
    <property name="address" value="杭州"/>
</bean>

XmlBeanDefinitionReader#loadBeanDefinitions 加載 XML 文件時,賦值 DefaultListableBeanFactory#beanDefinitionMap,這個 Map 中的 BeanDefinition 還沒有合並,也就是說 superUser 的屬性值還沒有從 user 中繼承過來。

AbstractBeanFactory#getBean 獲取 bean 時,執行 AbstractBeanFactory#getMergedBeanDefinition ,對 superUser 進行合並,放入 AbstractBeanFactory#mergedBeanDefinitions 中。

Spring Bean Class 加載階段

  • ClassLoader 類加載
  • Java Security 安全控制
  • ConfigurableBeanFactory 臨時 ClassLoader

AbstractBeanDefinition#beanClass 被定義為 Object ,有兩種形式,一種是 全類名 的 String,另一種是 Class 對象

Java Security 安全控制 相關

if (System.getSecurityManager() != null) {
	return AccessController.doPrivileged((PrivilegedExceptionAction<Class<?>>) () ->
doResolveBeanClass(mbd, typesToMatch), getAccessControlContext());
}

臨時 ClassLoader 與 load-time weaving 技術有關,用於進行類型檢查時(即尚未創建實際實例)

Spring Bean 實例化階段

  • 傳統實例化方式

    • 實例化策略 - InstantiationStrategy
  • 構造器依賴注入

    實例化階段,如果使用構造器注入,將解析構造器注入的依賴

AbstractAutowireCapableBeanFactory#createBeanInstance

Spring Bean 實例化前階段

  • 非主流生命周期 - Bean 實例化前階段
    • InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation

返回非 null 時,阻止 bean 的默認實例化過程及以下生命周期

唯一可以進一步生命周期處理的是 BeanPostProcessor#postProcessAfterInitialization

Spring Bean 實例化后階段

  • Bean 屬性賦值(Populate)判斷
    • InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation

在給 bean 實例做屬性賦值的方法 AbstractAutowireCapableBeanFactory#populateBean 的最開始調用,如果返回 false ,阻止 bean 的屬性賦值及以下生命周期

Spring Bean 屬性賦值前階段

  • Bean 屬性值元信息

    • PropertyValues
  • Bean 屬性賦值前回調

    • Spring 1.2 - 5.0:InstantiationAwareBeanPostProcessor#postProcessPropertyValues

      此方法已過期,使用 postProcessProperties 替代,為了兼容,只有在 postProcessProperties 返回 null 時(默認實現),才會調用此方法

    • Spring 5.1:InstantiationAwareBeanPostProcessor#postProcessProperties

在工廠將給定屬性值應用於給定 bean 之前,對它們進行處理。

在依賴注入( byNamebyType )之后,在將配置的屬性賦值給 bean 實例 AbstractAutowireCapableBeanFactory#applyPropertyValues 之前執行此階段方法

Spring Bean 初始化階段

AbstractAutowireCapableBeanFactory#initializeBean

Spring Bean Aware 接口回調階段

Spring Aware 接口,執行順序從上到下

  • BeanNameAware
  • BeanClassLoaderAware
  • BeanFactoryAware

依賴於 ApplicationContext

  • EnvironmentAware
  • EmbeddedValueResolverAware
  • ResourceLoaderAware
  • ApplicationEventPublisherAware
  • MessageSourceAware
  • ApplicationContextAware

在初始化 Bean 實例 AbstractAutowireCapableBeanFactory#initializeBean 的最開始執行此階段,前三個接口直接調用,而依賴於 ApplicationContext 的幾個 Aware 接口,在 ApplicationContext 的生命周期中,會在 beanFactory 中加入 ApplicationContextAwareProcessor ,在其 postProcessBeforeInitialization 方法中執行調用

ApplicationContextAwareProcessor 是包權限的

Spring Bean 初始化前階段

已完成

  • Bean 實例化

  • Bean 屬性賦值

  • Bean Aware 接口回調

方法回調

  • BeanPostProcessor#postProcessBeforeInitialization

Spring Bean 初始化階段

Bean 初始化(Initialization)

  • @PostConstruct 標注方法
  • 實現 InitializingBean 接口的 afterPropertiesSet() 方法
  • 自定義初始化方法

@PostConstruct 的處理需要依賴於注解驅動,CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization

Spring Bean 初始化后階段

方法回調

  • BeanPostProcessor#postProcessAfterInitialization

Spring Bean 初始化完成階段

方法回調

  • Spring 4.1 +:SmartInitializingSingleton#afterSingletonsInstantiated

SmartInitializingSingleton 通常在 Spring ApplicationContext 場景使用

使用 BeanFactory 時,需要顯式的調用此方法;在 ApplicationContext 啟動時,調用了此方法 AbstractApplicationContext#finishBeanFactoryInitialization ,這個方法做了兩件事情:

  1. 將已注冊的 BeanDefinition 初始化成 Spring Bean
  2. 調用所有 SmartInitializingSingleton#afterSingletonsInstantiated

Spring Bean 銷毀前階段

  • 方法回調
    • DestructionAwareBeanPostProcessor#postProcessBeforeDestruction

執行 ConfigurableBeanFactory#destroyBean 時,觸發 Bean 前銷毀階段

@PreDestroy 的處理需要依賴於注解驅動,CommonAnnotationBeanPostProcessor#postProcessBeforeDestruction

Spring Bean 銷毀階段

Bean 銷毀(Destroy)

  • @PreDestroy 標注方法
  • 實現 DisposableBean 接口的 destroy() 方法
  • 自定義銷毀方法

@PreDestroy 的處理需要依賴於注解驅動,CommonAnnotationBeanPostProcessor#postProcessBeforeDestruction

CommonAnnotationBeanPostProcessorDestructionAwareBeanPostProcessor 的實現類之一

如果其他 DestructionAwareBeanPostProcessor 排序在 CommonAnnotationBeanPostProcessor 后,會先執行 @PreDestroy 標注方法,后執行其他 DestructionAwareBeanPostProcessor 銷毀前階段方法

Spring Bean 垃圾收集

Bean 垃圾回收(GC)

  • 關閉 Spring 容器(應用上下文)
  • 執行 GC
  • Spring Bean 覆蓋的 finalize() 方法被回調

面試題

BeanPostProcessor 的使用場景有哪些?

答:BeanPostProcessor 提供 Spring Bean 初始化前和初始化后的生命周期回調,分別對應 postProcessBeforeInitialization 以及 postProcessAfterInitialization 方法,允許對關心的 Bean 進行擴展,甚至是替換。

加分項:其中,ApplicationContext 相關的 Aware 回調也是基於 BeanPostProcessor 實現,即 ApplicationContextAwareProcessor

BeanFactoryPostProcessor 與 BeanPostProcessor 的區別

答:BeanFactoryPostProcessor 是 Spring BeanFactory(實際為 ConfigurableListableBeanFactory) 的后置處理器,用於擴展 BeanFactory,或通過 BeanFactory 進行依賴查找和依賴注入。

加分項:BeanFactoryPostProcessor 必須有 Spring ApplicationContext 執行,BeanFactory 無法與其直接交互。而 BeanPostProcessor 則直接與 BeanFactory 關聯,屬於 N 對 1 的關系。

BeanFactory 是怎樣處理 Bean 生命周期?

BeanFactory 的默認實現為 DefaultListableBeanFactory,其中 Bean生命周期與方法映射如下:

  • BeanDefinition 注冊階段 - registerBeanDefinition
  • BeanDefinition 合並階段 - getMergedBeanDefinition
  • Bean 實例化前階段 - resolveBeforeInstantiation
  • Bean 實例化階段 - createBeanInstance
  • Bean 實例化后階段 - populateBean
  • Bean 屬性賦值前階段 - populateBean
  • Bean 屬性賦值階段 - populateBean
  • Bean Aware 接口回調階段 - initializeBean
  • Bean 初始化前階段 - initializeBean
  • Bean 初始化階段 - initializeBean
  • Bean 初始化后階段 - initializeBean
  • Bean 初始化完成階段 - preInstantiateSingletons
  • Bean 銷毀前階段 - destroyBean
  • Bean 銷毀階段 - destroyBean

第十章:Spring配置元信息(Configuration Metadata)

Spring 配置元信息

  • Spring Bean 配置元信息 - BeanDefinition
  • Spring Bean 屬性元信息 - PropertyValues
  • Spring 容器配置元信息
  • Spring 外部化配置元信息 - PropertySource
  • Spring Profile 元信息 - @Profile

Spring Bean 配置元信息

Bean 配置元信息 - BeanDefinition

  • GenericBeanDefinition:通用型 BeanDefinition
  • RootBeanDefinition:無 Parent 的 BeanDefinition 或者合並后 BeanDefinition
  • AnnotatedBeanDefinition:注解標注的 BeanDefinition

Spring Bean 屬性元信息

  • Bean 屬性元信息 - PropertyValues
    • 可修改實現 - MutablePropertyValues
    • 元素成員 - PropertyValue
  • Bean 屬性上下文存儲 - AttributeAccessor
  • Bean 元信息元素 - BeanMetadataElement

AttributeAccessorSupport#attributes 是附加屬性(不影響 Bean populate、initialize)

BeanMetadataAttributeAccessor#source 存儲當前 BeanDefinition 來自於何方(輔助作用)

Spring 容器配置元信息

Spring XML 配置元信息 - beans 元素相關

beans 元素屬性 默認值 使用場景
profile null(留空) Spring Profiles 配置值
default-lazy-init default 當 outter beans “default-lazy-init” 屬性存在時,繼承該值,否則為“false”
default-merge default 當 outter beans “default-merge” 屬性存在時,繼承該值,否則為“false”
default-autowire default 當 outter beans “default-autowire” 屬性存在時,繼承該值,否則為“no”
default-autowire-candidates null(留空) 默認 Spring Beans 名稱 pattern
default-init-method null(留空) 默認 Spring Beans 自定義初始化方法
default-destroy-method null(留空) 默認 Spring Beans 自定義銷毀方法

Spring XML 配置元信息 - 應用上下文相關

XML 元素 使用場景
<context:annotation-config /> 激活 Spring 注解驅動
<context:component-scan /> Spring @Component 以及自定義注解掃描
<context:load-time-weaver /> 激活 Spring LoadTimeWeaver
<context:mbean-export /> 暴露 Spring Beans 作為 JMX Beans
<context:mbean-server /> 將當前平台作為 MBeanServer
<context:property-placeholder /> 加載外部化配置資源作為 Spring 屬性配置
<context:property-override /> 利用外部化配置資源覆蓋 Spring 屬性值

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate

populateDefaults

基於 XML 資源裝載 Spring Bean 配置元信息

XML 元素 使用場景
<beans:beans /> 單 XML 資源下的多個 Spring Beans 配置
<beans:bean /> 單個 Spring Bean 定義(BeanDefinition)配置
<beans:alias /> 為 Spring Bean 定義(BeanDefinition)映射別名
<beans:import /> 加載外部 Spring XML 配置資源

底層實現 - XmlBeanDefinitionReader

加載 BeanDefinition 入口方法 loadBeanDefinitions

使用 DOM 來解析 XML 文件,實現為 BeanDefinitionDocumentReader

解析方法:XmlBeanDefinitionReader#parseBeanDefinitions

  1. 判斷 beans 標簽的 profile 屬性,如果不在激活狀態,直接返回,不再向下解析
  2. 如果是 beans 標簽下的特殊元素,進行特殊處理,方法為 DefaultBeanDefinitionDocumentReader#parseDefaultElement
    • beans
    • bean
    • alias
    • import
  3. 否則,BeanDefinitionParserDelegate#parseCustomElement

基於 Properties 資源裝載 Spring Bean 配置元信息

Properties 屬性名 使用場景
(class) Bean 類全稱限定名
(abstract) 是否為抽象的 BeanDefinition
(parent) 指定 parent BeanDefinition 名稱
(lazy-init) 是否為延遲初始化
(ref) 引用其他 Bean 的名稱
(scope) 設置 Bean 的 scope 屬性
${n} n 表示第 n+1 個構造器參數

底層實現 - PropertiesBeanDefinitionReader

如果出現重復的 Bean 定義,后者不會被注冊進 BeanFactory

基於 Java 注解裝載 Spring Bean 配置元信息

Spring 模式注解

Spring 注解 場景說明 起始版本
@Repository 數據倉儲模式注解 2.0
@Component 通用組件模式注解 2.5
@Service 服務模式注解 2.5
@Controller Web 控制器模式注解 2.5
@Configuration 配置類模式注解 3.0

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#registerDefaultFilters@Component 及其派生注解加入篩選

Spring Bean 定義注解

Spring 注解 場景說明 起始版本
@Bean 替換 XML 元素 <bean> 3.0
@DependsOn 替代 XML 屬性 <bean depends-on="..."/> 3.0
@Lazy 替代 XML 屬性 <bean lazy-init="true|falses" /> 3.0
@Primary 替換 XML 元素 <bean primary="true|false" /> 3.0
@Role 替換 XML 元素 <bean role="..." /> 3.1
@Lookup 替代 XML 屬性 <bean lookup-method="..."> 4.1

Spring Bean 依賴注入注解

Spring 注解 場景說明 起始版本
@Autowired Bean 依賴注入,支持多種依賴查找方式 2.5
@Qualifier 細粒度的 @Autowired 依賴查找 2.5

AutowiredAnnotationBeanPostProcessor@Autowired 相關

Java 注解 場景說明 起始版本
@Resource 類似於 @Autowired 2.5
@Inject 類似於 @Autowired 2.5

CommonAnnotationBeanPostProcessor@Resource 相關

AutowiredAnnotationBeanPostProcessor@Inject 相關

Spring Bean 條件裝配注解

Spring 注解 場景說明 起始版本
@Profile 配置化條件裝配 3.1
@Conditional 編程條件裝配 4.0

@Profile 基於 @Conditional 實現

@Conditional 相關 API,ConditionEvaluator ,用於判斷 Bean 是否滿足條件,滿足則注冊

Spring Bean 生命周期回調注解

Spring 注解 場景說明 起始版本
@PostConstruct 替換 XML 元素 <bean init-method="..." />InitializingBean 2.5
@PreDestroy 替換 XML 元素 <bean destroy-method="..." />DisposableBean 2.5

CommonAnnotationBeanPostProcessor

Spring Bean 配置元信息底層實現

Spring BeanDefinition 解析與注冊

實現場景 實現類 起始版本
XML 資源 XmlBeanDefinitionReader 1.0
Properties 資源 PropertiesBeanDefinitionReader 1.0
Java 注解 AnnotatedBeanDefinitionReader 3.0
  • XmlBeanDefinitionReaderPropertiesBeanDefinitionReader 都繼承自 AbstractBeanDefinitionReader,實現了 BeanDefinitionReader 接口,與資源(Resource)相關聯

  • AnnotatedBeanDefinitionReader 是獨立的類,與 Resource 無關

  • Spring XML 資源 BeanDefinition 解析與注冊

    • 核心 API - XmlBeanDefinitionReader
      • 資源 - Resource
      • 底層 - BeanDefinitionDocumentReader
        • XML 解析 - Java DOM Level 3 API
        • BeanDefinition 解析 - BeanDefinitionParserDelegate
        • BeanDefinition 注冊 - BeanDefinitionRegistry
  • Spring Properties 資源 BeanDefinition 解析與注冊

    • 核心 API - PropertiesBeanDefinitionReader
      • 資源
        • 字節流 - Resource
        • 字符流 - EncodedResouce
      • 底層
        • 存儲 - java.util.Properties
        • BeanDefinition 解析 - API 內部實現
        • BeanDefinition 注冊 - BeanDefinitionRegistry
  • Spring Java 注冊 BeanDefinition 解析與注冊

    • 核心 API - AnnotatedBeanDefinitionReader
      • 資源
        • 類對象 - java.lang.Class
      • 底層
        • 條件評估 - ConditionEvaluator
        • Bean 范圍解析 - ScopeMetadataResolver
        • BeanDefinition 解析 - 內部 API 實現
        • BeanDefinition 處理 - AnnotationConfigUtils.processCommonDefinitionAnnotations
        • BeanDefinition 注冊 - BeanDefinitionRegistry

Properties 資源加載默認編碼是 ISO-8859-1

AnnotatedBeanDefinitionReader 使用 ConditionEvaluator 判斷 Bean 的元信息,如果其中存在 @Conditional 條件,判斷此條件通過才會將 Bean 加入容器

基於 XML 資源裝載 Spring IoC 容器配置元信息

Spring IoC 容器相關 XML 配置

命名空間 所屬模塊 Schema 資源 URL
beans spring-beans https://www.springframework.org/schema/beans/spring-beans.xsd
context spring-context https://www.springframework.org/schema/context/spring-context.xsd
aop spring-aop https://www.springframework.org/schema/aop/spring-aop.xsd
tx spring-tx https://www.springframework.org/schema/tx/spring-tx.xsd
util spring-beans https://www.springframework.org/schema/util/spring-util.xsd
tool spring-beans https://www.springframework.org/schema/tool/spring-tool.xsd

基於 Java 注解裝載 Spring IoC 容器配置元信息

Spring IoC 容器裝配注解

Spring 注解 場景說明 起始版本
@ImportResource 替換 XML 元素 <import> 3.0
@Import 導入 Configuration Class 3.0
@ComponentScan 掃描指定 package 下標注 Spring 模式注解的類 3.1

Spring IoC 配屬屬性注

Spring 注解 場景說明 起始版本
@PropertySource 配置屬性抽象 PropertySource 注解 3.1
@PropertySources @PropertySource 集合注解 4.0

基於 Extensible XML authoring 擴展 Spring XML 元素

Spring XML 擴展

  • 編寫 XML Schema 文件:定義 XML 結構
    • users.xsd
  • 自定義 NamespaceHandler 實現:命名空間綁定
    • spring.handlers
    • org.geekbang.thinking.in.spring.configuration.metadata.UsersNamespaceHandler
  • 自定義 BeanDefinitionParser 實現:XML 元素與 BeanDefinition 解析
    • org.geekbang.thinking.in.spring.configuration.metadata.UserBeanDefinitionParser
  • 注冊 XML 擴展:命名空間與 XML Schema 映射
    • spring.schemas
    • users-context.xml

觸發時機:BeanDefinitionParserDelegate#parseCustomElement

  • 獲取 namespace
  • 通過 namespace 解析 NamespaceHandler
  • 構造 ParserContext
  • 解析元素,獲取 BeanDefinintion

基於 Properties 資源裝載外部化配置

  • 注解驅動
    • @org.springframework.context.annotation.PropertySource
    • @org.springframework.context.annotation.PropertySources
  • API 編程
    • org.springframework.core.env.PropertySource
    • org.springframework.core.env.PropertySources

基於 YAML 資源裝載外部化配置

API 編程

  • org.springframework.beans.factory.config.YamlProcessor
    • org.springframework.beans.factory.config.YamlMapFactoryBean
    • org.springframework.beans.factory.config.YamlPropertiesFactoryBean

Requires SnakeYAML 1.18 or higher, as of Spring Framework 5.0.6

通過 PropertySourceFactory 接口,引入 PropertySource

org.springframework.core.io.support.PropertySourceFactory

@PropertySource(
        name = "yamlPropertySource",
        value = "classpath:/META-INF/user.yaml",
        factory = YamlPropertySourceFactory.class)

面試題

Spring 內建 XML Schema 常見有哪些?

命名空間 所屬模塊 Schema 資源 URL
beans spring-beans https://www.springframework.org/schema/beans/spring-beans.xsd
context spring-context https://www.springframework.org/schema/context/spring-context.xsd
aop spring-aop https://www.springframework.org/schema/aop/spring-aop.xsd
tx spring-tx https://www.springframework.org/schema/tx/spring-tx.xsd
util spring-beans https://www.springframework.org/schema/util/spring-util.xsd
tool spring-beans https://www.springframework.org/schema/tool/spring-tool.xsd

Spring配置元信息具體有哪些?

  • Bean 配置元信息:通過媒介(如 XML、Proeprties 等),解析 BeanDefinition
  • IoC 容器配置元信息:通過媒介(如 XML、Proeprties 等),控制 IoC 容器行為,比如注解驅動、AOP 等
  • 外部化配置:通過資源抽象(如 Proeprties、YAML 等),控制 PropertySource
  • Spring Profile:通過外部化配置,提供條件分支流程

Extensible XML authoring 的缺點?

  • 高復雜度:開發人員需要熟悉 XML Schema,spring.handlers,spring.schemas 以及 Spring API 。
  • 嵌套元素支持較弱:通常需要使用方法遞歸或者其嵌套解析的方式處理嵌套(子)元素。
  • XML 處理性能較差:Spring XML 基於 DOM Level 3 API 實現,該 API 便於理解,然而性能較差。
  • XML 框架移植性差:很難適配高性能和便利性的 XML 框架,如 JAXB。

第十一章:Spring 資源管理

引入動機

為什么 Spring 不使用 Java 標准資源管理,而選擇重新發明輪子?

  • Java 標准資源管理強大,然而擴展復雜,資源存儲方式並不統一
  • Spring 要自立門戶(重要的話,要講三遍)
  • Spring “抄”、“超” 和 “潮”

Java 標准資源管理

Java 標准資源定位

職責 說明
面向資源 文件系統、artifact(jar、war、ear 文件)以及遠程資源(HTTP、FTP 等)
API 整合 java.lang.ClassLoader#getResource、java.io.File 或 java.net.URL
資源定位 java.net.URL 或 java.net.URI
面向流式存儲 java.net.URLConnection
協議擴展 java.net.URLStreamHandler 或 java.net.URLStreamHandlerFactory

Java URL 協議擴展

  • 基於 java.net.URLStreamHandlerFactory
  • 基於 java.net.URLStreamHandler

img

基於 java.net.URLStreamHandler 擴展協議

JDK 1.8 內建協議實現

協議 實現類
file sun.net.www.protocol.file.Handler
ftp sun.net.www.protocol.ftp.Handler
http sun.net.www.protocol.http.Handler
https sun.net.www.protocol.https.Handler
jar sun.net.www.protocol.jar.Handler
mailto sun.net.www.protocol.mailto.Handler
netdoc sun.net.www.protocol.netdoc.Handler

實現類名必須為 Handler

實現類命名規則 說明
默認 sun.net.www.protocol.${protocol}.Handler
自定義 通過 Java Properties java.protocol.handler.pkgs 指定實現類包名,實現類名必須為 Handler。如果存在多包名指定,通過分隔符 |

Spring 資源接口

類型 接口
輸入流 org.springframework.core.io.InputStreamSource
只讀資源 org.springframework.core.io.Resource
可寫資源 org.springframework.core.io.WritableResource
編碼資源 org.springframework.core.io.support.EncodedResource
上下文資源 org.springframework.core.io.ContextResource

Spring 內建 Resource 實現

資源來源 資源協議 實現類
Bean 定義 org.springframework.beans.factory.support.BeanDefinitionResource
數組 org.springframework.core.io.ByteArrayResource
類路徑 classpath:/ org.springframework.core.io.ClassPathResource
文件系統 file:/ org.springframework.core.io.FileSystemResource
URL URL 支持的協議 org.springframework.core.io.UrlResource
ServletContext org.springframework.web.context.support.ServletContextResource

Spring Resource 接口擴展

  • 可寫資源接口
    • org.springframework.core.io.WritableResource
      • org.springframework.core.io.FileSystemResource
      • org.springframework.core.io.FileUrlResource(@since 5.0.2)
      • org.springframework.core.io.PathResource(@since 4.0 & @Deprecated)
  • 編碼資源接口
    • org.springframework.core.io.support.EncodedResource

Spring 資源加載器

Resource 加載器

  • org.springframework.core.io.ResourceLoader
    • org.springframework.core.io.DefaultResourceLoader
      • org.springframework.core.io.FileSystemResourceLoader
      • org.springframework.core.io.ClassRelativeResourceLoader
      • org.springframework.context.support.AbstractApplicationContext

Spring 通配路徑資源加載器

  • 通配路徑 ResourceLoader
    • org.springframework.core.io.support.ResourcePatternResolver
    • org.springframework.core.io.support.PathMatchingResourcePatternResolver
  • 路徑匹配器
    • org.springframework.util.PathMatcher
      • Ant 模式匹配實現 - org.springframework.util.AntPathMatcher

Spring 通配路徑資源擴展

  1. 實現 org.springframework.util.PathMatcher
  2. 重置 PathMatcher
    • PathMatchingResourcePatternResolver#setPathMatcher

依賴注入 Spring Resource

基於 @Value 實現

@Value("classpath:/...") 
private Resource resource;

@Value("classpath*:/META-INF/*.properties")
private Resource[] propertiesResources;

依賴注入 ResourceLoader

  • 方法一:實現 ResourceLoaderAware 回調
  • 方法二:@Autowired 注入 ResourceLoader
  • 方法三:注入 ApplicationContext 作為 ResourceLoader

ApplicationContext 接口繼承 ResourcePatternResolver 繼承 ResourceLoader

ResourceLoaderAware 回調在 實例初始化(@PostConstruct 等)之前

面試題

Spring 配置資源中有哪些常見類型?

  • XML 資源
  • Properties 資源
  • YAML 資源

請例舉不同類型 Spring 配置資源?

  • XML 資源
    • 普通 Bean Definition XML 配置資源 - *.xml
    • Spring Schema 資源 - *.xsd
  • Properties 資源
    • 普通 Properties 格式資源 - *.properties
    • Spring Handler 實現類映射文件 - META-INF/spring.handlers
    • Spring Schema 資源映射文件 - META-INF/spring.schemas
  • YAML 資源
    • 普通 YAML 配置資源 - *.yaml*.yml

Java 標准資源管理擴展的步驟?

  • 簡易實現
    • 實現 URLStreamHandler 並放置在 sun.net.www.protocol.${protocol}.Handler 包下
  • 自定義實現
    • 實現 URLStreamHandler
    • 添加 -Djava.protocol.handler.pkgs 啟動參數,指向 URLStreamHandler 實現類的包下
  • 高級實現
    • 實現 URLStreamHandlerFactory 並傳遞到 URL 之中
簡易實現實例
  1. 擴展 x 協議,新建類 sun.net.www.protocol.x.Handler,類名格式必須符合規范

    public class Handler extends URLStreamHandler {
        @Override
        protected URLConnection openConnection(URL u) throws IOException {
            return new XURLConnection(u);
        }
    }
    
  2. 新建類 XURLConnection ,實現 java.net.URLConnection

    public class XURLConnection extends URLConnection {
    
        private final ClassPathResource resource;
    
        // URL = x:///META-INF/default.properties
        protected XURLConnection(URL url) {
            super(url);
            this.resource = new ClassPathResource(url.getPath());
        }
    
        @Override
        public void connect() throws IOException {
    
        }
    
        public InputStream getInputStream() throws IOException {
            return resource.getInputStream();
        }
    }
    
  3. 測試使用

    public class HandlerTest {
    
        public static void main(String[] args) throws IOException {
            URL url = new URL("x:///META-INF/default.properties"); // 類似於 classpath:/META-INF/default.properties
            InputStream inputStream = url.openStream();
            System.out.println(StreamUtils.copyToString(inputStream, Charset.forName("UTF-8")));
        }
    }
    
自定義實現
  1. 新建類 Handler,繼承 sun.net.www.protocol.x.Handle,類名必須為 Handler,包名無限制

    public class Handler extends sun.net.www.protocol.x.Handler {
    
        // -Djava.protocol.handler.pkgs=org.geekbang.thinking.in.spring.resource
        public static void main(String[] args) throws IOException {
            // springx 協議
            URL url = new URL("springx:///META-INF/production.properties"); // 類似於 classpath:/META-INF/default.properties
            InputStream inputStream = url.openStream();
            System.out.println(StreamUtils.copyToString(inputStream, Charset.forName("UTF-8")));
        }
    }
    
  2. 運行時增加 VM 參數,-Djava.protocol.handler.pkgs=org.geekbang.thinking.in.spring.resource

高級實現
public class MyURLStreamHandlerFactory implements URLStreamHandlerFactory {
    @Override
    public URLStreamHandler createURLStreamHandler(String protocol) {
        return new Handler();
    }

    public static void main(String[] args) throws IOException {
        // URL 設置 URLStreamHandlerFactory,必須在創建 URL 實例之前
        URL.setURLStreamHandlerFactory(new MyURLStreamHandlerFactory());
        // springx 協議
        URL url = new URL("springx:///META-INF/production.properties"); // 類似於 classpath:/META-INF/default.properties
        
        InputStream inputStream = url.openStream();
        System.out.println(StreamUtils.copyToString(inputStream, Charset.forName("UTF-8")));
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM