受不了springboot的yml和properties配置谤辜,我擴展出了groovy配置

文中代碼地址:https://github.com/gaohanghbut/groovy-configuration

起因

Springboot支持yml和properties兩種方式的配置,不知道有沒有同學和我一樣剿骨,對yml, properties, xml這類配置非常不喜歡,配置太多了之后,可讀性急劇下降勃黍,維護配置非常困難,估計只有java這樣的編程語言的框架使用大量的xml, properties等作為配置文件了

但是Java支持groovy腳本晕讲,我們可以利用groovy來取代yml和properties覆获,使用application.groovy替代application.yml或application.propertie,使用application-xxx.groovy替代application-xxx.yml或application-xxx.properties瓢省,并且支持groovy語法弄息,配置類似如下圖,對于groovy類中類型為String或者GString的屬性都會被認為是一個property勤婚,對于類型為Map的屬性摹量,會認為是property的集合,基于這個特性馒胆,我們可以將同一類型的配置用同一個Map表示缨称,極大的增加了可讀性,降低了維護成本:

image

如果只需要用一個Map表示所有的配置祝迂,則可以不定義類睦尽,只定義一個Map:

image

在工程的resources目錄下,通過application.groovy或者application-xxx.groovy表示配置:

image

可支持profile型雳,本文中的例子是一個簡化的配置当凡,實際中的配置要復雜得多,在實際應用中纠俭,可將application.groovy與application.properties或者application.yml共存沿量。

應用的啟動類則不變,還是原來的樣子:

image

關(guān)于實現(xiàn)方式冤荆,我們先從springboot的擴展開始

SpringBoot的擴展

這里不從頭講述springboot的擴展朴则,這不是文章的重點,我們直接進入到一個類PropertySourcesLoader钓简,其中初始化相關(guān)代碼:

image

可以看到乌妒,這里通過SpringFactoriesLoader獲取了PropertySourceLoader接口的實例肖卧,那么SpringFactoriesLoader是干嘛的许溅?它就是用來加載spring的jar包中的spring.factories文件的,源碼如下:

image
image

咱們再來看看PropertySourceLoader有哪些實現(xiàn):

image

可以看到辽俗,springboot提供了properties和yml兩種實現(xiàn)坐榆,咱們再看看PropertySourcesLoader中加載配置的代碼:

image

通過這個方法我們可以看到拴魄,springboot分別用了不同的PropertySourceLoader加載不同格式的配置

實現(xiàn)對groovy配置的支持

咱們先看看PropertySourceLoader接口的定義:

image

它只有兩個方法:

  • getFileExtensions:用于獲取支持的配置文件的后綴
  • load:用于加載配置,得到PropertySource

講到這里,大家應該就明白了匹中,想要支持groovy夏漱,分兩步即可:

  1. 實現(xiàn)一個PropertySourceLoader,用于加載groovy文件并得到PropertySource
  2. 創(chuàng)建META-INF/spring.factories并將實現(xiàn)的PropertySourceLoader配置在此文件中

咱們來看代碼顶捷,先是對PropertySourceLoader的實現(xiàn):

import com.google.common.collect.Sets;
import groovy.lang.GString;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.scripting.groovy.GroovyScriptFactory;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * springboot 支持groovy配置
 *
 * @author gaohang
 */
public class GroovyPropertySourceLoader implements PropertySourceLoader {

  private static final String[] STRINGS = {"groovy"};

  private final Set<String> loaded = Sets.newHashSet();

  @Override
  public String[] getFileExtensions() {
    return STRINGS;
  }

  @Override
  public PropertySource<?> load(final String name, final Resource resource, final String profile) throws IOException {
    return createStringValueResolver((ClassPathResource) resource);
  }

  private PropertySource createStringValueResolver(final ClassPathResource resource) throws IOException {

    if (loaded.contains(resource.getPath())) {
      return null;
    }

    final Properties properties = new Properties();

    try {
      final Object scriptedObject = getGroovyConfigObject(resource);

      if (scriptedObject instanceof Map) {
        putToProperties(properties, (Map<?, ?>) scriptedObject);
      } else {
        final List<Field> fields = Reflections.getFields(scriptedObject.getClass());
        for (Field field : fields) {
          final Object value = Reflections.getField(field.getName(), scriptedObject);
          if (value instanceof Map) {
            putToProperties(properties, (Map<?, ?>) value);
          } else if (value instanceof String || value instanceof GString) {
            properties.put(field.getName(), String.valueOf(value));
          }
        }
      }
      return new PropertiesPropertySource("groovy:" + resource.getPath(), properties);
    } finally {
      loaded.add(resource.getPath());
    }
  }

  private void putToProperties(final Properties properties, final Map<?, ?> values) {
    if (CollectionUtils.isEmpty(values)) {
      return;
    }
    for (Map.Entry<?, ?> en : values.entrySet()) {
      properties.put(String.valueOf(en.getKey()), String.valueOf(en.getValue()));
    }
  }

  private Object getGroovyConfigObject(final ClassPathResource scriptSourceLocator) throws IOException {
    final GroovyScriptFactory groovyScriptFactory = new GroovyScriptFactory(scriptSourceLocator.getPath());
    groovyScriptFactory.setBeanClassLoader(getClass().getClassLoader());

    final ResourceScriptSource resourceScriptSource = new ResourceScriptSource(scriptSourceLocator);
    return groovyScriptFactory.getScriptedObject(resourceScriptSource);
  }

}

有了這個GroovyPropertySourceLoader后挂绰,我們再創(chuàng)建spring.factories:

image

其中的內(nèi)容:

org.springframework.boot.env.PropertySourceLoader=**
cn.yxffcode.springboot.configuration.groovy.GroovyPropertySourceLoader

最后,GroovyPropertySourceLoader中使用到的Reflections類:

import com.google.common.collect.Lists;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

/**
 * @author gaohang on 15/12/4.
 */
final class Reflections {
  private Reflections() {
  }

  private static Field findField(Class<?> clazz, String name) {
    return findField(clazz, name, null);
  }

  public static Field findField(Class<?> clazz, String name, Class<?> type) {
    Class<?> searchType = clazz;
    while (!Object.class.equals(searchType) && searchType != null) {
      Field[] fields = searchType.getDeclaredFields();
      for (Field field : fields) {
        if ((name == null || name.equals(field.getName())) && (type == null || type
            .equals(field.getType()))) {
          return field;
        }
      }
      searchType = searchType.getSuperclass();
    }
    return null;
  }

  public static List<Field> getFields(Class<?> clazz) {
    final List<Field> fields = Lists.newArrayList();
    Class<?> type = clazz;
    while (type != Object.class) {
      final Field[] declaredFields = type.getDeclaredFields();
      fields.addAll(Arrays.asList(declaredFields));
      type = type.getSuperclass();
    }
    return fields;
  }

  public static Object getField(String fieldName, Object target) {
    Field field = findField(target.getClass(), fieldName);
    if (!field.isAccessible()) {
      field.setAccessible(true);
    }
    try {
      return field.get(target);
    } catch (IllegalAccessException ex) {
      throw new IllegalStateException("Unexpected reflection exception - " + ex.getClass()
          .getName() + ": " + ex.getMessage(), ex);
    }
  }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末服赎,一起剝皮案震驚了整個濱河市葵蒂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌重虑,老刑警劉巖践付,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缺厉,居然都是意外死亡永高,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門提针,熙熙樓的掌柜王于貴愁眉苦臉地迎上來命爬,“玉大人,你說我怎么就攤上這事辐脖∷峭穑” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵揖曾,是天一觀的道長落萎。 經(jīng)常有香客問我,道長炭剪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任翔脱,我火速辦了婚禮奴拦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘届吁。我一直安慰自己错妖,他們只是感情好,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布疚沐。 她就那樣靜靜地躺著暂氯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亮蛔。 梳的紋絲不亂的頭發(fā)上痴施,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音,去河邊找鬼辣吃。 笑死动遭,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的神得。 我是一名探鬼主播厘惦,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哩簿!你這毒婦竟也來了宵蕉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤节榜,失蹤者是張志新(化名)和其女友劉穎羡玛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體全跨,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡缝左,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了浓若。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渺杉。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挪钓,靈堂內(nèi)的尸體忽然破棺而出是越,到底是詐尸還是另有隱情,我是刑警寧澤碌上,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布倚评,位于F島的核電站,受9級特大地震影響馏予,放射性物質(zhì)發(fā)生泄漏天梧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一霞丧、第九天 我趴在偏房一處隱蔽的房頂上張望呢岗。 院中可真熱鬧,春花似錦蛹尝、人聲如沸后豫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挫酿。三九已至,卻和暖如春愕难,著一層夾襖步出監(jiān)牢的瞬間早龟,已是汗流浹背惫霸。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拄衰,地道東北人它褪。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像翘悉,于是被迫代替她去往敵國和親茫打。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

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

  • SpringBoot基礎(chǔ) 學習目標: 能夠理解Spring的優(yōu)缺點 能夠理解SpringBoot的特點 能夠理解S...
    dwwl閱讀 5,446評論 4 81
  • 一妖混、Spring Boot基本配置 1老赤、入口類和@SpringBootApplication Spring Boo...
    夢中一點心雨閱讀 18,547評論 0 5
  • 配置文件解析(下) 原創(chuàng)者:文思 一、yml(YAML Ain’t Markup Language)基本用法...
    文思li閱讀 1,981評論 0 2
  • SpringMVC原理分析 Spring Boot學習 5制市、Hello World探究 1抬旺、POM文件 1、父項目...
    jack_jerry閱讀 1,289評論 0 1
  • 對呀祥楣,已經(jīng)是2017年了开财,準確來說,2017年開始3天了误褪。對于一個畢業(yè)生來說责鳍,2017年應該是憧憬的一年吧,...
    沐11閱讀 319評論 3 2