1. 程式人生 > 程式設計 >gradle專案中資原始檔的相對路徑打包技巧必看

gradle專案中資原始檔的相對路徑打包技巧必看

開發java application時,不管是用ant/maven/gradle中的哪種方式來構建,通常最後都會打包成一個可執行的jar包程式,而程式執行所需的一些資原始檔(配置檔案),比如jdbc.properties,log4j2.xml,spring-xxx.xml這些,可以一起打包到jar中,程式執行時用類似classpath*:xxx.xml的去載入,大多數情況下,這樣就能工作得很好了。

但是,如果有一天,需要修正配置,比如:一個應用上線初期,為了除錯方便,可能會把log的日誌級別設定低一些,比如:INFO級別,執行一段時間穩定以後,只需要記錄WARN或ERROR級別的日誌,這時候就需要修改log4j2.xml之類的配置檔案,如果把配置檔案打包在jar檔案內部,改起來就比較麻煩,要把重新打包部署,要麼在線上,先用jar命令將jar包解壓,改好後,再打包回去,比較繁瑣。

面對這種需求,更好的方式是把配置檔案放在jar檔案的外部相對目錄下,程式啟動時去載入相對目錄下的配置檔案,這樣改起來,就方便多了,下面演示如何實現:(以gradle專案為例)

主要涉及以下幾點:

1、如何不將配置檔案打包到jar檔案內

既然配置檔案放在外部目錄了,jar檔案內部就沒必要再重複包含這些檔案了,可以修改build.gradle檔案,參考下面這樣:

processResources {
 exclude { "**/*.*" }
}

相當於覆蓋了預設的processResouces task,這樣gradle打包時,資源目錄下的任何檔案都將排除。

2、log4j2的配置載入處理

log4j2載入配置檔案時,預設情況下會找classpath下的log4j2.xml檔案,除非手動給它指定配置檔案的位置,分析它的原始碼,

可以找到下面這段:

org.apache.logging.log4j.core.config.ConfigurationFactory.Factory#getConfiguration(java.lang.String,java.net.URI)

public Configuration getConfiguration(final String name,final URI configLocation) {
   if (configLocation == null) {
    final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties()
      .getStringProperty(CONFIGURATION_FILE_PROPERTY));
    if (configLocationStr != null) {
     ConfigurationSource source = null;
     try {
      source = getInputFromUri(NetUtils.toURI(configLocationStr));
     } catch (final Exception ex) {
      // Ignore the error and try as a String.
      LOGGER.catching(Level.DEBUG,ex);
     }
     if (source == null) {
      final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
      source = getInputFromString(configLocationStr,loader);
     }
     if (source != null) {
      for (final ConfigurationFactory factory : getFactories()) {
       final String[] types = factory.getSupportedTypes();
       if (types != null) {
        for (final String type : types) {
         if (type.equals("*") || configLocationStr.endsWith(type)) {
          final Configuration config = factory.getConfiguration(source);
          if (config != null) {
           return config;
          }
         }
        }
       }
      }
     }
    } else {
     for (final ConfigurationFactory factory : getFactories()) {
      final String[] types = factory.getSupportedTypes();
      if (types != null) {
       for (final String type : types) {
        if (type.equals("*")) {
         final Configuration config = factory.getConfiguration(name,configLocation);
         if (config != null) {
          return config;
         }
        }
       }
      }
     }
    }
   } else {
    // configLocation != null
    final String configLocationStr = configLocation.toString();
    for (final ConfigurationFactory factory : getFactories()) {
     final String[] types = factory.getSupportedTypes();
     if (types != null) {
      for (final String type : types) {
       if (type.equals("*") || configLocationStr.endsWith(type)) {
        final Configuration config = factory.getConfiguration(name,configLocation);
        if (config != null) {
         return config;
        }
       }
      }
     }
    }
   }
   Configuration config = getConfiguration(true,name);
   if (config == null) {
    config = getConfiguration(true,null);
    if (config == null) {
     config = getConfiguration(false,name);
     if (config == null) {
      config = getConfiguration(false,null);
     }
    }
   }
   if (config != null) {
    return config;
   }
   LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
   return new DefaultConfiguration();
  }

其中常量CONFIGURATION_FILE_PROPERTY的定義為:

public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";

從這段程式碼可以看出,只要在第一次呼叫log4j2的getLogger之前設定系統屬性,將其指到配置檔案所在的位置即可。

3、其它一些配置檔案(比如spring配置)的相對路徑載入

這個比較容易,spring本身就支援從檔案目錄載入配置的能力。

綜合以上分析,可以封裝一個工具類:

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import java.io.File;
public class ApplicationContextUtil {
 private static ConfigurableApplicationContext context = null;
 private static ApplicationContextUtil instance = null;
 public static ApplicationContextUtil getInstance() {
  if (instance == null) {
   synchronized (ApplicationContextUtil.class) {
    if (instance == null) {
     instance = new ApplicationContextUtil();
    }
   }
  }
  return instance;
 }
 public ConfigurableApplicationContext getContext() {
  return context;
 }
 private ApplicationContextUtil() {
 }
 static {
  //載入log4j2.xml
  String configLocation = "resources/log4j2.xml";
  File configFile = new File(configLocation);
  if (!configFile.exists()) {
   System.err.println("log4j2 config file:" + configFile.getAbsolutePath() + " not exist");
   System.exit(0);
  }
  System.out.println("log4j2 config file:" + configFile.getAbsolutePath());
  try {
   //注:這一句必須放在整個應用第一次LoggerFactory.getLogger(XXX.class)前執行
   System.setProperty("log4j.configurationFile",configFile.getAbsolutePath());
  } catch (Exception e) {
   System.err.println("log4j2 initialize error:" + e.getLocalizedMessage());
   System.exit(0);
  }
  //載入spring配置檔案
  configLocation = "resources/spring-context.xml";
  configFile = new File(configLocation);
  if (!configFile.exists()) {
   System.err.println("spring config file:" + configFile.getAbsolutePath() + " not exist");
   System.exit(0);
  }
  System.out.println("spring config file:" + configFile.getAbsolutePath());
  if (context == null) {
   context = new FileSystemXmlApplicationContext(configLocation);
   System.out.println("spring load success!");
  }
 }
}

注:這裡約定了配置檔案放在相對目錄resources下,而且log4j2的配置檔名為log4j2.xml,spring的入口配置檔案為spring-context.xml(如果不想按這個約定來,可參考這段程式碼自行修改)

有了這個工具類,mainclass入口程式上可以這麼用:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
/**
 * Created by yangjunming on 12/15/15.
 * author: [email protected]
 */
public class App {
 private static ApplicationContext context;
 private static Logger logger;
 public static void main(String[] args) {
  context = ApplicationContextUtil.getInstance().getContext();
  logger = LoggerFactory.getLogger(App.class);
  System.out.println("start ...");
  logger.debug("debug message");
  logger.info("info message");
  logger.warn("warn message");
  logger.error("error message");
  System.out.println(context.getBean(SampleObject.class));
 }
}

再次友情提醒:logger的例項化,一定要放在ApplicationContextUtil.getInstance().getContext();之後,否則logger在第一次初始化時,仍然嘗試會到classpath下去找log4j2.xml檔案,例項化之後,後面再設定系統屬性就沒用了。

4、gradle 打包的處理

程式碼寫完了,還有最後一個工作沒做,既然配置檔案不打包到jar裡了,那就得複製到jar包的相對目錄resources下,可以修改build.gradle指令碼,讓計算機處理處理,在代替手動複製配置檔案。

task pack(type: Copy,dependsOn: [clean,installDist]) {
 sourceSets.main.resources.srcDirs.each {
  from it
  into "$buildDir/install/$rootProject.name/bin/resources"
 }
}

增加這個task後,直接用gradle pack 就可以實現打包,並自動複製配置檔案到相對目錄resources目錄下了,參考下圖:

gradle專案中資原始檔的相對路徑打包技巧必看

最後國際慣例,給個示例原始碼:https://github.com/yjmyzz/config-load-demo

gradle pack 後,可進入build/install/config-load-demo/bin 目錄,執行./config-load-demo (windows下執行config-load-demo.bat) 檢視效果,然後嘗試修改resources/log4j2.xml裡的日誌級別,再次執行,觀察變化 。

以上這篇gradle專案中資原始檔的相對路徑打包技巧必看就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。