问题背景:
服务依赖的.jar包文件会和代码打包在一起,会导致最终打包好的文件特别大,部署的时候每次都需要上传大文件很麻烦,为了解决这个问题,需要将打包文件里面的lib包文件和classes文件分离开。
异常方案:
在springboot 项目中,为了方便jar包替换,把 jar 包中 BOOT-INF\lib 下面的 .jar 文件拷贝出来放在外部文件中
使用 java -jar 的方式启动jar 包时,出现了如下异常:
F:\TbpsServer>C:/Java/jdk1.8.0_251/bin/java -Xms256m -Xmx512m -XX:PermSize=128M -XX:MaxPermSize=256M -XX:NewRatio=3 -Dhostname=tbps01 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9110 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.ext.dirs=C:/Java/jdk1.8.0_251/jre/lib/ext;./lib -jar tbps.jar
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=128M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0
Logging system failed to initialize using configuration from 'null'
java.lang.IllegalStateException: Logback configuration error detected:
ERROR in ch.qos.logback.core.pattern.parser.Compiler@32c4e8b2 - Failed to instantiate converter class [com.cfcc.tbps.util.LogDesensitizationUtil] for keyword [msg] ch.qos.logback.core.util.DynamicClassLoadingException: Failed to instantiate type com.cfcc.tbps.util.LogDesensitizationUtil
注意: 抛出一个logback 异常,项目中为了做日志脱敏, 自定义一个 logback 的 MessageConverter
自定义MessageConverter类:
package com.cfcc.tbps.util;
import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
public class LogDesensitizationUtil extends MessageConverter {
@Override
public String convert(ILoggingEvent event) {
String oriLog = event.getFormattedMessage();
return filterSensitive(oriLog);
}
private String filterSensitive(String oriLog){
String sensString = "";
if(oriLog.contains("<PayAcct>")){
String payAcct = oriLog.substring(oriLog.indexOf("<PayAcct>")+9,oriLog.indexOf("</PayAcct>"));
for (int i=0;i<payAcct.length();i++){
sensString += "*";
}
oriLog = oriLog.replace(payAcct,sensString);
}
if(oriLog.contains("<TaxPayName>")){
sensString = "";
String payAcct = oriLog.substring(oriLog.indexOf("<TaxPayName>")+12,oriLog.indexOf("</TaxPayName>"));
for (int i=0;i<payAcct.length();i++){
sensString += "*";
}
oriLog = oriLog.replace(payAcct,sensString);
}
if(oriLog.contains("<HandOrgName>")){
sensString = "";
String payAcct = oriLog.substring(oriLog.indexOf("<HandOrgName>")+13,oriLog.indexOf("</HandOrgName>"));
for (int i=0;i<payAcct.length();i++){
sensString += "*";
}
oriLog = oriLog.replace(payAcct,sensString);
}
return oriLog;
}
}
spring logback 配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<!-- jar包日志设置 -->
<property name="log.path" value="logs" />
<!-- war包日志路径设置
<property name="log.path" value="../logs" />-->
<!-- 日志输出格式 %d表示日期时间,%thread表示线程名,¥-5level表示级别从左显示5个字符宽度
%logger{20}表示logger名字最长50个字符,否则按照句点分割。%msg:日志信息 %n:换行符-->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!--日志脱敏配置, conversionRule标签不能放到appender标签下面,可以挨着configuration或者在property标签下面
conversionWord对应脱敏的日志内容,即%msg%中脱敏的内容,converterClass属性指向自定义脱敏类-->
<conversionRule conversionWord="msg" converterClass="com.cfcc.tbps.util.LogDesensitizationUtil" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- <file>${log.path}/TbpsServer.log</file>-->
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
%i:当文件大小超过maxFileSize时,按照i进行文件滚动-->
<fileNamePattern>${log.path}/%d{yyyyMMdd}/TbpsServer.log.%i</fileNamePattern>
<!-- 当日志文件超过maxFileSize指定的大小时,格局上面提到的%i进行日志文件滚动,注意此处配置SizeBasedTriggeringPolicy
是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 级别过滤 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:接收,info中打印所有日志信息 -->
<onMismatch>ACCEPT</onMismatch>
</filter>
</appender>
<!--配置logger,指向异步appender-->
<logger name="com.cybermax.manager.log.printer" additivity="false" level="info">
<appender-ref ref="ACTION_LOG_APPENDER_ASYNC"/>
</logger>
<!-- 用户访问日志输出 -->
<!--<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!– 按天回滚 daily –>
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
<!– 日志最大的历史 60天 –>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>-->
<!-- 系统模块日志级别控制 日志级别:TRACE,DEBUG,INFO,WARN,ERROR-->
<logger name="com.cfcc.tbps" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>
启动命令
title TBPS
cd ..
rem 设置java环境变量
set JAVA_HOME=C:/Java/jdk1.8.0_251
set JAVA_JRE=%JAVA_HOME%/jre/lib/ext
rem 设置java虚拟机参数信息
set JAVA_OPPTIONS=-Xms256m -Xmx512m -XX:PermSize=128M -XX:MaxPermSize=256M -XX:NewRatio=3 -Dhostname=tbps01
rem 开启jconsole系统监测
set JCONSOLE=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9110 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
rem 加载外部jar包
set JAVA_EXT=-Djava.ext.dirs=%JAVA_JRE%;./lib
%JAVA_HOME%/bin/java %JAVA_OPPTIONS% %JCONSOLE% %JAVA_EXT% -jar tbps.jar
springboot 类加载过程
Springboot 的类加载过程是AppClassLoader加载org目录下的所有类文件,再由JarLauncher创建LauncherClassLoader(父类加载器是APPClassLoader)作为默认的类加载器去加载BOOT/classes/ 和BOOT-INF/lib/中的类和第三方库,并运行start-class中的main方法启动springboot应用。
问题分析:
因为通过 -Djava.ext.dirs 方式指定引导类加载器的路径,这时第三方包(包括Logback )会被引导类加载器加载,BOOT-INF\class 下的类(项目类)会被 LauncherClassLoader类加载器加载。这时,在Spring 解析的logback-spring.xml 文件,创建LogDesensitizationUtil对象时,会导致父类加载器加载的类调用子类加载器加载的类,因为父类加载器加载的类不能调用子类加载器加载的类,会导致对象初始化异常。
解决办法:
将logback 转换器的自定义实现类打成jar包,放到lib目录下,和第三方包一起被Extension ClassLoader 加载,问题解决。
上述方式很繁琐,通过查阅资料,可通过maven的方式实现项目代码与第三方jar包分离
参考资料【Maven】Maven打jar包分离lib包_maven打包lib分离_你怎么不笑了的博客-CSDN博客
Maven打包分离lib包
步骤一: 先修改pom.xml文件,打包的时候将lib和classes分离
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- lib依赖包输出目录,打包的时候不打进jar包里 -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<!-- 压缩jar包,打出来的jar中没有了lib文件夹 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
</plugin>
步骤二:部署服务
将lib包脱离后的jar包是不可以直接通过java -jar xxx.jar的方式启动的,因为该jar包已经没有依赖包,没法去执行代码里面依赖的各种包的环境,
需要在启动的时候将lib包的路径也一并配置上。
nohup java -Dloader.path="lib/" -jar luan-account-web.jar &
注意:-Dloader.path 配置的就是打包后的整个lib文件夹上传到服务器的目录下所指的路径,通常lib不会经常改动,所以在第一次部署的时候,我们将lib包部署到服务器之后就可以不用动了,后期如果有新的依赖包,只需要将新的依赖包再单独上传到服务器的lib包里面就好了,然后改动业务的代码后,每次只需要将最新打包的服务jar包发布部署就好。