美文网首页
java项目打包最佳实践

java项目打包最佳实践

作者: Nisus_Liu | 来源:发表于2020-09-09 12:18 被阅读0次

    摸索了一晚上外加多年经验终于有了这个最佳实践.

    环境

    • assembly
    • springboot
    • idea
    • java8
    • logback

    需求

    • jar包要可执行: java -jar jar_name.
    • 依赖分离: 主业务代码独立成可执行jar, 其他所有依赖放到./lib下.
    • 配置文件分离: application.yml, logback.xml等配置文件不在jar内, 而是在项目根目录下. 这样方便中途查看和修改配置.
    • 分环境打包: 我这里分了三个环境dev,test,prod.
    • 脚本启动,停止,重启应用.

    配置参考

    pom.xml

    <profiles>
            <profile>
                <!-- 本地开发环境 -->
                <id>dev</id>
                <properties>
                    <profileActive>dev</profileActive>
                </properties>
                <activation>
                    <activeByDefault>true</activeByDefault>
                </activation>
                <!--<build>
                    &lt;!&ndash;Idea运行时, 需要借助这个配置build指定的配置文件. 打包时排除了. (缺点是打包dev时配置文件不会分离)&ndash;&gt;
                    <resources>
                        <resource>
                            <directory>src/main/resources</directory>
                            <filtering>true</filtering>
                        </resource>
                    </resources>
                </build>-->
            </profile>
            <profile>
                <!-- 测试环境 -->
                <id>test</id>
                <properties>
                    <profileActive>test</profileActive>
                </properties>
            </profile>
            <profile>
                <!-- 生产环境 -->
                <id>prod</id>
                <properties>
                    <profileActive>prod</profileActive>
                </properties>
            </profile>
    
        </profiles>
    
    
        <build>
            <!--例: 让logback.xml中解析pom.xml的属性变量-->
            <pluginManagement>
                <plugins>
                    <plugin>
                        <artifactId>maven-resources-plugin</artifactId>
                        <configuration>
                            <encoding>utf-8</encoding>
                            <useDefaultDelimiters>true</useDefaultDelimiters>
                        </configuration>
                    </plugin>
                </plugins>
            </pluginManagement>
            <finalName>${project.artifactId}-${project.version}</finalName>
            <resources>
                <!--让xml等资源文件在maven这里过一手, 就可以解析里面的属性值引用了 `${xxx}`-->
                <resource>
                    <directory>src/main/resources</directory>
                    <!--<includes>-->
                    <!--    <include>**/*.xml</include>-->
                    <!--</includes>-->
                    <!--<excludes>
                        &lt;!&ndash;排除 resources 下所有(assembly将其放到根目录下)&ndash;&gt;
                        <exclude>**/*</exclude>
                        &lt;!&ndash;<exclude>**/*.properties</exclude>&ndash;&gt;
                        &lt;!&ndash;<exclude>**/*.yml</exclude>&ndash;&gt;
                    </excludes>-->
                    <filtering>true</filtering>
                </resource>
            </resources>
            <plugins>
                <!--<plugin>-->
                <!--    <groupId>org.springframework.boot</groupId>-->
                <!--    <artifactId>spring-boot-maven-plugin</artifactId>-->
                <!--</plugin>-->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <!--<version>2.2-beta-5</version>-->
                    <configuration>
                        <descriptors>
                            <descriptor>src/main/resources/assembly.xml</descriptor>
                        </descriptors>
                    </configuration>
                    <executions>
                        <execution>
                            <id>make-my-jar-with-dependencies</id>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>
                            <configuration>
                                <finalName>${project.artifactId}</finalName>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <configuration>
                        <!--不打包资源文件 !推荐, 这样可以实现打包和Ide运行分开, 打包排除资源文件, Ide运行包含, 保证本地运行成功. 优于 resource 中配置排除.
                        但需要仔细, 避免将不能排除的文件排除了-->
                        <excludes>
                            <exclude>*.xml</exclude>
                            <exclude>*.yml</exclude>
                            <exclude>*.properties</exclude>
                            <exclude>*.sh</exclude>
                            <exclude>public</exclude>
                            <exclude>conf</exclude>
                        </excludes>
                        <archive>
                            <manifest>
                                <!--指定入口类-->
                                <mainClass>com.gx.app.GxAppApplication</mainClass>
                                <addClasspath>true</addClasspath>
                                <classpathPrefix>lib/</classpathPrefix>
                                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                                <!-- manifest是否含时间戳  jar包不包含唯一版本标识 -->
                                <useUniqueVersions>false</useUniqueVersions>
                            </manifest>
                            <manifestEntries>
                                <!--MANIFEST.MF 中 Class-Path 有当前目录  ! devtools 不能打进依赖, 否则不停的重启 !
                                当前目录加入classpath便于使用当前目录下的配置文件, 如 logback(就不需要脚本中指定配置文件了)-->
                                <Class-Path>./</Class-Path>
                                <!--    <implementation-version>${project.version}</implementation-version>-->
                                <!--    <implementation-build>${buildNumber}</implementation-build>-->
                            </manifestEntries>
                        </archive>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    

    assembly.xml

    <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
        <id>assembly</id>
        <formats>
            <format>dir</format>
        </formats>
        <fileSets>
            <fileSet>
                <directory>${project.basedir}</directory>
                <outputDirectory>/</outputDirectory>
                <filtered>true</filtered>
                <includes>
                    <include>application.yml</include>
                    <include>application-${profileActive}.yml</include>
                    <include>*.xml</include>
                </includes>
            </fileSet>
            <fileSet>
                <directory>${project.build.directory}/site</directory>
                <outputDirectory>docs</outputDirectory>
            </fileSet>
            <!--     <fileSet> -->
            <!--       <directory>${project.basedir}/src/main/resources</directory> -->
            <!--       <outputDirectory>/</outputDirectory> -->
            <!--       <filtered>true</filtered> -->
            <!--       <fileMode>0755</fileMode> -->
            <!--       <includes> -->
            <!--         <include>*.properties</include> -->
            <!--       </includes> -->
            <!--     </fileSet> -->
            <fileSet>
                <directory>${project.basedir}/src/main/resources</directory>
                <outputDirectory>/</outputDirectory>
                <filtered>true</filtered>
                <fileMode>0775</fileMode>
                <includes>
                    <!--将资源全部移到根目录下-->
                    <include>**/*</include>
                </includes>
            </fileSet>
            <fileSet>
                <directory>${project.basedir}/src/main/resources/conf/${profileActive}</directory>
                <outputDirectory>/</outputDirectory>
                <filtered>true</filtered>
                <fileMode>0755</fileMode>
                <includes>
                    <include>*.properties</include>
                </includes>
            </fileSet>
        </fileSets>
        <dependencySets>
            <dependencySet>
                <outputDirectory>/lib</outputDirectory>
                <outputFileNameMapping>
                    ${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}
                </outputFileNameMapping>
                <useProjectArtifact>true</useProjectArtifact>
                <scope>runtime</scope>
                <excludes>
                    <exclude>${project.groupId}:${project.artifactId}:*</exclude>
                </excludes>
            </dependencySet>
            <dependencySet>
                <scope>provided</scope>
                <outputDirectory>/</outputDirectory>
                <includes>
                    <include>${project.groupId}:${project.artifactId}:*</include>
                </includes>
            </dependencySet>
        </dependencySets>
    </assembly>
    
    

    logback.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Reference Manual http://logback.qos.ch/manual/index.html -->
    <configuration>
        <!-- 修改部分 如果有job需要监控,单独修改 -->
        <property name="LOG_HOME" value="./logs" />
    
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder charset="UTF-8">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <!-- 所有日志 -->
        <appender name="RollingFile"
            class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_HOME}/${project.artifactId}.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/rolling/${project.artifactId}_%d{yyyy-MM-dd}.%i.log.zip
                </fileNamePattern>
                <!-- 保存多少天 -->
                 <maxHistory>30</maxHistory>
                <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
    
        </appender>
    
        <!-- ###################### SQL日志监控 ###################### -->
        <appender name="SqlStatistics"
            class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/sql/${project.artifactId}_sql_%d{yyyy-MM-dd}.log
                </fileNamePattern>
                <!-- <maxHistory>3</maxHistory> -->
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
    
    
    
        <logger name="java.sql.Connection" level="INFO">
            <appender-ref ref="SqlStatistics" />
        </logger>
        <logger name="java.sql.Statement" level="INFO">
            <appender-ref ref="SqlStatistics" />
        </logger>
        <logger name="java.sql.PreparedStatement" level="INFO">
            <appender-ref ref="SqlStatistics" />
        </logger>
    
        <!-- ###################### SQL日志监控 ###################### -->
    
    
    
        <!-- ###################### DAO日志监控 ###################### -->
        <appender name="DaoStatistics"
            class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/dao/${project.artifactId}_dao_%d{yyyy-MM-dd}.log
                </fileNamePattern>
                <!-- <maxHistory>3</maxHistory> -->
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
    
    <!--    <logger name="**.dao.**" level="INFO"> -->
        <logger name="com.jfbank.fincloud.loan.cif.biz.core.dao" level="DEBUG">
            <appender-ref ref="DaoStatistics" />
        </logger>
    
        <!-- ###################### SQL日志监控 ###################### -->
    
        <appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_HOME}/error/${project.artifactId}_error.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/error/${project.artifactId}_error_%d{yyyy-MM-dd}.log
                </fileNamePattern>
                <!-- <maxHistory>60</maxHistory> -->
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
    
    
        <!-- 测试环境+开发环境. 多个使用逗号隔开. -->
        <springProfile name="dev">
            <root level="DEBUG">
                <appender-ref ref="STDOUT" />
                 <appender-ref ref="RollingFile" />
                 <appender-ref ref="errorFile" />
            </root>
        </springProfile>
    
        <springProfile name="test">
            <root level="DEBUG">
                <!-- <appender-ref ref="STDOUT" /> -->
                <appender-ref ref="RollingFile" />
                <appender-ref ref="errorFile" />
            </root>
        </springProfile>
    
        <!-- 生产环境. -->
        <springProfile name="prod">
            <root level="INFO">
                <!-- <appender-ref ref="STDOUT" /> -->
                <appender-ref ref="RollingFile" />
                <appender-ref ref="errorFile" />
            </root>
        </springProfile>
    
    </configuration>
    
    

    application.yml

    spring:
      application:
        name: GxApp
      profiles:
        active: @profileActive@
    
    
    logging:
      level:
        root: info
        com.gx: debug
    

    启动脚本

    #!/bin/bash
    
    #此脚本为Linux下启动java程序的通用脚本。(包含启动,停止,重启)
    #cd 进入脚本执行的bin目录,sh run.sh start(启动) | stop(停止)| restart(重启)
    #
    
    #bin目前路径以及相关目录路径
    cd `dirname $0`
    BIN_DIR=`pwd`
    DEPLOY_DIR=`pwd`
    #LOG_BACK=-Dlogback.configurationFile=$DEPLOY_DIR/logback.xml
    JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
    JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false  -Djava.rmi.server.hostname=$ip "
    #拼接全路径jar包名, 为了精确匹配jar包, 能确保准确定位pid
    JAR_NAME=$BIN_DIR/`ls ${project.artifactId}*.jar`
    cmd=$2
    echo "$cmd"
    
    start(){
        echo "DEPLOY_DIR=$DEPLOY_DIR"
        PIDS=`ps  --no-heading -C java -f --width 1000 | grep "$DEPLOY_DIR" |awk '{print $2}'`
        if [ -n "$PIDS" ]; then
             echo "ERROR: The $SERVER_NAME already started!"
            echo "PID: $PIDS"
            exit 1
        fi
    
        echo -e "Starting the $JAR_NAME ...\c"
        nohup java -Xms256M -Xmx1024M -XX:PermSize=128M -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -Xloggc:./gc.log  -XX:+PrintGCDateStamps -jar $JAR_NAME > /dev/null 2>&1 &
        COUNT=0
        while [ $COUNT -lt 1 ]; do
            echo -e ".\c"
            sleep 1
    
            COUNT=`ps  --no-heading -C java -f --width 1000 | grep "$JAR_NAME" | awk '{print $2}' | wc -l`
    
                if [ $COUNT -gt 0 ]; then
                    break
                fi
        done
        echo "start OK!"
        PIDS=`ps  --no-heading -C java -f --width 1000 | grep "$JAR_NAME" | awk '{print $2}'`
        echo "PID: $PIDS"
        echo $PIDS > .pid
    
    
    
    }
    
    stop(){
    
            PIDS=`ps  --no-heading -C java -f --width 1000 | grep "$JAR_NAME" |awk '{print $2}'`
            if [ -z "$PIDS" ]; then
                echo "ERROR: The $SERVER_NAME does not started!"
            fi
    
            echo -e "Stopping the $SERVER_NAME ...\c"
            for PID in $PIDS ; do
                kill $PID > /dev/null 2>&1
            done
    
            COUNT=0
            while [ $COUNT -lt 1 ]; do
                echo -e ".\c"
                sleep 1
                COUNT=1
                for PID in $PIDS ; do
                    PID_EXIST=`ps --no-heading -p $PID`
                    if [ -n "$PID_EXIST" ]; then
                        COUNT=0
                        break
                    fi
                done
            done
            echo "stop OK!"
            echo "PID: $PIDS"
            rm .pid
    
    }
    
    case $1 in
      start)
            start;
        ;;
      stop)
            stop;
        ;;
      restart)
            echo "############ Application of '"$JAR_NAME"' restarting....############"
        stop;
        sleep 2
        start;
        ;;
      *)
        echo "Usage: startup.sh {start|stop|restart}"
        ;;
    esac
    exit 0
    
    

    目录结构

    image.png

    打包产物
    配置文件, 静态资源都在根下.

    image.png

    主程序执行jar包结构
    没有配置文件了.

    image.png

    坑点

    如何配置不当:

    1. 打包时配置文件能够隔离. 但IDE里本地运行时, 找不到配置文件, 因为配置文件被排除了.
    2. spring 的devtools 一定要设置成 scope provided, 让它不参与打包. 这样可以避免在设置当前目录加入了classpath, 而日志文件又写入当亲目录内时, 应用不停的重启. (这是因为检测到了classpath内内容变化就会热部署, 就会重启).
    3. 找不到 logback.xml 配置文件. 解决: M1: 加入当前目录到classpath; M2: 启动命令中指定文件.

    相关文章

      网友评论

          本文标题:java项目打包最佳实践

          本文链接:https://www.haomeiwen.com/subject/rwreektx.html