前言

我一直都觉得spring boot很难用。。因为不知道里面做了些什么。。
一个项目,一个程序,一点就能够运行的话,那么如果我要拿来做二次开发我就会觉得—坑爹。。什么都不知道呢。

这次遇到的一个问题是spring boot自带的日志问题。额,不知道是bug,坑还是直接个人水平问题了。下面来解决一下。

问题重现

一个spring boot项目,具体来说

就是xxl-conf-admin的本地搭建以及编译。

编译过后,会发现出现这个问题:

springboot gradle版本选择 springboot gradle plugin_springboot

背景简介:

项目背景是多模块项目,如下图:

springboot gradle版本选择 springboot gradle plugin_排查jar冲突_02

其中,根项目的构建build.gradle如下:

plugins {
    id 'java'
}

group 'net.w2p'
version '1.0-SNAPSHOT'



/***所有项目共通***/
allprojects {
    sourceCompatibility = 1.8
    targetCompatibility = 1.8
    apply plugin: 'java'
    apply plugin: 'idea'
    apply plugin: 'groovy'
    ext{

        /***常见或主要第三方依赖版本号定义 begin***/
        globalSpringVersion = "5.1.4.RELEASE"
        globalSpringDataJpaVersion ="2.1.2.RELEASE"
        globalSpringBootVersion = '2.1.1.RELEASE'
        globalFastJsonVersion="1.2.54"
        globalMyBatisVersion="3.4.6"
        globalGoogleGuavaVersion="27.0.1-jre"
        globalDom4jVersion="1.6.1"
        globalJavaMailVersion="1.4.7"
        globalJsoupVersion="1.11.3" //--一个过滤html危险字符串api,用于web安全
        globalQuartzVersion="2.3.0"
        globalFlexmarkVersion="0.34.32" //--java对markdown语法的解释以及翻译api
        globalPostgresqlJdbcDriverVersion="42.2.5"
        globalQiniuSdkVersion="7.2.18"//--七牛上传下载客户端sdk
        globalApacheAntVersion="1.10.5"
        globalGoogleZXingVersion="3.3.3"
        globalFastdfsClientVersion="1.27"
        globalLog4jVersion="1.2.17"
        globalSlf4jVersion="1.7.25"
        globalRedisClientVersion="2.10.1"
        globalFreemarkerVersion="2.3.28"
        globalSpringBootStaterVersionOfMyBatis="1.3.2"
        globalMysqlJdbcDriverVersion="5.1.40"
        globalApacheCommonLang3Version="3.8.1"
        /***常见或主要第三方依赖版本号定义 end***/











        /****常见或者程序主要引用依赖定义 begin****/
        //--这个是spring boot要直接compile进去的框架。
        ref4SpringBoot=[
                /***spring boot 相关依赖***/
                "org.springframework.boot:spring-boot:$globalSpringBootVersion",
                "org.springframework.boot:spring-boot-starter:$globalSpringBootVersion",
                "org.springframework.boot:spring-boot-starter-web:$globalSpringBootVersion",                
                "org.springframework.boot:spring-boot-starter-freemarker:$globalSpringBootVersion",                
                "org.springframework.boot:spring-boot-devtools:$globalSpringBootVersion"                
        ]
        //--这个是spring boot要compileOnly的类库
        ref4SpringBootProvided=[
                "org.springframework.boot:spring-boot-dependencies:$globalSpringBootVersion",                
        ]
        //--这个是spring boot的测试框架,用testCompile导入
        ref4SpringBootTest=[
                "org.springframework.boot:spring-boot-starter-test:$globalSpringBootVersion"                
        ]
        //--spring框架api
        ref4SpringFramework=[
                "org.springframework:spring-web:$globalSpringVersion",
                "org.springframework:spring-webmvc:$globalSpringVersion",
                "org.springframework:spring-jdbc:$globalSpringVersion",
                "org.springframework:spring-context-support:$globalSpringVersion",                
                "org.springframework.data:spring-data-jpa:$globalSpringDataJpaVersion",
                "org.springframework:spring-test:$globalSpringVersion"                
        ]        
        
        //--jsp&servlet等javaweb容器api,通常都用 compileOnly引用的。
        ref4JspAndServletApi=[
                "javax.servlet:javax.servlet-api:3.1.0",
                "javax.servlet.jsp:jsp-api:2.2",
                "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1"                
        ]
        
        //--jstl等java web的tag标准api,引入的话要用compile
        ref4Jstl=[
                'taglibs:standard:1.1.2',
                'jstl:jstl:1.2'
        ]
        //--mybatis
        ref4MyBatis=[
                "org.mybatis:mybatis:$globalMyBatisVersion"
        ]
        //--这是apache common 类库引用的地址
        ref4ApacheCommons = [
                'commons-lang:commons-lang:2.6',
                'commons-logging:commons-logging:1.2',
                'commons-io:commons-io:2.5',
                'commons-fileupload:commons-fileupload:1.3.2',
                'commons-codec:commons-codec:1.10',
                'commons-beanutils:commons-beanutils:1.9.3',
                'commons-httpclient:commons-httpclient:3.1',
                'org.apache.httpcomponents:fluent-hc:4.3.6',
                'org.apache.httpcomponents:httpclient:4.5.3',
                'org.apache.httpcomponents:httpclient-cache:4.5.3',
                'org.apache.httpcomponents:httpcore:4.4.8',
                'org.apache.httpcomponents:httpmime:4.5.3',
                'org.apache.curator:curator-framework:4.0.1',
                'org.jfree:jfreechart:1.0.19',
                'org.apache.velocity:velocity:1.7',
                'org.apache.poi:poi:3.16'                
        ]
        //--redis client
        ref4RedisClient=["redis.clients:jedis:$globalRedisClientVersion"]

        ref4Freemarker=["org.freemarker:freemarker:$globalFreemarkerVersion"]

        //--这是阿里云短信引用的第三方类库
        ref4AliYunSms=[
                'com.aliyun:aliyun-java-sdk-core:3.2.8',
                'com.aliyun:aliyun-java-sdk-dysmsapi:1.1.0'                
        ]
        //--阿里云图片裁剪
        ref4AliSimpleImage=[
                'com.alibaba:simpleimage:1.2.3'
        ]
        //--阿里fast json引用地址
        ref4FastJson=["com.alibaba:fastjson:$globalFastJsonVersion"]
        //--json-lib引用地址
        ref4JsonLib=["net.sf.json-lib:json-lib:2.4:jdk15"]
        //--jdom1&jdom2以及相关api
        ref4Jdom=[
                'org.jdom:jdom2:2.0.6',
                'org.jdom:jdom:1.1.3',
                'joda-time:joda-time:2.9.7'
        ]

        //--google guava
        ref4GoogleGuava=["com.google.guava:guava:$globalGoogleGuavaVersion"]
        //--dom4j
        ref4Dom4j=["dom4j:dom4j:$globalDom4jVersion"]

        ref4JavaMail=["javax.mail:mail:$globalJavaMailVersion"]

        ref4Jsoup=["org.jsoup:jsoup:$globalJsoupVersion"]

        ref4Quartz=[
                "org.quartz-scheduler:quartz:$globalQuartzVersion",
                "org.quartz-scheduler:quartz-jobs:$globalQuartzVersion"
        ]


        ref4Flexmark=[
                "com.vladsch.flexmark:flexmark-all:$globalFlexmarkVersion"
        ]
        
        ref4PostgresqlJdbcDriver=[
                "org.postgresql:postgresql:$globalPostgresqlJdbcDriverVersion"
        ]

        ref4QiuniuSdkVersion=[
                "com.qiniu:qiniu-java-sdk:$globalQiniuSdkVersion"
        ]

        ref4ApacheAnt=["org.apache.ant:ant:$globalApacheAntVersion"]

        //--二维码
        ref4ZXing=[
                "com.google.zxing:core:$globalGoogleZXingVersion",
                "com.google.zxing:javase:$globalGoogleZXingVersion"
        ]
        ref4FastdfsClient=["cn.bestwu:fastdfs-client-java:$globalFastdfsClientVersion"]


        ref4Log4j=["log4j:log4j:$globalLog4jVersion"]

        ref4Slf4jToLog4j=["org.slf4j:slf4j-log4j12:$globalSlf4jVersion"]

        /****常见或者程序主要引用依赖定义 end****/
        
    }
    idea {
        module {
            inheritOutputDirs = true
        }
    }
    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
    tasks.withType(GroovyCompile) {
        groovyOptions.encoding = "MacRoman"
    }
    repositories {
        maven{
            //更换为阿里的仓库
            url  'http://maven.aliyun.com/nexus/content/groups/public'
        }

        //有些jar包在中央仓库是没有的,需要手动添加上去
//        flatDir {  dirs 'local_jars' }
//        mavenCentral()
    }
    dependencies {
        testCompile group: 'junit', name: 'junit', version: '4.12'
    }
}

xxl-conf-core的构建文件如下:

group 'com.xuxueli'
version '1.0-SNAPSHOT'


dependencies {
    //--日志
    compile ref4Log4j
    compile ref4Slf4jToLog4j
    compile "org.springframework:spring-beans:$globalSpringVersion"
}

xxl-conf-admin的构建文件如下:

buildscript {
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${globalSpringBootVersion}")
    }
}

plugins {
    id 'java'
    id 'war'
    id "application"
}

group 'com.xuxueli'
version '1.6.1'

dependencies {

    compile (project(":xxl-conf-core"))

    /***spring boot 相关依赖***/

    compile "org.springframework.boot:spring-boot:$globalSpringBootVersion"
    compile ("org.springframework.boot:spring-boot-starter:$globalSpringBootVersion")

    compile ("org.springframework.boot:spring-boot-starter-web:$globalSpringBootVersion")


    compile "org.springframework.boot:spring-boot-starter-freemarker:$globalSpringBootVersion"
    compile "org.springframework.boot:spring-boot-devtools:$globalSpringBootVersion"
//    compile 'ch.qos.logback:logback-classic:1.2.3'
    compileOnly "org.springframework.boot:spring-boot-dependencies:$globalSpringBootVersion"
    testCompile "org.springframework.boot:spring-boot-starter-test:$globalSpringBootVersion"

    /****spring boot end****/


    compile "org.mybatis.spring.boot:mybatis-spring-boot-starter:$globalSpringBootStaterVersionOfMyBatis"
    compile "mysql:mysql-connector-java:$globalMysqlJdbcDriverVersion"
    compile "org.apache.commons:commons-lang3:$globalApacheCommonLang3Version"

    compile ref4PostgresqlJdbcDriver
    compile "org.springframework:spring-jdbc:$globalSpringVersion"

}
//
///**
// *
// * 打包为可执行 jar
// *
// * ***/
//

apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

dependencyManagement {
    imports {
        mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
    }
}

//
mainClassName = 'com.xxl.conf.admin.XxlConfAdminApplication'
//
configurations {
    providedRuntime
}
/****
 *
 * 注意,引入spring boot以后,默认打包的是war,
 * 而且生成的执行脚本也会找到war包进行执行,就是说,
 * jar的生成已经没用了,只需要用war即可。
 *
 * *****/
jar {
//    baseName = 'conf-admin'
//    version =  '1.0.0'
//    destinationDir='lib'
//    destinationDir = file("$buildDir/lib")
    manifest {
        attributes 'Main-Class': 'com.xxl.conf.admin.XxlConfAdminApplication'
    }
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

///****
// * 注意,application打包的jar以及war都放在build的libs下面,但是bootscript下面的启动脚本是启动的build
// * 下面的lib下面。所以下面要添加一个文件复制操作
// * ***/

build.doLast {


    copy {

        from file("${rootDir}/build/libs/")
        into("${rootDir}/build/lib/")
        include '**/*.war'
        include '*.war'
    }

    println "build 结束,执行文件复制操作"
}

背景完毕,根据网上的查阅资料:
解决Gradle传递性依赖冲突

注意,这里归纳一下,
下面命令:
gradle dependencyInsight --dependency slf4j-api
是用来检查具体依赖结构的。

springboot项目maven报错 LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback

上文归纳一下,就是说,日志第三方库冲突了,两个解决方案:

方案1、不要slf4j-log4j12
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
方案2、不要log4j-slf4j-impl
<dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
        </dependency>

其中博主的一些观点个人是很有意思的:

springboot gradle版本选择 springboot gradle plugin_springboot_03

欸,我还是继续用成熟的spring 好了。

下面来解决问题。

问题解决

我这里解决去掉slf4j-log4j12这个依赖,首先,在xxl-conf-admin下面查找具体依赖的结构:

gradle dependencyInsight --dependency slf4j-log4j12

如下图:

springboot gradle版本选择 springboot gradle plugin_gradle_04

注意了,结果显示,只有project xxl-conf-core引用了。好了,我们要排除这个:

compile (project(":xxl-conf-core"))
            {
                exclude group:"org.slf4j",module:"slf4j-log4j12"
            }

springboot gradle版本选择 springboot gradle plugin_gradle_05

然后执行:

结果如下:

springboot gradle版本选择 springboot gradle plugin_日志冲突_06

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.1.RELEASE)

14:01:52.517 logback [restartedMain] INFO  c.x.c.admin.XxlConfAdminApplication - Starting XxlConfAdminApplication on tw-server with PID 16159 (started by too-white in /home/too-white/文档/svn/clover)
14:01:52.550 logback [restartedMain] INFO  c.x.c.admin.XxlConfAdminApplication - No active profile set, falling back to default profiles: default
14:01:52.583 logback [restartedMain] INFO  o.s.b.d.e.DevToolsPropertyDefaultsPostProcessor - Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
14:01:52.583 logback [restartedMain] INFO  o.s.b.d.e.DevToolsPropertyDefaultsPostProcessor - For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
14:01:54.362 logback [restartedMain] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port(s): 7788 (http)
14:01:54.383 logback [restartedMain] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-7788"]
14:01:54.391 logback [restartedMain] INFO  o.a.catalina.core.StandardService - Starting service [Tomcat]
14:01:54.392 logback [restartedMain] INFO  o.a.catalina.core.StandardEngine - Starting Servlet Engine: Apache Tomcat/9.0.13
14:01:54.398 logback [restartedMain] INFO  o.a.c.core.AprLifecycleListener - The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib]
14:01:54.463 logback [restartedMain] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
14:01:54.463 logback [restartedMain] INFO  o.s.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 1879 ms
14:01:55.051 logback [pool-2-thread-2] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
14:01:55.167 logback [restartedMain] INFO  o.s.s.c.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'
14:01:55.675 logback [pool-2-thread-2] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
14:01:55.882 logback [pool-2-thread-2] INFO  c.x.c.a.s.i.XxlConfNodeServiceImpl - >>>>>>>>>>> xxl-conf, sync totel conf data success, sync conf count = 3
14:01:56.444 logback [restartedMain] INFO  o.s.b.d.a.OptionalLiveReloadServer - LiveReload server is running on port 35729
14:01:56.490 logback [restartedMain] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-7788"]
14:01:56.495 logback [restartedMain] INFO  o.a.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read
14:01:56.509 logback [restartedMain] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 7788 (http) with context path ''
14:01:56.512 logback [restartedMain] INFO  c.x.c.admin.XxlConfAdminApplication - Started XxlConfAdminApplication in 4.884 seconds (JVM running for 5.906)

好了,终于执行成功了。

问题再现

2019-01-31日补充。
上面我们进行了jar的依赖排错,确定了compile某个类库时候就exclude–去掉某些依赖,然而,假如是有很多库都用到了这个jar或者全部都用到,我们应该怎么解决?逐个逐个compile加上exclude?
下面就是对此的解决。

问题描述

近日要整合log4j2这个日志插件,发现冲突很多,譬如,在根项目下面添加了:

springboot gradle版本选择 springboot gradle plugin_springboot_07


springboot gradle版本选择 springboot gradle plugin_xxl-conf-core_08

然后在allprojects 的依赖里面统一设置:

/****项目统一使用log4j2日志插件 begin***/
        //--log4j2相关库
        compile ref4Slf4jBindingLog4j2
        compile ref4Log4j2
        compile ref4Disruptor //log4j2要异步记录日志必须有这个。
        //--log4j2在web项目中要有这个。
        runtime "org.apache.logging.log4j:log4j-web:$globalLog4j2Version"
        /****项目统一使用log4j2日志插件 end***/

然后你会发现某些项目,譬如,xxl-conf-admin运行不了,提示:

springboot gradle版本选择 springboot gradle plugin_springboot_09

logback和log4j 2.11.1冲突了,然后按照之前的查找依赖,会发现:

gradle dependencyInsight --dependency log4j-slf4j-impl

springboot gradle版本选择 springboot gradle plugin_日志冲突_10


有两个地方用到log4j,其中一个是log4j-slfj-impl,另一个是。。。compileClasspath。。

就是说,编译出来就带log4j了。。没有引入其他jar都这样了!!!
好了,说说解决方案吧:
How to exclude a jar from gradle

springboot gradle版本选择 springboot gradle plugin_xxl-conf-core_11

看到这一段了没有?我们只需要添加:

springboot gradle版本选择 springboot gradle plugin_gradle_12

configurations {
    /***xxl-conf-admin中默认使用的是logback,
     * 全局引入log4j2之后冲突无法运行,没必要在这个项目引用log4j以及第二代log4j,故全部排除**/
    all*.exclude group:"org.slf4j",module:"slf4j-log4j12"
    all*.exclude group:"org.apache.logging.log4j",module:"log4j-slf4j-impl"
}

运行:

springboot gradle版本选择 springboot gradle plugin_排查jar冲突_13

好了,成功运行。。其实,可以将logback排除的。。