Springboot Property Binder

用API獲取property 的值在springboot 1.x版本中,通常從Environment對象讀取

  //判斷是否包含鍵值
  boolean containsProperty(String key);
  
  //獲取屬性值,如果獲取不到返回null
  String getProperty(String key);
  
  //獲取屬性值甩挫,如果獲取不到返回缺省值
  String getProperty(String key, String defaultValue);
  
  //獲取屬性對象;其轉(zhuǎn)換和Converter有關(guān),會根據(jù)sourceType和targetType查找轉(zhuǎn)換器
  <T> T getProperty(String key, Class<T> targetType);

但是這樣的方式功能有限兽狭,還需要自己進(jìn)行字符串到對象的轉(zhuǎn)換。

Binder

Springboot 2.x新引入的類鹿蜀,負(fù)責(zé)處理對象與多個ConfigurationPropertySource(屬性)之間的綁定箕慧,比Environment類好用很多,可以非常方便地進(jìn)行類型轉(zhuǎn)換茴恰,以及提供回調(diào)方法介入綁定的各個階段進(jìn)行深度定制颠焦。

 //綁定對象
  MailPropertiesC propertiesC = Binder.get(environment) //首先要綁定配置器
      //再將屬性綁定到對象上
      .bind( "binder.sample.test", Bindable.of(MailPropertiesC.class) ).get(); //再獲取實例
      
  //綁定Map
  Map<String,Object> propMap = Binder.get(environment)
      .bind( "fish.jdbc.datasource",Bindable.mapOf(String.class, Object.class) ).get();
      
  //綁定List
  List<String> list = Binder.get(environment)
      .bind( "binder.sample.list",Bindable.listOf(String.class) ).get();
      
  //轉(zhuǎn)換以及默認(rèn)值
  String datestr = (String) Binder.get(environment)
      .bind( "binder.sample.date",Bindable.of(String.class) )
          //映射為大寫
          .map(String::toUpperCase)
          //默認(rèn)值
          .orElse("not date string");
           
  //綁定過程回調(diào)函數(shù),高度定制
  LocalDate str = Binder.get(environment)
      .bind("binder.sample.date", Bindable.of(LocalDate.class), new BindHandler() {
   
        @Override
        public <T> Bindable<T> onStart(ConfigurationPropertyName name, 
              Bindable<T> target, BindContext context) {
          log.info("綁定開始{}",name);
          return target;
        }
        
        @Override
        public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, 
              BindContext context, Object result) {
          log.info("綁定成功{}",target.getValue());
          return result;
        }
   
        @Override
        public Object onFailure(ConfigurationPropertyName name, Bindable<?> target, 
              BindContext context, Exception error) throws Exception {
          log.info("綁定失敗{}",name);
          return "沒有找到匹配的屬性";
        }
   
        @Override
        public void onFinish(ConfigurationPropertyName name, Bindable<?> target, 
              BindContext context, Object result) throws Exception {
          log.info("綁定結(jié)束{}",name);
        }
      }).get();

那么我們看看Binder 是怎么工作的往枣。org.springframework.boot.context.properties.bind.Binder

首先看和Environment的綁定伐庭。

    public static Binder get(Environment environment) {
        return get(environment, null);
    }

    /**
     * Create a new {@link Binder} instance from the specified environment.
     * @param environment the environment source (must have attached
     * {@link ConfigurationPropertySources})
     * @param defaultBindHandler the default bind handler to use if none is specified when
     * binding
     * @return a {@link Binder} instance
     * @since 2.2.0
     */
    public static Binder get(Environment environment, BindHandler defaultBindHandler) {
        Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
        PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment);
        return new Binder(sources, placeholdersResolver, null, null, defaultBindHandler);
    }

之后是構(gòu)造函數(shù), 這時候初始化了一個類型轉(zhuǎn)換Service conversionService, 它的實例 ApplicationConversionService.getSharedInstance();

    public Binder(Iterable<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver,
            ConversionService conversionService, Consumer<PropertyEditorRegistry> propertyEditorInitializer,
            BindHandler defaultBindHandler, BindConstructorProvider constructorProvider) {
        Assert.notNull(sources, "Sources must not be null");
        this.sources = sources;
        this.placeholdersResolver = (placeholdersResolver != null) ? placeholdersResolver : PlaceholdersResolver.NONE;
        this.conversionService = (conversionService != null) ? conversionService
                : ApplicationConversionService.getSharedInstance();
        this.propertyEditorInitializer = propertyEditorInitializer;
        this.defaultBindHandler = (defaultBindHandler != null) ? defaultBindHandler : BindHandler.DEFAULT;
        if (constructorProvider == null) {
            constructorProvider = BindConstructorProvider.DEFAULT;
        }
        ValueObjectBinder valueObjectBinder = new ValueObjectBinder(constructorProvider);
        JavaBeanBinder javaBeanBinder = JavaBeanBinder.INSTANCE;
        this.dataObjectBinders = Collections.unmodifiableList(Arrays.asList(valueObjectBinder, javaBeanBinder));
    }

請注意粉渠, ApplicationConversionService.getSharedInstance(); 不是在這個時候才被初始化的。而是SpringApplication 在啟動的時候configureEnvironment

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }

在本文后面會介紹如何加一個converter service圾另。 在Context 下面在bind 方法中構(gòu)造了Context 對象霸株,

        Context() {
            this.converter = BindConverter.get(Binder.this.conversionService, Binder.this.propertyEditorInitializer);
        }


    private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, boolean create) {
        Assert.notNull(name, "Name must not be null");
        Assert.notNull(target, "Target must not be null");
        handler = (handler != null) ? handler : this.defaultBindHandler;
        Context context = new Context();
        return bind(name, target, handler, context, false, create);
    }

BindConverter 是重要的類型轉(zhuǎn)換接口,它里面封裝了一個CompositeConversionService 是TypeConverterConversionService,ApplicationConversionService 的組合集乔。


    private BindConverter(ConversionService conversionService,
            Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
        Assert.notNull(conversionService, "ConversionService must not be null");
        List<ConversionService> conversionServices = getConversionServices(conversionService,
                propertyEditorInitializer);
        this.conversionService = new CompositeConversionService(conversionServices);
    }

    private List<ConversionService> getConversionServices(ConversionService conversionService,
            Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
        List<ConversionService> services = new ArrayList<>();
        services.add(new TypeConverterConversionService(propertyEditorInitializer));
        services.add(conversionService);
        if (!(conversionService instanceof ApplicationConversionService)) {
            services.add(ApplicationConversionService.getSharedInstance());
        }
        return services;
    }

之后就尋找PropertyResource去件, 然后進(jìn)行轉(zhuǎn)換了。



    private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
            boolean allowRecursiveBinding, boolean create) {
        try {
            Bindable<T> replacementTarget = handler.onStart(name, target, context);
            if (replacementTarget == null) {
                return handleBindResult(name, target, handler, context, null, create);
            }
            target = replacementTarget;
            Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
            return handleBindResult(name, target, handler, context, bound, create);
        }
        catch (Exception ex) {
            return handleBindError(name, target, handler, context, ex);
        }
    }

    private <T> T handleBindResult(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
            Context context, Object result, boolean create) throws Exception {
        if (result != null) {
            result = handler.onSuccess(name, target, context, result);
            result = context.getConverter().convert(result, target);
        }
        if (result == null && create) {
            result = create(target, context);
            result = handler.onCreate(name, target, context, result);
            result = context.getConverter().convert(result, target);
            Assert.state(result != null, () -> "Unable to create instance for " + target.getType());
        }
        handler.onFinish(name, target, context, result);
        return context.getConverter().convert(result, target);
    }

從上面代碼可以看出饺著,一個bind 基本由BindHandler來執(zhí)行箫攀, 得出result由BindConverter 來轉(zhuǎn)換類型。

Add a Customization ConverterService

如何在解析Property 時候來支持我們一個新的類型幼衰,比如給一個字符串靴跛,自動構(gòu)建一個URL。從上文看到渡嚣,我們可以從這段代碼入手

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }

我們應(yīng)可以對environment里的conversionService,做個代理就可以實現(xiàn)增加一個conversion service了梢睛。


public class URLConfigurableConversionService implements ConfigurableConversionService {

    private final ConfigurableConversionService m_delegate;

    public RaptorIOConfigurableConversionService(ConfigurableConversionService delegate) {
        m_delegate = delegate;
    }

    @Override
    public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
        m_delegate.addConverter(sourceType, targetType, converter);
    }

    @Override
    public void addConverter(Converter<?, ?> converter) {
        m_delegate.addConverter(converter);
    }

    @Override
    public void addConverter(GenericConverter converter) {
        m_delegate.addConverter(converter);
    }

    @Override
    public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
        m_delegate.addConverterFactory(converterFactory);
    }

    @Override
    public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
        return m_delegate.canConvert(sourceType, targetType);
    }

    @Override
    public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
        return m_delegate.canConvert(sourceType, targetType);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T convert(Object source, Class<T> targetType) {
        //only try to convert to URL in case of file existence case
        if (targetType.equals(URL.class) && source instanceof String) {
            try {
                File file = new File((String) source); //NOSONAR
                if (file.exists()) {
                    return (T) file.toURI().toURL();
                }
            } catch (Exception e) { //NOSONAR
                //ignore it
            }
        }
        return m_delegate.convert(source, targetType);
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        return m_delegate.convert(source, sourceType, targetType);
    }

    @Override
    public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
        m_delegate.removeConvertible(sourceType, targetType);
    }
}

實現(xiàn)一個自己的conversion service ,用environment 的conversion service作為代理识椰, 在onApplicationEvent 時設(shè)置進(jìn)去绝葡。

   public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
       try {
           ConfigurableEnvironment env = event.getEnvironment()
           //setup raptorio file to URL conversion service
           //to support "URL configFileUrl = getProperty(String key, URL.class)"
           URLConfigurableConversionService conversionService = new URLConfigurableConversionService(env.getConversionService());
           env.setConversionService(conversionService);
       } catch (Exception e) {
           logger.error("Failed to init environment variables.", e);
       }
   }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者腹鹉。
  • 序言:七十年代末藏畅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子功咒,更是在濱河造成了極大的恐慌愉阎,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件力奋,死亡現(xiàn)場離奇詭異榜旦,居然都是意外死亡,警方通過查閱死者的電腦和手機景殷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門溅呢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猿挚,你說我怎么就攤上這事咐旧。” “怎么了亭饵?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵休偶,是天一觀的道長。 經(jīng)常有香客問我辜羊,道長踏兜,這世上最難降的妖魔是什么词顾? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮碱妆,結(jié)果婚禮上肉盹,老公的妹妹穿的比我還像新娘。我一直安慰自己疹尾,他們只是感情好上忍,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纳本,像睡著了一般窍蓝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上繁成,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天吓笙,我揣著相機與錄音,去河邊找鬼巾腕。 笑死面睛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尊搬。 我是一名探鬼主播叁鉴,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼佛寿!你這毒婦竟也來了幌墓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤冀泻,失蹤者是張志新(化名)和其女友劉穎克锣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腔长,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年验残,在試婚紗的時候發(fā)現(xiàn)自己被綠了捞附。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡您没,死狀恐怖鸟召,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氨鹏,我是刑警寧澤欧募,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站仆抵,受9級特大地震影響跟继,放射性物質(zhì)發(fā)生泄漏种冬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一舔糖、第九天 我趴在偏房一處隱蔽的房頂上張望娱两。 院中可真熱鬧,春花似錦金吗、人聲如沸十兢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旱物。三九已至,卻和暖如春卫袒,著一層夾襖步出監(jiān)牢的瞬間宵呛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工玛臂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烤蜕,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓迹冤,卻偏偏與公主長得像讽营,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泡徙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容