开发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: yangjunming@huijiame.com
*/
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目录下了,参考下图:

最后国际惯例,给个示例源码: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项目中资源文件的相对路径打包处理技巧的更多相关文章

  1. 【Android Studio探索之路系列】之十:Gradle项目构建系统(四):Android Studio项目多渠道打包

    作者:郭孝星 微博:郭孝星的新浪微博 邮箱:allenwells@163.com 博客:http://blog.csdn.net/allenwells github:https://github.co ...

  2. 第一次使用Android Studio时你应该知道的一切配置(三):gradle项目构建

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...

  3. 【转】第一次使用Android Studio时你应该知道的一切配置(三):gradle项目构建

    原文网址:http://www.cnblogs.com/smyhvae/p/4456420.html [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.c ...

  4. Delphi编程中资源文件的应用

    Delphi编程中资源文件的应用/转自 http://chamlly.spaces.live.com/blog/cns!548f73d8734d3acb!236.entry一.引子: 现在的Windo ...

  5. Android Studio 入门级教程(三):gradle项目构建

    声明 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4456420.html [系列] Andr ...

  6. 将Gradle项目公布到maven仓库

    将Gradle项目公布到maven仓库 1 Gradle简单介绍 1.1 Ant.Maven还是Gradle? 1.1.1 Ant和Maven介绍 全称为Apache Maven,是一个软件(特别是J ...

  7. Spring boot dubbo+zookeeper 搭建------基于gradle项目的消费端与服务端分离实战

    1. Dubbo简介 Dubbo是Alibaba开源的分布式框架,是RPC模式的一种成熟的框架,优点是可以与Spring无缝集成,应用到我们的后台程序中.具体介绍可以查看Dubbo官网. 2. Why ...

  8. Android官方技术文档翻译——迁移 Gradle 项目到1.0.0 版本

    本文译自Android官方技术文档<Migrating Gradle Projects to version 1.0.0>,原文地址:http://tools.android.com/te ...

  9. linux下将eclipse项目转换为gradle项目

    本文针对于在linux环境下,不使用eclipse而把一个eclipse项目转换为gradle默认结构的项目的情况,脚本可能在mac下也适用,未验证. windows中的转换问题,以及使用eclips ...

随机推荐

  1. JavaScript利用装饰模拟实现私有状态

    在经典的面向对象编程中,经常需要将对象的某个状态封装或隐藏在对象内,只有通过对象的一幅幅和能访问这些状态,对外只暴露一些重要的状态变量可以直接读写. 我们可以通过将变量(或参数)装饰在一个构造函数内来 ...

  2. 关于腾讯云ubuntu服务器tomcat访问慢问题

    在腾讯云上配了个一元的学生云,开始一切正常,直到配置tomcat开始出现各种莫名其妙的问题.最莫名其妙的是tomcat启动了,端口也 正常监听,安全组也放行端口了,然后问题来了. 用浏览器访问tomc ...

  3. HTML块级标签汇总(小篇)

    块级元素,简单来说,就是自己独占一行的元素.其特点: ①总是在新行上开始: ②高度,行高以及外边距和内边距都可控制: ③宽度缺省是它的容器的100%,除非设定一个宽度. ④它可以容纳内联元素和其他块元 ...

  4. iOS 中常用的对密码进行MD5加密

    iOS中MD5加密 标签(空格分隔): iOS MD5 + (NSString *)MD5:(NSString *)str { const char *cStr = [str UTF8String]; ...

  5. 升级Xcode8、iOS10问题记录

    1.webView的代理方法: 升级前: - (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)e ...

  6. centos7 无法启动网卡

    (1)需要指定一下hwaddr (2)onboot=yes /etc/sysconfig/network-script/

  7. 【转】JavaScript中的原型和继承

    请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans ...

  8. MS SQL 需要定期清理日志文件

    前言碎语 关于对SQL SERVER 日志文件管理方面了解不多的话,可以参考我的这篇博客文章“MS SQL 日志记录管理”,不过这篇文章只是介绍对SQL SERVER日志记录的深入认知了解,并没有提出 ...

  9. OpenSessionInViewFilter配置和作用

    Spring为我们解决Hibernate的Session的关闭与开启问题. Hibernate 允许对关联对象.属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Sessio ...

  10. Eclipse 安装 jBPM 插件

    下载jbpm-installer安装包并解压,找到 jbpm-installer\build.properties搜索eclipse.home 修改 eclipse.home=./eclipse 成 ...