解决问题:
springboot虽然支持打包的时候通过打包命令中的不同入参来解决发布中遇到的需要发布到各个环境中包的配置文件(一般是application.properties)不同的问题,但是还是需要每一个环境打一个包,这样的话比如我们有开发环境、测试环境、正式环境3个环境,那按这种方式就需要打3次包。
这个文章解决了该问题!打一个包,就能自适应到对应环境使用对应配置文件的问题,基本原理就是,通过预配置好ip对应环境的配置文件的关系,然后程序在启动的时候通过读取启动环境的ip找到对应的配置文件,在runtime自行写入到classpath中,再启动项目,从而做到动态代理选择配置文件的目的。
阅读要求:
1、首先要有java的一些编译啊,虚拟机相关和tomcat相关的知识。(本文基于tomcat8服务器测试)。
2、有java文件啊,数据流啊,系统文件路径啊,这方面的知识。
3、对springboot有一定的了解。
正文:
- 看一下我的配置文件的结构:
1号配置文件就是上文中提到的
“预配置好ip对应环境的配置文件的关系”
而2、3、4、5就是真正项目中的对应不同环境的配置文件(“2公司开发环境”,“3在家办公环境”,“4正式环境”,“5测试环境”)。
程序在启动的初始阶段,会通过1号system-env.properties中对应的内容,找到适配的配置文件,并写入到classpath下的application.properties中,当然,如果你这个application.properties文件中有内容会被覆盖。同理,你也可以通过这种办法来动态配置log4j2.xml中的内容,或其他。
- 看一下我的system-env.properties中的内容
#环境ip设置 共通前缀“system.env.ip” 若要开发环境需要跟dev
#环境对应配置文件 前缀“system.env.properties.file” 若要开发环境需要跟dev
#测试环境 .test
#部署机器ip
system.env.ip.test=192.168.
#静态配置文件地址
system.env.properties.file.test=application-test.properties
#覆盖log4j2.xml文件中的level配置
system.env.log4j2.level.test=error
#正式环境 .prod
system.env.ip.prod=192.168.
system.env.properties.file.prod=application-prod.properties
system.env.log4j2.level.prod=error
#以上不可随意修改!
#------------------------------------------------------------------------------------------
#以下私有个性化环境随意修改,可以加多个人的个人电脑
##------he----->>>-----
#在家办公开发环境
#通配字符串 .dev.self.he
system.env.ip.dev.self.he=192.168.
system.env.properties.file.dev.self.he=application-dev-home.properties
system.env.log4j2.level.dev.self.he=debug
#公司个人开发-he
#通配字符串 .dev.company.he
system.env.ip.dev.company.he=192.168.
system.env.properties.file.dev.company.he=application-dev-company.properties
system.env.log4j2.level.dev.company.he=debug
##------he-----<<<-----
这里ip隐藏了一部分,比如说测试环境
#测试环境 .test
#部署机器ip
system.env.ip.test=192.168.
#静态配置文件地址
system.env.properties.file.test=application-test.properties
这里192.168.*.*这个ip对应的application-test.properties这个配置文件,那程序启动的时候,就会把这个配置文件的内容写到classpath下的application.properties里面。
- 代码
文件处理的工具类
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
public class FileUtils {
public static boolean isSameFile(String firFile, String secFile) throws IOException {
boolean same = true;
BufferedInputStream fir = new BufferedInputStream(new FileInputStream(firFile));
BufferedInputStream sec = new BufferedInputStream(new FileInputStream(secFile));
// 比较文件的长度是否一样
if (fir.available() == sec.available()) {
while (fir.read() != -1 && sec.read() != -1) {
if (fir.read() != sec.read()) {
same = false;
break;
}
}
same = true;
} else {
same = false;
}
fir.close();
sec.close();
return same;
}
public static Properties getPropertiesFile(ClassLoader thisClassLoader, String propertiesFile) throws IOException {
InputStream in = thisClassLoader.getResourceAsStream(propertiesFile);
Properties properties = new Properties();
properties.load(in);
in.close();
return properties;
}
public static Document getXmlDocFile(ClassLoader thisClassLoader, String xmlFile) throws IOException, SAXException, ParserConfigurationException {
InputStream in = thisClassLoader.getResourceAsStream(xmlFile);
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document doc = documentBuilder.parse(in);
in.close();
return doc;
}
}
主要的类
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class PropertiesFileAdapter {
private static String SYSTEM_ENV_PROPERTIES_FILE_NAME = "system-env.properties";
private static String TARGET_PROPERTIES_FILE_NAME = "application.properties";
private static String SYSTEM_ENV_LOG4J2_FILE_NAME = "log4j2.xml";
private static String SYSTEM_ENV_IP = "system.env.ip";
private static String SYSTEM_ENV_PROPERTIES_FILE = "system.env.properties.file";
private static String SYSTEM_ENV_LOG4J2_LEVEL = "system.env.log4j2.level";
private static ClassLoader thisClassLoader;
private static boolean usePropertiesProxy = true;// 是否启动配置文件代理
private static boolean useLogLevelProxy = true;// 使用启动日志级别代理
public static void writeToClassPath()
throws IOException, InterruptedException, SAXException, ParserConfigurationException, TransformerFactoryConfigurationError, TransformerException {
thisClassLoader = Thread.currentThread().getContextClassLoader();
if (thisClassLoader == null) {
thisClassLoader = PropertiesFileAdapter.class.getClassLoader();
}
String refPath = thisClassLoader.getResource("").getFile();
InetAddress inetAddress = InetAddress.getLocalHost();
String hostIp = inetAddress.getHostAddress().toString();// 获得本机Ip
Properties systemEnvProperties = FileUtils.getPropertiesFile(thisClassLoader, SYSTEM_ENV_PROPERTIES_FILE_NAME);
Iterator<Entry<Object, Object>> it = systemEnvProperties.entrySet().iterator();
Map<String, String> ipToEnvMap = new HashMap<String, String>();
Map<String, String> envToPropertyFileMap = new HashMap<String, String>();
Map<String, String> evnToLog4jLevelMap = new HashMap<String, String>();
while (it.hasNext()) {
Entry<Object, Object> item = it.next();
String key = String.valueOf(item.getKey());
if (key.contains(SYSTEM_ENV_IP)) {
ipToEnvMap.put(String.valueOf(item.getValue()), key.replaceAll(SYSTEM_ENV_IP, ""));
} else if (key.contains(SYSTEM_ENV_PROPERTIES_FILE)) {
envToPropertyFileMap.put(key.replaceAll(SYSTEM_ENV_PROPERTIES_FILE, ""), String.valueOf(item.getValue()));
} else if (key.contains(SYSTEM_ENV_LOG4J2_LEVEL)) {
evnToLog4jLevelMap.put(key.replaceAll(SYSTEM_ENV_LOG4J2_LEVEL, ""), String.valueOf(item.getValue()));
}
}
String thisEnv = ipToEnvMap.get(hostIp);
System.out.println("thisEnv : " + thisEnv);
String srcPropertyFileName = null;
if (thisEnv != null) {
srcPropertyFileName = envToPropertyFileMap.get(thisEnv);
}
if (thisEnv == null || srcPropertyFileName == null) {
System.err.println("env setting error, please check system-env.properties");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
URL srcFileUrl = thisClassLoader.getResource(srcPropertyFileName);
FileOutputStream targetFileOutPutStream = null;
String targetFileName = TARGET_PROPERTIES_FILE_NAME;
URL targetFileUrl = thisClassLoader.getResource(targetFileName);
File targetFile = null;
boolean needCopy = false;
if (targetFileUrl == null) {// 目标文件是空需要处理
needCopy = true;
} else {
targetFile = new File(targetFileUrl.getFile());
if (!FileUtils.isSameFile(targetFileUrl.getFile(), srcFileUrl.getFile())) {// 目标文件和源文件不一样才处理
needCopy = true;
}
}
if (needCopy && usePropertiesProxy) {
targetFile = new File(refPath + targetFileName);
targetFileOutPutStream = new FileOutputStream(targetFile);
File srcFile = new File(srcFileUrl.getFile());
System.out.println("--srcFile--");
System.out.println(srcFile.getAbsolutePath());
System.out.println("--targetFile--");
System.out.println(targetFile.getAbsolutePath());
FileInputStream srcFileInputSteam = new FileInputStream(srcFile);
byte[] buf = new byte[srcFileInputSteam.available()];
srcFileInputSteam.read(buf);
targetFileOutPutStream.write(buf);
targetFileOutPutStream.flush();
targetFileOutPutStream.close();
srcFileInputSteam.close();
System.out.println("srcFile >>> targetFile");
}
if (usePropertiesProxy) {
System.out.println("init properties using : " + srcPropertyFileName);
}
// 日志级别代理
String logLevel = evnToLog4jLevelMap.get(thisEnv);
if (useLogLevelProxy && logLevel != null && !"error".equalsIgnoreCase(logLevel)) {
Document doc = FileUtils.getXmlDocFile(thisClassLoader, SYSTEM_ENV_LOG4J2_FILE_NAME);
NodeList element = doc.getElementsByTagName("Logger");
int length = element.getLength();
for (int i = 0; i < length; i++) {
Node loggerNode = element.item(i);
loggerNode.getAttributes().getNamedItem("level").setNodeValue(logLevel);
}
// 写入
Transformer ts = TransformerFactory.newInstance().newTransformer();
ts.transform(new DOMSource(doc), new StreamResult(refPath + SYSTEM_ENV_LOG4J2_FILE_NAME));
System.out.println("init log level using : " + logLevel + " level. you can change by eidt log4j2.xml in runtime");
}
TimeUnit.SECONDS.sleep(3);
}
}
然后springboot中的启动类需要这样处理
启动类一般都是这样开头的,我举个例子,防止误会
public class Application extends SpringBootServletInitializer implements WebApplicationInitializer
在里面要写入两个点,因为开发环境是从main函数启动的,所以main函数第一句要加上我们写好的代码的调用
public static void main(String[] args) throws Throwable {
PropertiesFileAdapter.writeToClassPath();
//后面略
另外一种是从服务器tomcat启动的,这时候不走main函数了,解决办法就是实现这个方法,并调用我们写好的代码。
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
System.out.println("running configure : ");
try {
PropertiesFileAdapter.writeToClassPath();
} catch (Throwable e) {
e.printStackTrace();
SystemUtils.terminat();
}
return application.sources(Application.class);
}
然后启动的时候,最先看到控制台输出的应该是运行这一部分的代码,这代表“大功告成”:
thisEnv : .dev.company.he
–srcFile–
D:\newWs*\target\classes\application-dev-company.properties
–targetFile–
D:\newWs*\target\classes\application.properties
srcFile >>> targetFile
init properties using : application-dev-company.properties
init log level using : debug level. you can change by eidt log4j2.xml in runtime
如果配置文件已经是一样的了,那应该会少输出一些东西。
输出的srcFile>>>targetFile,代表已经从D:\newWs*\target\classes\application-dev-company.properties这个配置文件覆盖写入到了这个配置文件中D:\newWs*\target\classes\application.properties。