原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。java
今天和首架聊到springboot的配置参数问题。他说,这些配置的参数,开发人员拷贝来拷贝去的,很容易出错,不如咱们屏蔽一下吧。linux
确实,通过工程师的ctrl+c
和ctrl+v
,大多数重要的参数已经面目全非,彻底不是当初的模样。我见过太多这样的案例,因此我表示赞同。程序员
为何复制粘贴也会出问题?有两个缘由:由于提供者善变;由于使用者骄傲。web
我摸着首架的手说:能够开工了。spring
随着咱们对springboot 2.0
的了解逐步加深,以及部署环境对打包方式的要求变化,咱们逐步但愿将springboot
应用打包成可执行jar并在启动时更便捷的指定系统参数。apache
好比在linux环境中,或者将其容器化。springboot
一个可能的方式,是将springboot 打包成可执行jar,而后经过相似于以下方式启动或者关闭程序:bash
$> ./application.jar start
$> ./application.jar stop
$> JAVA_OPTS=-Xmx2g ./application.jar start
复制代码
能够看到这种方式很是的简洁,可是SpringBoot默认却不支持。微信
除此以外,咱们可能但愿统一管理springboot的打包方式,好比限定日志目录、统一指定JVM参数,或者在启动时额外的从配置中心拉取一些静态文件等。架构
这些特殊要求,原生的launch.script没法完成,咱们须要扩展launch.scipt或者自定义它。
可是达成这个结果,仍是有些困难,由于原生的机制没法支持。
面临的问题:
<embeddedLaunchScript>
,可是这个脚本只能放在项目的本地目录
。若是咱们将此脚本嵌入在外部的jar中(主要是不但愿全部的项目都重复这个脚本)则可能没法加载。<inlinedConfScript>
,可是这种内联脚本
没法支持复杂的脚本逻辑。解决问题的方式:
launch.script
和自定义的inlined-conf.script
文件都放在此插件模块中。咱们的初心是但愿此插件能够被众多项目通用,script统一管理(修改、升级),业务项目只须要引用便可。spring-boot-maven-plugin
的配置稍微调整一下,就能够引用到这两个script了,由于这个两个script已经经过咱们自研的plugin复制到了项目target目录。从上面能够看出,咱们的目的很简单,就是引用此插件的web项目,在打包时,将两个script复制到web项目的target目录中,以供spring-boot-maven-plugin使用。
此插件的处于package
阶段,主要包含:
LauncherWriterMojo
:在package期间,用于复制脚本文件到使用插件的web项目的target目录。inlined-conf.script
:spring-boot-maven-plugin支持的<inlinedConfScript>
配置,内部主要是指定一些springboot可执行jar支持的一些系统参数。launch.script
:启动脚本,底板来自springboot自带的源码,咱们在内部增长了一些功能,好比拼装JVM参数、系统参数配置等。import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import java.io.*;
@Mojo(name = "package", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class LauncherWriterMojo extends AbstractMojo {
@Parameter(defaultValue = "${basedir}/target", required = true)
private File outputDirectory;
public void setOutputDirectory(File outputDirectory) {
this.outputDirectory = outputDirectory;
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
try {
copy("launch.script");
getLog().info("launch.script has been created.");
copy("inlined-conf.script");
getLog().info("inlined-conf.script has been created.");
} catch (IOException ie) {
throw new MojoExecutionException("launch.script written error!",ie);
}
}
private void copy(String filename) throws IOException{
InputStream inputStream = getClass().getResourceAsStream("/" + filename);
BufferedWriter writer = null;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
File target = new File(outputDirectory + "/" + filename);
target.setExecutable(true,false);
target.setWritable(true,false);
target.setReadable(true,false);
writer = new BufferedWriter(new FileWriter(target));
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
writer.write(line);
writer.newLine();
}
writer.flush();
} finally {
if (inputStream != null) {
inputStream.close();
}
if (writer != null) {
writer.close();
}
}
}
}
复制代码
MODE=service; identity=run; PID_FOLDER=./var; LOG_FOLDER=./; LOG_FILENAME=std.out; pidFilename=pid; JAVA_OPTS="$JAVA_OPTS -XX:NewRatio=2 -XX:G1HeapRegionSize=8m -XX:MaxMetaspaceSize=256m -XX:MaxTenuringThreshold=10 -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=45 -XX:MaxGCPauseMillis=200 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintReferenceGC -XX:+PrintAdaptiveSizePolicy -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=6 -XX:GCLogFileSize=32m -Xloggc:./var/run/gc.log.$(date +%Y%m%d%H%M) -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./var/run/java_pid<pid>.hprof -Dfile.encoding=UTF-8 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=${JMX_PORT:-0} -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"; mkdir -p var/run
复制代码
简单描述一下:此行内脚本,主要是下降用户配置spring-boot-maven-plugin的复杂度,将日志目录、PID文件、除了heap大小以外的其余通用JVM参数等,统一指定,这样使用此插件打包的项目就能够更加规范。
launch.script:代码copy自spring-boot自带的,本文你能够认为没有什么差异。
你的web项目或者module的pom.xml
<plugin>
<groupId>com.??.commons</groupId>
<artifactId>meteor-spring-boot-maven-plugin</artifactId>
<version>${meteor-project.version}</version>
<executions>
<execution>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<executable>true</executable>
<embeddedLaunchScriptProperties>
<inlinedConfScript>${basedir}/target/inlined-conf.script</inlinedConfScript>
</embeddedLaunchScriptProperties>
<embeddedLaunchScript>${basedir}/target/launch.script</embeddedLaunchScript>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
复制代码
固然为了统一插件的使用,你可能会将上述配置放在一个parent-pom.xml中或者一个parent项目中,其余使用此框架的项目直接引用上述插件而再也不指定插件中的配置便可。例如:
<build>
<finalName>application</finalName>
<plugins>
<plugin>
<groupId>com.??.commons</groupId>
<artifactId>meteor-spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
复制代码
使用maven打包以后,生成的application.jar就是可执行文件,且已经将咱们自定义的launch.script融入进去,执行时会运行咱们自定义的的script。
到此为止,咱们自定义打包脚本的功能就已经实现了。在一些持续集成工具之中,这种方式被频繁使用,能够帮助咱们在对项目的技术管理、部署管理,有一个统一的视图。
脚本能够封装一些经常使用、容易出现问题的点和面,提供相应的覆盖机制,也会将公司的基础建设进行集成。减小出错的几率,封装冗余的重复。
做者简介:小姐姐味道 (xjjdog),一个不容许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不同的味道。个人我的微信xjjdog0,欢迎添加好友,进一步交流。