Keep it simple.

由于开发环境和部署环境的不同,项目的配置通常也是不同的。比如开发环境连接本地的测试数据库,部署环境连接线上的正式数据库;或者开发环境使用的Windows系统,部署环境使用Linux系统。这些不同会影响项目的配置,比如数据库主机IP、文件存储的位置、其它服务IP等等。另外,我们的项目可能会部署到多台服务器上,不同服务器的配置也可能是不同的。

另外,一个项目通常有多个开发者,开发者为了测试代码有可能会频繁修改配置文件,如果所有开发者共用一个配置文件,就会造成开发者之间互相干扰。

所以,必须要采取一种动态的方式来加载配置文件。本文将介绍一种解决方案,其核心思想是为不同主机创建不同的配置文件,文件名使用主机名进行区分,以使多个配置文件相互独立。

一般情况下,我们会将数据库连接的配置放在配置文件里。在开发环境下,数据库的主机为127.0.0.1,而正式环境可能为192.168.100.139。那么我们就根据主机名创建两个配置文件,如开发环境的主机名为zq-pc,那么配置文件名就叫zq-pc.properties;部署环境的主机名为server1,那么配置文件名就叫server1.properties。配置文件的代码如下:

zq-pc.properties代码:

mysql.host = 127.0.0.1
mysql.user = root
mysql.password = 123456

server1.properties代码:

mysql.host = 192.168.100.139
mysql.user = root
mysql.password = 123456

下面考虑一个问题,如果该项目的开发者非常多,每一个开发者都要创建一个配置文件,甚至一个开发者可能不只一台开发机,比如在公司用Linux机,家里用Windows机,还有一台Mac笔记本,都要拿来做开发,配置文件的数量就可能有点多了……虽然这没有什么问题,但我们还是能进一步优化。因此,我们决定创建一个common.properties文件,作为默认配置文件使用,即若项目在启动时找不到主机名对应的配置文件,就直接采用common.properties即可。若开发者修改了自己的配置文件里的某项配置,则自动覆盖common.properties里的相应配置。那么如何实现呢?请看下面的代码,这段代码就是一个Properties辅助类,所有方法都是静态(static)的。

PropertiesHelper.java代码:

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

public class PropertiesHelper {

    private static final Logger logger = Logger.getLogger(PropertiesHelper.class);

    private static final Properties properties = new Properties();

    static {
        // 首先加载默认配置文件
        String commonProperties = getConfigBasePath() + "common.properties";
        logger.info("Loading propreties file: " + commonProperties);
        loadProperties(new File(commonProperties));
        // 然后加载主机名对应的配置文件
        String localHostName = getLocalHostName();
        if (StringUtils.isNotBlank(localHostName)) {
            String localHostProperties = getConfigBasePath() + localHostName + ".properties";
            logger.info("Loading propreties file: " + localHostProperties);
            loadProperties(new File(localHostProperties));
        }
    }

    // 加载配置文件
    private static void loadProperties(File propertiesFile) {
        try {
            InputStream in = new BufferedInputStream(new FileInputStream(propertiesFile));
            properties.load(in);
            in.close();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
    }

    // 获取主机名
    private static String getLocalHostName() {
        InetAddress localHost = null;
        try {
            localHost = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            logger.error("Can't get host name.", e);
        }
        if (localHost != null) {
            return localHost.getHostName();
        }
        return null;
    }

    // 获取properties文件的根目录
    private static String getConfigBasePath() {
        return PropertiesHelper.class.getResource("/config/").getPath();
    }

    public static Object get(Object key) {
        return properties.get(key);
    }

    public static boolean containsKey(Object key) {
        return properties.containsKey(key);
    }

}

注意上面的代码使用了同一个Properties对象,它的Properties.load()方法被调用了两次。第一次调用时加载的是common.properties,第二次调用时加载的是主机名.properties。如果两次加载有相同的配置项,则第二次加载的配置项会覆盖第一次加载的配置项。

下面说说log4j的配置,解决思路跟上面的一样。创建zq-pc.log4j.propreties文件,我的开发机使用该配置。再创建server1.log4j.propreties文件,部署环境的server1服务器使用该配置。配置代码如下:

zq-pc.log4j.propreties代码(适用Windows系统):

log4j.rootLogger = INFO,R

log4j.appender.R = org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File = D:\zq_blog.log
log4j.appender.R.layout = org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern = [%-d{yyyy-MM-dd HH:mm:ss}]%5p[%F:%L]%m%n

server1.log4j.propreties代码(适用Linux系统):

log4j.rootLogger = INFO,R

log4j.appender.R = org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File = /alidata/log/zq_blog/info.log
log4j.appender.R.layout = org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern = [%-d{yyyy-MM-dd HH:mm:ss}]%5p[%F:%L]%m%n

那么,我们如何让log4j自动加载不同的配置呢?本人参考了 Spring 框架的 Log4jConfigListener 类的实现,创建了自己的Log4jConfigListener类,这是一个ServletContextListener的实现。其主要思路就是在web服务启动时,通过Log4jConfigListener自动找到主机名对应的log4j配置文件。

Log4jConfigListener.java代码如下:

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Properties;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.log4j.PropertyConfigurator;

public class Log4jConfigListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        String basePath = getBasePath();
        String localHostName = getLocalHostName();
        Properties properties = new Properties();
        String propertiesPath = basePath + localHostName + ".log4j.properties";
        servletContext.log("Initializing log4j from [" + propertiesPath + "]");
        File propertiesFile = new File(propertiesPath);
        try {
            InputStream in = new BufferedInputStream(new FileInputStream(propertiesFile));
            properties.load(in);
            in.close();
        } catch (Exception e) {
            servletContext.log(e.getMessage(), e);
        }
        PropertyConfigurator.configure(properties);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }

    private static String getLocalHostName() {
        InetAddress localHost = null;
        try {
            localHost = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
        }
        if (localHost != null) {
            return localHost.getHostName();
        }
        return null;
    }

    private static String getBasePath() {
        return Log4jConfigListener.class.getResource("/").getPath();
    }

}

接着在web.xml中的listener节点上添加我们的Log4jConfigListener,使其生效。

web.xml代码片段:

<listener>
    <listener-class>com.zhengqiangblog.blog.util.Log4jConfigListener</listener-class>
</listener>

至此,自定义配置文件和log4j配置文件的动态加载就基本实现了。唯一需要注意的是,你要保证主机名不能重复

  • zhengqiang
  • 技术
  • 2016-01-23 03:50:28.0
  • 2822