深入理解spring註解@PropertySource的實現原理

之前文章簡單的介紹了一下@Value和@PropertySource註解的使用,沒有看過的同學可以點擊查看:

一分鐘學會spring註解之@value註解

一分鐘學會spring註解之@PropertySource註解


今天這篇文章將給大家詳細的介紹一下@PropertySource註解實現原理


首先讓我們一起看下@PropertySource的源碼如下:


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
   /**
    * 資源的名稱
    */

   String name() default "";
   /**
    * 資源文件路徑,可以是數據多個文件地址
    * 可以是classpath地址如:
    *                  "classpath:/com/myco/app.properties"
    * 也可以是對應的文件系統地址如:
    *                  "file:/path/to/file"
    */

   String[] value();
   /**
    * 是否忽略文件資源是否存在,默認是false,也就是說配置不存在的文件地址spring啓動將會報錯
    */

   boolean ignoreResourceNotFound() default false;
   /**
    * 這個沒什麼好說的了就是對應的字符編碼了,默認是空值,如果配置文件中有中文應該設置爲utf-8     */

   String encoding() default "";
   /**
    * 關鍵的元素了 讀取對應資源文件的工廠類了 默認的是PropertySourceFactory
    */

   Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}


注意看上面代碼中的註釋,之前文章有演示過讀取classpath中的配置文件,這邊演示一下如何讀取系統目錄中文件如下:


@PropertySource(value={"classpath:/user2.properties","file:/d://user2.properties"},encoding="utf-8",ignoreResourceNotFound=true)


d盤中的user2.properties的配置文件如下:


u.name2=王五
u.age2=25


增加一個user1對象如下:


/**
* 用戶名
*/

@Value("${u.name2}")
private String userName;
/**
* 年齡
*/

@Value("${u.age2}")
private Integer age;


運行測試如下:


實例1 === User [userName=李四, age=29]
實例2 === User [userName=王五, age=25]


從上我們可以發現@PropertySource註解的地址可以是以下兩種:



  •  classpath路徑:"classpath:/com/myco/app.properties"

  •  文件對應路徑:"file:/path/to/file"


接下來我們來詳細的介紹@PropertySource註解底層是如何解析這些配置文件,這個就必須得PropertySourceFactory的具體實現源碼了


進入PropertySourceFactory中你會發現它是一個接口代碼如下:


public interface PropertySourceFactory {
   /**
    * Create a {@link PropertySource} that wraps the given resource.
    * @param name the name of the property source
    * @param resource the resource (potentially encoded) to wrap
    * @return the new {@link PropertySource} (never {@code null})
    * @throws IOException if resource resolution failed
    */

   PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException;
}


裏邊只有一個createPropertySource方法,進入其中的實現類中如下:


public class DefaultPropertySourceFactory implements PropertySourceFactory {
   @Override
   public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
       return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
   }
}


注意,重要的類ResourcePropertySource出現了,進去可以看到兩個主要的構造方法如下:


/**
    * Create a PropertySource having the given name based on Properties
    * loaded from the given encoded resource.
    */

   public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
       super(name, PropertiesLoaderUtils.loadProperties(resource));
       this.resourceName = getNameForResource(resource.getResource());
   }
   /**
    * Create a PropertySource based on Properties loaded from the given resource.
    * The name of the PropertySource will be generated based on the
    * {@link Resource#getDescription() description} of the given resource.
    */

   public ResourcePropertySource(EncodedResource resource) throws IOException {
       super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
       this.resourceName = null;
   }


在構造方法中你可以發現加載資源的地方PropertiesLoaderUtils.loadProperties(resource),一路進去你可以返現如下代碼:


static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
           throws IOException
{
       InputStream stream = null;
       Reader reader = null;
       try {
           String filename = resource.getResource().getFilename();
           // 加載xml文件
           if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
               stream = resource.getInputStream();
               persister.loadFromXml(props, stream);
           }
           // 判斷是否有需要對應的字符編碼設置  有的話處理對應的InputStream
           else if (resource.requiresReader()) {
               reader = resource.getReader();
               persister.load(props, reader);
           }
           else {
               stream = resource.getInputStream();
               persister.load(props, stream);
           }
       }
}


怎麼樣,是不是可以發現@PropertySource不僅可以解析properties的文件同樣也可以解析xml文件,下邊我們一起來演示一下解析xml的例子吧

首先新增一個user2.xml如下:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
   <entry key="u.name3">王二小</entry>
   <entry key="u.age3">22</entry>
</properties>


配置類增加配置如下:


@PropertySource(value={"classpath:/user.properties","classpath:/user2.xml","file:/d://user2.properties"},encoding="utf-8",ignoreResourceNotFound=false)


測試運行結果如下:


實例1 === User [userName=李四, age=29]
實例2 === User [userName=王二小, age=22]


好了,到目前爲止我們不僅學會了@PropertySource註解的使用,而且瞭解到了其底層的具體實現,做到知其然知其所以然,以及瞭解了其默認的資源解析器PropertySourceFactory,並且你也可以繼承PropertySourceFactory實現自定義的解析器感興趣的同學可以自己去實現一個自定義解析類


以上是今天文章的所有內容,歡迎大家吐槽


推薦閱讀


深入理解spring生命週期與BeanPostProcessor的實現原理


深入理解java的反射機制

250G偷懶必看資料全集


更多優質文章請關注以下公衆號查閱:




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章