Springboot 2.0打包與自定義launch.script

原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。java

今天和首架聊到springboot的配置參數問題。他說,這些配置的參數,開發人員拷貝來拷貝去的,很容易出錯,不如咱們屏蔽一下吧。linux

確實,通過工程師的ctrl+cctrl+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或者自定義它。

可是達成這個結果,仍是有些困難,由於原生的機制沒法支持。

面臨的問題:

  1. 即便咱們從新開發了launch.script,藉助<embeddedLaunchScript>,可是這個腳本只能放在項目的本地目錄。若是咱們將此腳本嵌入在外部的jar中(主要是不但願全部的項目都重複這個腳本)則可能沒法加載。
  2. 即便咱們使用<inlinedConfScript>,可是這種內聯腳本沒法支持複雜的腳本邏輯。

解決問題的方式:

  1. 爲了保留springboot原生的launch.script的絕大部分功能,因此從springboot源碼中copy一份。
  2. 開發一個maven-plugin,將咱們自定義的launch.script和自定義的inlined-conf.script文件都放在此插件模塊中。咱們的初心是但願此插件能夠被衆多項目通用,script統一管理(修改、升級),業務項目只須要引用便可。
  3. 這個maven-plugin,功能很是簡單,就是在package階段,將這兩個script複製到項目的target目錄中。
  4. spring-boot-maven-plugin的配置稍微調整一下,就能夠引用到這兩個script了,由於這個兩個script已經經過咱們自研的plugin複製到了項目target目錄。

1、maven-plugin開發

從上面能夠看出,咱們的目的很簡單,就是引用此插件的web項目,在打包時,將兩個script複製到web項目的target目錄中,以供spring-boot-maven-plugin使用。

此插件的處於package階段,主要包含:

  1. LauncherWriterMojo:在package期間,用於複製腳本文件到使用插件的web項目的target目錄。
  2. inlined-conf.script:spring-boot-maven-plugin支持的<inlinedConfScript>配置,內部主要是指定一些springboot可執行jar支持的一些系統參數。
  3. launch.script:啓動腳本,底板來自springboot自帶的源碼,咱們在內部增長了一些功能,好比拼裝JVM參數、系統參數配置等。

LauncherWriterMojo.java

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();  
            }  
        }  
    }  
}  
複製代碼

inlined-conf.script

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自帶的,本文你能夠認爲沒有什麼差異。

2、使用方式

你的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。

END

到此爲止,咱們自定義打包腳本的功能就已經實現了。在一些持續集成工具之中,這種方式被頻繁使用,能夠幫助咱們在對項目的技術管理、部署管理,有一個統一的視圖。

腳本能夠封裝一些經常使用、容易出現問題的點和麪,提供相應的覆蓋機制,也會將公司的基礎建設進行集成。減小出錯的機率,封裝冗餘的重複。

做者簡介:小姐姐味道 (xjjdog),一個不容許程序員走彎路的公衆號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高併發世界,給你不同的味道。個人我的微信xjjdog0,歡迎添加好友,​進一步交流。​

相關文章
相關標籤/搜索