夯實Spring系列|第十五章:Spring 配置元信息

夯實Spring系列|第十五章:Spring 配置元信息

本章說明

本章將詳細的介紹 Spring 中的各類配置元信息,比較重要或者是前面章節沒有討論過的內容,會有簡單的演示代碼;這部分內容雖然在國內討論的熱度不高,但實際上在很多分佈式應用的源碼中處處可見,包括 dubbo、nacos 都有用到其中相關的技術,簡而言之,這一章是學習 Spring 相關分佈式應用技術棧源碼的基礎。

1.項目環境

2.Spring 配置元信息

配置元信息

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

3.Spring Bean 配置元信息

Bean 配置元信息 - BeanDefinition

  • GenericBeanDefinition : 通用性 BeanDefinition
  • RootBeanDefinition : 無 Parent 的 BeanDefinition 或者是 合併後 BeanDefinition
  • AnnotatedBeanDefinition : 註解標註的 BeanDefinition

4.Spring Bean 屬性元信息

Bean 屬性元信息 - PropertyValues

  • 可修改實現 - MutablePropertyValues
  • 元素成員 - PropertyValue

Bean 屬性上下文存儲 - AttributeAccessor

Bean 元信息元素 - BeanMetadataElement

4.1 PropertyValues

第五章:Spring Bean 定義 4.BeanDefinition 構建 小節有相關的例子

4.2 AttributeAccessor

beanDefinition.setAttribute(key, value);

這個屬性是附加屬性,並不會影響我們 Spring Bean 的實例化,初始化階段。

public class BeanConfigurationMetadataDemo {
    public static void main(String[] args) {
        // BeanDefinition 定義
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder.addPropertyValue("name", "xwf");
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        // 附加屬性(不影響 Bean 實例化、屬性賦值)
        beanDefinition.setAttribute("name", "小仙");

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        beanFactory.registerBeanDefinition("user", beanDefinition);

        User user = beanFactory.getBean("user", User.class);
        Object name = beanDefinition.getAttribute("name");
        System.out.println(name);
        System.out.println(user);
    }
}

執行結果:

小仙
User{beanName='user', id=null, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}

可以看到從 beanDefinition 獲取 name 屬性確實是 小仙,但是 user 對象的 name 並沒有改變。

我們可以通過 postProcessAfterInitialization 在初始化之後利用 beanDefinition 的 attribute 信息來自己進行替換。

public class BeanConfigurationMetadataDemo {
    public static void main(String[] args) {
        // BeanDefinition 定義
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder.addPropertyValue("name", "xwf");
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        // 附加屬性(不影響 Bean 實例化、屬性賦值)
        beanDefinition.setAttribute("name", "小仙");
        //當前 BeanDefinition 來自於何方(輔助作用)
        beanDefinition.setSource(BeanConfigurationMetadataDemo.class);

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (ObjectUtils.nullSafeEquals("user", beanName) && User.class.equals(bean.getClass())) {
                    BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
                    if (BeanConfigurationMetadataDemo.class.equals(bd.getSource())) {
                        String name = String.valueOf(bd.getAttribute("name"));
                        User user = (User) bean;
                        user.setName(name);
                        return user;
                    }
                }
                return bean;
            }
        });
        beanFactory.registerBeanDefinition("user", beanDefinition);

        User user = beanFactory.getBean("user", User.class);
        Object name = beanDefinition.getAttribute("name");
        System.out.println(name);
        System.out.println(user);
    }
}

執行結果:

小仙
User{beanName='user', id=null, name='小仙', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}

4.3 BeanMetadataElement

org.springframework.beans.BeanMetadataElement

public interface BeanMetadataElement {

	/**
	 * Return the configuration source {@code Object} for this metadata element
	 * (may be {@code null}).
	 */
	@Nullable
	default Object getSource() {
		return null;
	}

}

可以用表示 BeanDefinition 來自於何方(輔助作用);比如上面的例子中 BeanConfigurationMetadataDemo.class.equals(bd.getSource()) 用來判斷 BeanDefinition 來源,當然前提是提前設置了來源 beanDefinition.setSource(BeanConfigurationMetadataDemo.class)

5.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:annotation-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

6.基於 XML 文件裝載 Spring Bean 配置元信息

Spring bean 配置元信息

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

底層實現 org.springframework.beans.factory.xml.XmlBeanDefinitionReader

7.基於 Properties 文件裝載 Spring Bean 配置元信息

Spring bean 配置元信息

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

底層實現 org.springframework.beans.factory.support.PropertiesBeanDefinitionReader

8.基於 Java 註解裝載 Spring Bean 配置元信息

Spring 模式註解

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

Spring Bean 依賴注入註解

註解 類型 場景說明 起始版本
@Autowired Spring 註解 Bean 依賴注入 2.5
@Qualifier Spring 註解 限定注入 2.5
@Resource Java 註解 類似 @Autowired 2.5
@Inject Java 註解 類似 @Autowired 2.5

Spring Bean 條件裝配註解

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

Spring Bean 生命週期回調註解

Spring 註解 場景說明 起始版本
@PostConstruct 代替 XML 元素 或者 InitializingBean 2.5
@PreDestroy 代替 XML 元素 或者 DisposableBean 2.5

9.Spring Bean 配置元信息底層實現

Spring BeanDefintion 解析與註冊

實現場景 實現類 起始版本
XML 資源 XmlBeanDefinitionReader 1.0
Properties 資源 PropertiesBeanDefinitionReader 1.0
java 註解 AnnotatedBeanDefinitionReader 3.0

9.1 Spring XML 資源 BeanDefinition 解析與註冊

核心 API - XMLBeanDefintionReader

  • 資源 - Resource
  • 底層- BeanDefintionDocumentReader
    • XML 解析 - Java DOM Level 3 API
    • BeanDefintion 解析 - BeanDefinitionParserDelegate
    • BeanDefintion 註冊 - BeanDefinitionRegistry

9.2 Spring Properties 資源 BeanDefintion 解析與註冊

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

9.3 Spring Java 註冊 BeanDefinition 解析與註冊

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

10.基於 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

11.基於 Java 註解裝載 Spring IOC 容器配置元信息

11.1 Spring IoC 容器裝配註解

Spring 註解 場景說明 起始版本
@ImportResource 替換 XML 元素 3.0
@Import 導入 Configuration Class 3.0
@ComponentScan 掃描指定 package 3.1

示例

/**
 * 基於 Java 註解裝載 Spring IOC 容器配置元信息
 */
//將當前類作爲 Configuration Class
@ImportResource("classpath:/META-INF/dependency-lookup-context.xml")
@Import(User.class)
public class AnnotatedSpringIoCContainerMetaConfigurationDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 註冊當前類作爲 Configuration Class
        context.register(AnnotatedSpringIoCContainerMetaConfigurationDemo.class);
        // 啓動
        context.refresh();
        Map<String, User> beansOfType = context.getBeansOfType(User.class);
        printfEach(beansOfType);
        // 關閉
        context.close();
    }

    public static void printfEach(Map<String, User> map) {
        for (Map.Entry<String, User> entry : map.entrySet()) {
            System.out.printf("User Bean name : %s , content : %s \n", entry.getKey(), entry.getValue());
        }
    }
}

執行結果:

user用戶對象初始化...
superUser用戶對象初始化...
User Bean name : com.huajie.thinking.in.spring.ioc.overview.domain.User , content : User{beanName='com.huajie.thinking.in.spring.ioc.overview.domain.User', id=null, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null} 
User Bean name : user , content : User{beanName='user', id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=WUHAN, cities=[WUHAN, BEIJING], lifeCities=[WUHAN, BEIJING]} 
User Bean name : superUser , content : SuperUser{address='wuhan'}User{beanName='superUser', id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=WUHAN, cities=[WUHAN, BEIJING], lifeCities=[WUHAN, BEIJING]} 
superUser用戶對象銷燬...
user用戶對象銷燬...
com.huajie.thinking.in.spring.ioc.overview.domain.User用戶對象銷燬...

11.2 Spring IoC 配置屬性註解

Spring 註解 場景說明 其實版本
@PropertySource 配置屬性抽象 PropertySource 註解 3.1
@PropertySources @PropertySource 集合註解 4.0

示例

/**
 * 基於 Java 註解裝載 Spring IOC 容器配置元信息
 */
//將當前類作爲 Configuration Class
@ImportResource("classpath:/META-INF/dependency-lookup-context.xml")
@Import(User.class)
@PropertySource(value = "classpath:/META-INF/user-bean-definitions.properties", encoding = "gbk")
@PropertySource(value = "classpath:/META-INF/user-bean-definitions.properties", encoding = "gbk")//可以寫多個 jdk8 @Repeatable
public class AnnotatedSpringIoCContainerMetaConfigurationDemo {

    @Bean
    public User configuredUser(@Value(value = "${usr.name}") String name) {
        return User.createUser(name);
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 註冊當前類作爲 Configuration Class
        context.register(AnnotatedSpringIoCContainerMetaConfigurationDemo.class);
        // 啓動
        context.refresh();
        Map<String, User> beansOfType = context.getBeansOfType(User.class);
        printfEach(beansOfType);
        // 關閉
        context.close();
    }

    public static void printfEach(Map<String, User> map) {
        for (Map.Entry<String, User> entry : map.entrySet()) {
            System.out.printf("User Bean name : %s , content : %s \n", entry.getKey(), entry.getValue());
        }
    }
}

執行結果:

User Bean name : com.huajie.thinking.in.spring.ioc.overview.domain.User , content : User{beanName='com.huajie.thinking.in.spring.ioc.overview.domain.User', id=null, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null} 
User Bean name : configuredUser , content : User{beanName='configuredUser', id=null, name='小仙', age=null, configFileReource=null, city=null, cities=null, lifeCities=null} 
User Bean name : user , content : User{beanName='user', id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=WUHAN, cities=[WUHAN, BEIJING], lifeCities=[WUHAN, BEIJING]} 
User Bean name : superUser , content : SuperUser{address='wuhan'}User{beanName='superUser', id=1, name='xwf', age=18, configFileReource=class path resource [META-INF/user-config.properties], city=WUHAN, cities=[WUHAN, BEIJING], lifeCities=[WUHAN, BEIJING]} 

12.基於 Extensible XML authoring 擴展 Spring XML 元素

Spring XML 擴展

  • 編寫 XML Schema 文件:定義 XML 結構
  • 自定義 NamespaceHandler 實現:命名空間綁定
  • 自定義 BeanDefinitionParser 實現:XML 元素與 BeanDefinition 解析
  • 註冊 XML 擴展:命名空間與 XML Schema 映射

12.1 編寫 XML Schema 文件

在 resource 目錄下面建相同的包路徑 com.huajie.thinking.in.spring.configuration.metadata

新建 users.xsd 文件

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://com.huajie/schema/users"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://com.huajie/schema/users">

    <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>

    <!-- 定義 User 類型(定義複雜類型) -->
    <xsd:complexType name="User">
        <xsd:attribute name="id" type="xsd:integer" use="required"/>
        <xsd:attribute name="name" type="xsd:string" use="required"/>
        <xsd:attribute name="city" type="City"/>
    </xsd:complexType>

    <!-- 定義 City 類型(簡單類型,枚舉) -->
    <xsd:simpleType name="City">
        <xsd:restriction base="xsd:string">
            <xsd:enumeration value="WUHAN"/>
            <xsd:enumeration value="BEIJING"/>
            <xsd:enumeration value="SHANGHAI"/>
        </xsd:restriction>
    </xsd:simpleType>

    <!-- 定義 user 元素 -->
    <xsd:element name="user" type="User"/>

</xsd:schema>

新建 Spring xml 配置文件 users-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:users="http://com.huajie/schema/users"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://com.huajie/schema/users
        http://com.huajie/schema/users.xsd">

       <users:user id="1" name="小仙" city="WUHAN"/>

</beans>

12.2 自定義 NamespaceHandler 實現 & 自定義 BeanDefinitionParser 實現

新建 UsersNamespaceHandler

public class UsersNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("user", new UsersBeanDefinitionParser());
    }

    private static class UsersBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {

        @Override
        protected Class<?> getBeanClass(Element element) {
            return User.class;
        }

        @Override
        protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
            setPropertyValue("id", element, builder);
            setPropertyValue("name", element, builder);
            setPropertyValue("city", element, builder);
        }

        private void setPropertyValue(String attributeName, Element element, BeanDefinitionBuilder builder) {
            String value = element.getAttribute(attributeName);
            if (StringUtils.hasText(value)) {
                builder.addPropertyValue(attributeName, value);
            }
        }

    }

}

在 resource/META-INF 目錄下面新建 spring.handlers 文件

http\://com.huajie/schema/users=com.huajie.thinking.in.spring.configuration.metadata.UsersNamespaceHandler

12.3 註冊 XML 擴展

在 resource/META-INF 目錄下面新建 spring.schemas 文件

http\://com.huajie/schema/users.xsd=com/huajie/thinking/in/spring/configuration/metadata/users.xsd

12.4 示例

/**
 * Spring xml 元素擴展示例
 */
public class ExtensibleXmlAuthoringDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String location = "classpath:/META-INF/users-context.xml";
        xmlBeanDefinitionReader.loadBeanDefinitions(location);

        User bean = beanFactory.getBean(User.class);

        System.out.println(bean);
    }
}

執行結果:

User{beanName='1', id=1, name='小仙', age=null, configFileReource=null, city=WUHAN, cities=null, lifeCities=null}

13.Extensible XML authoring 擴展原理

觸發時機在啓動應用上下文時,Application.refresh() 方法

  • org.springframework.context.support.AbstractApplicationContext#obtainFreshBeanFactory
    • AbstractRefreshableApplicationContext#refreshBeanFactory
      • AbstractXmlApplicationContext#loadBeanDefinitions
        • AbstractBeanDefinitionReader#loadBeanDefinitions
          • BeanDefinitionParserDelegate#parseCustomElement

BeanDefinitionParserDelegate#parseCustomElement 核心流程

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

13.1 源碼調試

使用 12.基於 Extensible XML authoring 擴展 Spring XML 元素 小節中的示例

調試我們將斷點打在 BeanDefinitionParserDelegate#parseCustomElement 1383 行

1.獲取 namespace
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bUHjuiTX-1588497089744)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200503101955019.png)]
2.通過 namespace 解析 NamespaceHandler 獲取到我們定義的 handler
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LDfjhXne-1588497089747)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200503102024931.png)]
3.構造 ParserContext
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9cgzvpf7-1588497089748)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200503102428452.png)]
4.調用我們 handler#parse 方法,將 element 解析成 BeanDefinition
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3DxDf3zn-1588497089750)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200503102443763.png)]

14.基於 Properties 文件裝載外部化配置

註解驅動

  • org.springframework.context.annotation.PropertySource
  • org.springframework.context.annotation.PropertySources

API 編程

  • org.springframework.core.env.PropertySource
  • org.springframework.core.env.PropertySources

14.1 示例

在本章 11.2 Spring IoC 配置屬性註解 中已經有相關的示例

我們對示例做一定的改造,在外部化配置加載之前,新建一個 PropertySource 去替換其中的 usr.name 屬性;PropertySources 有一定的順序,先添加的 PropertySource 可以覆蓋後添加的。

/**
 * 外部化配置示例
 */
@PropertySource(value = "classpath:/META-INF/user-bean-definitions.properties", encoding = "gbk")
public class PropertySourceDemo {

    @Bean
    public User user(@Value(value = "${usr.name}") String name) {
        return User.createUser(name);
    }


    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 擴展 Environment 中的 propertySources
        // 這個操作必須在 context.refresh() 之前完成
        Map<String,Object> propertySource = new HashMap<>();
        propertySource.put("usr.name","new-小仙");
        MapPropertySource ms = new MapPropertySource("first-property-source",propertySource);
        context.getEnvironment().getPropertySources().addFirst(ms);
        // 註冊當前類作爲 Configuration Class
        context.register(PropertySourceDemo.class);
        // 啓動
        context.refresh();
        Map<String, User> beansOfType = context.getBeansOfType(User.class);
        printfEach(beansOfType);
        MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
        System.out.println(propertySources);

        // 關閉
        context.close();
    }

    public static void printfEach(Map<String, User> map) {
        for (Map.Entry<String, User> entry : map.entrySet()) {
            System.out.printf("User Bean name : %s , content : %s \n", entry.getKey(), entry.getValue());
        }
    }
}

執行結果:

User Bean name : user , content : User{beanName='user', id=null, name='new-小仙', age=null, configFileReource=null, city=null, cities=null, lifeCities=null} 
[MapPropertySource {name='first-property-source'}, PropertiesPropertySource {name='systemProperties'}, SystemEnvironmentPropertySource {name='systemEnvironment'}, ResourcePropertySource {name='class path resource [META-INF/user-bean-definitions.properties]'}]

可以看到 user 對象的 name = ‘new-小仙’

MapPropertySource 順序:

1.first-property-source 我們自定義的 PropertySource

2.systemEnvironment Java 系統環境變量

3.通過 properties 文件加載的 PropertySource

15.基於 YAML 文件裝載外部化配置

API 編程

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

15.1 基於 XML 示例

新建 user.yaml 文件;user.name 和 Java 系統環境變量相同,所以我們用 usr 表示

usr:
  id: 1
  name: 小仙-yaml

新建 yaml-property-source-context.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="yamlMap" class="org.springframework.beans.factory.config.YamlMapFactoryBean">
        <property name="resources" value="classpath:/META-INF/user.yaml"/>
    </bean>

</beans>

調用示例

/**
 * 基於 xml 的 yaml 外部化配置示例
 */
public class XmlBasedYamlPropertySourceDemo {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        String location = "classpath:/META-INF/yaml-property-source-context.xml";
        xmlBeanDefinitionReader.loadBeanDefinitions(location);

        Map<String, Object> users = beanFactory.getBean("yamlMap", Map.class);

        System.out.println(users);
    }
}

執行結果:

{usr={id=1, name=小仙-yaml}}

15.2 基於 Java 註解示例

新建 YamlPropertySourceFactory

public class YamlPropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
        yamlPropertiesFactoryBean.setResources(resource.getResource());
        Properties properties = yamlPropertiesFactoryBean.getObject();
        return new PropertiesPropertySource(name,properties);
    }
}

示例

/**
 * 基於 Java 註解的 yaml 外部化配置示例
 */
@PropertySource(name="yamlPropertySource",value = "classpath:/META-INF/user.yaml",factory = YamlPropertySourceFactory.class)
public class AnnotatedYamlPropertySourceDemo {

    @Bean
    public User user(@Value(value = "${usr.name}") String name) {
        return User.createUser(name);
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 註冊當前類作爲 Configuration Class
        context.register(AnnotatedYamlPropertySourceDemo.class);
        // 啓動
        context.refresh();

        User user = context.getBean("user", User.class);

        System.out.println(user);
        // 關閉
        context.close();
    }
}

執行結果:

User{beanName='user', id=null, name='小仙-yaml', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}

16.面試題

16.1 Spring 內建 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

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

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

16.3 Extensible XML authoring 的缺點?

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

17.參考

  • 極客時間-小馬哥《小馬哥講Spring核心編程思想》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章