如果你须要一个自动化的工具帮忙你或者你的团队发现代码中的缺点,在晋升代码品质同时缩小人工Code Review的老本,那这篇文章十分的适宜你。本文围绕SpotBugs与Gradle集成,将相干配置和应用进行了具体介绍,并提供了各种可能为你的我的项目定制化配置的伎俩。起源和出处都已在文中要害处以超链接给出,纵情享受吧。

SpotBugs是什么?

SpotBugs是一个开源的Java动态剖析工具,旨在帮忙开发人员检测代码中的潜在缺点和破绽。SpotBugs能够通过扫描Java字节码来发现潜在的问题,例如空指针援用、类型转换谬误、未应用的变量等等。它还能够检测代码中的潜在安全漏洞,例如SQL注入、XSS攻打等。SpotBugs提供了一个用户敌对的GUI和命令行接口,能够轻松地与各种构建工具和IDE集成,例如Ant、Maven、Gradle、Eclipse和IntelliJ IDEA。SpotBugs还反对插件和自定义规定,使得开发人员能够依据我的项目的特定需要和规范对其进行定制化配置。更多详细信息能够查看SpotBugs官网文档。

SpotBugs是FindBugs的一个分支,它在FindBugs的根底上进行了改良和降级,它应用了更先进的算法和技术来进步剖析的准确性和效率。SpotBugs还增加了对新的Java版本的反对,例如Java 8和Java 11。SpotBugs还提供了更好的用户界面和命令行界面,并反对更多的构建工具和IDE集成。

FindBugs也是一款十分风行的Java动态剖析工具,然而曾经进行更新。停更的起因仿佛是我的项目拥有者不愿持续在这个我的项目上花工夫,代码贡献者因为没有权限也无奈持续迭代和公布新的版本,在FindBugs GitHub首页的README文件中提供了两个文档我的项目状态 2016-November和我的项目状态 2017-September,外面讲述了代码贡献者过后的一些担心和无奈。

SpotBugs与Gradle集成

开始之前先简略介绍下本文中将会提到的几个依赖以及它们之间的关系,以便于了解后续内容:

  • com.github.spotbugs.snom:spotbugs-gradle-plugin是一个Gradle插件,它将SpotBugs集成到 Gradle构建中,生成SpotBugsTask并能够通过配置属性来扩大。
  • com.github.spotbugs:spotbugs这个依赖根本蕴含了所有SpotBugs检测器,这些检测器实现了Bug descriptions中提到的相干Bug检测项的逻辑。
  • com.h3xstream.findsecbugs:findsecbugs-plugincom.github.spotbugs:spotbugs的根底上减少了平安相干的查看器也就是Bug descriptions#Security中形容的相关检查项。
  • com.github.spotbugs:spotbugs-annotations是Spotbugs的一个扩大/辅助工具,开发者应用这外面注解能够让Spotbugs依照咱们的用意来查看代码。

spotbugs-gradle-plugin

spotbugs-gradle-plugin是一个Gradle插件,它将SpotBugs集成到Gradle构建中,生成SpotBugsTask并提供相干配置来扩大,运行SpotBugsTask就能够执行查看,生成报告等。

默认状况下,这个插件外面曾经蕴含了一个spotbugs,所以利用了这个插件后个别无需在另外增加com.github.spotbugs:spotbugs。从SpotBugs version mapping中能够晓得spotbugs-gradle-plugin不同版本中默认蕴含的spotbugs版本之间的关系,比方:com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13蕴含了com.github.spotbugs:spotbugs:4.7.3

SpotBugs Gradle插件要求Gradle版本为7.0或者更高,JDK版本为1.8或者更高。

这个插件会为每个sourceSets生成SpotBugsTask。例如我的项目中有两个sourceSets main和test,插件将生成两个SpotBugsTask(spotbugsMain和spotbugsTest)。

如果不想主动生成SpotBugsTask,能够应用SpotBugs Base插件从头开始配置,参考com.github.spotbugs-base。

生成的SpotBugsTask执行时须要编译后的.class文件作为输出,因而它们将在Java编译后运行。SpotBugs Gradle减少了check相干的工作依赖,所以简略的运行./gradlew check也会执行生成的SpotBugsTask。

参考com.github.spotbugs在build.gradle文件中减少以下内容来引入SpotBugs Gradle插件:

buildscript {  repositories {    maven {      url "https://plugins.gradle.org/m2/"    }  }  dependencies {    classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13"  }}apply plugin: "com.github.spotbugs"

插件外面提供了很多可选的属性来配置或者扩大插件的行为。

上面展现了在build.gradle文件减少spotbugs{}相干属性的例子,详细信息能够参考SpotBugsExtension,这外面有每个属性作用、可指定的值和其余相干信息。spotbugs{}中指定的大部分属性将作为生成的SpotBugsTask配置的默认值,意味着能够通过spotbugs{}给所有SpotBugsTask配置属性。

spotbugs {    ignoreFailures = false    showStackTraces = true    showProgress = false    reportLevel = 'default'    effort = 'default'    visitors = [ 'FindSqlInjection', 'SwitchFallthrough' ]    omitVisitors = [ 'FindNonShortCircuit' ]    reportsDir = file("$buildDir/reports/spotbugs")    includeFilter = file('spotbugs-include.xml')    excludeFilter = file('spotbugs-exclude.xml')    onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']    projectName = name    release = version    extraArgs = [ '-nested:false' ]    jvmArgs = [ '-Duser.language=ja' ]    maxHeapSize = '512m'}

除了上述形式外,还可间接配置SpotBugsTask,以设置某个工作特定的属性,比方上面为名为spotbugsMain的工作独自进行了设置,跟spotbugs{}同名的属性将被笼罩,详细信息参考SpotBugsTask。

spotbugsMain {    sourceDirs = sourceSets.main.allSource.srcDirs    classDirs = sourceSets.main.output    auxClassPaths = sourceSets.main.compileClasspath      reports {        html {            required = true            outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html")            stylesheet = 'fancy-hist.xsl'        }    }    ignoreFailures = false    showStackTraces = true    showProgress = false    reportLevel = 'default'    effort = 'default'    visitors = [ 'FindSqlInjection', 'SwitchFallthrough' ]    omitVisitors = [ 'FindNonShortCircuit' ]    reportsDir = file("$buildDir/reports/spotbugs")    includeFilter = file('spotbugs-include.xml')    excludeFilter = file('spotbugs-exclude.xml')    baselineFile = file('spotbugs-baseline.xml')    onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']    projectName = name    release = version    extraArgs = [ '-nested:false' ]    jvmArgs = [ '-Duser.language=ja' ]    maxHeapSize = '512m'}

前面将会有一段形容比拟罕用或者重要的属性。

com.github.spotbugs:spotbugs

因为spotbugs-gradle-plugin中曾经蕴含了spotbugs,所以个别状况下不须要再独自引入这个依赖。不过可能因为默认带的版本有Bug、须要跟IDEA的SpotBugs插件应用雷同的版本,又或者新版本的检测器新加了实用的检测项等起因咱们会须要独自指定版本,上面提供了两种形式实现。

在配置spotbugs-gradle-plugin的时候通过toolVersion指定spotbugs版本。

spotbugs {  toolVersion = '4.7.3'}

dependencies增加依赖并指定依赖版本。

dependencies {    spotbugs 'com.github.spotbugs:spotbugs:4.7.3'}

findsecbugs-plugin

findsecbugs-plugin是Spotbugs的安全漏洞检测插件。它在spotbugs的根底上减少了本人的规定集,专一于检测平安相干的问题,例如明码透露、SQL 注入、XSS 等,也就是Bug descriptions#Security中形容的相关检查项。

dependencies {    spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0'}

spotbugs-annotations

spotbugs-annotations是Spotbugs的一个扩大/辅助工具,外面提供了很多注解,咱们能够在编码时将它们加在被检测代码的相应地位(如属性、办法、形参、本地变量),让Spotbugs依照咱们的用意来查看代码,以便SpotBugs能够更失当地收回正告。在Annotations中列举了所有注解,须要留神外面有很多曾经标记为Deprecated表明曾经弃用了。

比方上面这段代码,只是输入value到终端,即便test()传入null也不会导致空指针。Spotbugs原本不会收回正告,然而因为咱们在办法上加了注解edu.umd.cs.findbugs.annotations.NonNull,Spotbugs会依照咱们的用意进行入参不容许为null的校验,从而收回正告。

import edu.umd.cs.findbugs.annotations.NonNull;public class SpotbugsAnnotationsSample {    public static void main(String[] args) {        test(null);    }        public static void test(@NonNull Integer value) {        // 输入到终端        System.out.println(value);    }}

上面是引入这个依赖的例子,参考Refer the version in the build script,这外面还提到从spotbugs v4版本开始,spotbugs.toolVersionString 变为 Provider<String>,所以请应用 get() 或其余形式援用理论版本。

dependencies {    compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"}

SpotBugs Gradle扩大属性

includeFilter和excludeFilter

过滤器(Filter file)文件能够定义匹配检测项、被查看的类和被查看办法的一套匹配规定,让咱们能够为我的项目做定制化。文件配置好后,应用SpotBugsTask提供的属性指定过滤器文件的门路,实现蕴含(includeFilter)或者排除(excludeFilter)匹配的内容。

文件是xml格局的,每个匹配规定独自写在<Match></Match>中,应用Types of Match clauses形容的各种标签组合成匹配规定的内容,在Examples中给出了很多例子作为参考。

咱们重点关注下<Bug>这个标签,它能够通category、code、pattern来指定Bug descriptions中列出来的查看项,多个参数用逗号分隔,也能够应用正则表达式,以~字符结尾。

每个查看项有惟一的pattern,比方下图中的XSS_REQUEST_PARAMETER_TO_SEND_ERRORXSS_REQUEST_PARAMETER_TO_SERVLET_WRITER;多个查看项可能属于同一个code,比方下图中的XSS;多个不同的code可能属于同一个category。这三者之间是一个清晰的层级关系,这样就能够按不同的粒度来配置。

上面给出了一个应用<Bug>简略的例子和形容,更多例子请参考Examples。

<!--匹配指定的这两个查看项--><Match>  <Bug pattern="XSS_REQUEST_PARAMETER_TO_SEND_ERROR,XSS_REQUEST_PARAMETER_TO_SERVLET_WRITER" /></Match><!--匹配所有code为XSS的查看项--><Match>  <Bug code="XSS" /></Match><!--匹配SECURITY目录下所有查看项--><Match>  <Bug category="SECURITY" /></Match>

visitors和omitVisitors

这两个属性能够别离指定应用(visitors)和禁用(omitVisitors)查看器,一个查看器会蕴含一个或者多个Bug descriptions中提到的查看项,所以这两个属性配置的目标和includeFilterexcludeFilter是一样的,让咱们能够为我的项目做定制化,只是从查看器的维度进行配置。

在Detectors中将Spotbugs所有的查看器都列出来了,在不配置的状况下,Standard detectors中列出来的默认应用,Disabled detectors中列出来的默认禁用。

以查看器SynchronizationOnSharedBuiltinConstant为例,从下图咱们能够看到,查看器名称上面有段简短的形容,再往下将查看器蕴含的查看项都列出来了,能够看到上文提到的patterncode,点击能够跳转到Filter file文档相应的地位。咱们配置visitorsomitVisitors的时候填查看器的名字SynchronizationOnSharedBuiltinConstant即可。

effort

effort是配置代码检测预期的级别,级别由低到高别离为min、less、more、max,级别越高计算成本越高,破费的工夫也就越长,在这个文档Effort外面有表格清晰的列出了这几个级别别离蕴含了哪些查看内容。effort的默认值是default,等同于more

jvmArgs

jvmArgs用于设置JVM参数,因为SpotBugs是Java写的,天然要在JVM上运行。咱们在例子外面看到了-Duser.language=ja,这个意思是设置语言为日语,也就是代码剖析的后果展现的语音(输入到终端或者报告中)。总共有英语(en 默认),日语(ja),法语(fr)三种,在GitHub中能够看到相干展现文本的配置文件。

maxHeapSize

maxHeapSize是设置JVM最大堆内存大小,在spotbugsextension#maxHeapSizea中说了默认值为空,因而会应用Gradle的默认配置,所以个别不必管。

onlyAnalyze

onlyAnalyze指定哪些代码要被SpotBugs剖析,在大型项目外面用这个属性防止没必要的剖析,可能会大大减少运行剖析所需的工夫。能够指定类名,或者包名,指定包名的时候用.*.-的作用一样,意思是剖析这个包下以及子包中的文件。

onlyAnalyze = ['com.foobar.MyClass', 'com.foobar.mypkg.*']

reportLevel

SpotBugs按重大水平高到低将Bug分为了三个等级P1、P2和P3,reportLevel属性指明达到哪个等级的Bug才须要展现在报告外面,它可配置值对应有HIGH、MEDIUM、LOWDEFAULT,它们定义在Confidence.groovy中。默认值是DEFAULT等同于MEDIUM,意思是要达到P2级别,那就意味着低级别的P3 Bug将被疏忽,

reports

reports配置报告的类型,有html、xml、sarif和text四种,配置在SpotBugsTask中(比方spotbugsMain {}),再往里一层可配置的属性不是很多,参考SpotBugsReport。上面是html类型报告的配置示例:

spotbugsMain {    reports {        html {            required = true            outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html")            stylesheet = 'fancy-hist.xsl'        }    }}

最佳实际

配置

在根目录gradle.properties中配置版本。

spotbugsGradlePluginVersion=5.0.13findsecbugsPluginVersion=1.12.0

在根目录build.gradle中减少以下配置:

buildscript {    repositories {        mavenLocal()        maven {            url "https://plugins.gradle.org/m2/"        }        mavenCentral()    }    dependencies {        classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:${spotbugsGradlePlugin}"    }}apply plugin: "com.github.spotbugs"repositories {    mavenLocal()    maven {        url "https://plugins.gradle.org/m2/"    }    mavenCentral()}dependencies {    compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"    spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${findsecbugsPluginVersion}"}spotbugs {    ignoreFailures = false    showStackTraces = false    showProgress = false    excludeFilter = file("${project.rootDir}/code-analysis/spotbugs/exclude-filter.xml")    extraArgs = [ '-nested:false' ]}spotbugsMain {    reports {        html {            required = true            stylesheet = 'fancy-hist.xsl'        }    }}

如果是多模块我的项目按这种形式配置:

buildscript {    repositories {        mavenLocal()        maven {            url "https://plugins.gradle.org/m2/"        }        mavenCentral()    }    dependencies {        classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:${spotbugsGradlePlugin}"    }}allprojects {        apply plugin: "com.github.spotbugs"            repositories {        mavenLocal()        maven {            url "https://plugins.gradle.org/m2/"        }        mavenCentral()    }    dependencies {        compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}"        spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${findsecbugsPluginVersion}"    }    spotbugs {        ignoreFailures = false        showStackTraces = false        showProgress = false        excludeFilter = file("${project.rootDir}/code-analysis/spotbugs/exclude-filter.xml")        extraArgs = ['-nested:false']    }    spotbugsMain {        reports {            html {                required = true                stylesheet = 'fancy-hist.xsl'            }        }    }}

在我的项目根目录下创立排除查看项的文件/code-analysis/spotbugs/exclude-filter.xml,前期再依据须要配置。

<?xml version="1.0" encoding="UTF-8"?><FindBugsFilter        xmlns="https://github.com/spotbugs/filter/3.0.0"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd"></FindBugsFilter>

应用

在Gradle窗口中双击运行spotbugsMain工作即可,检测实现会在Run窗口中打印出报告地址。

也能够在Terminal窗口中执行./gradlew spotbugsMain,可能须要先执行chmod +x gradlew给gradlew文件执行权限。

如果执行过程中产生相似上面这种异样能够不必管,只是有局部相干的剖析会无奈执行,并不会中断整个过程,其余不相干的局部都会失常查看实现。在GitHub上issues#527很多人反映了这个问题,然而临时没有完满的解决。

The following classes needed for analysis were missing:
apply
test
accept

解读报告

报告概要(Summary)按包和BUG等级统计了数量。

按目录浏览(Browse by Categories)将发现的缺点代码按查看项中的category、code、pattern分层展现。

按包名浏览(Browse by Packages)就是换了个角度,依照包名 > 类名 > 查看项pattern分层展现。

最初一个窗口Info展现剖析的代码文件(Analyzed Files)、剖析的源文件夹(Source Files)应用的依赖(Used Libraries)、应用到的SpotBugs插件(Plugins)和剖析中产生的异样(Analysis Errors)。

过滤配置

通过includeFilterexcludeFilter指定过滤器(Filter file)文件是最灵便的,根本什么维度都能够管制。然而倡议这外面只配置要蕴含或者排除哪些查看项,应用category、code、pattern配置不同的粒度。

从整体放大被查看的代码范畴应用onlyAnalyze,如果有必要的话。如果要疏忽不查看具体的类或者办法能够应用注解@SuppressFBWarnings来标记,它来自spotbugs-annotations

其余技巧

SpotBugs Links外面列出了跟SpotBugs集成或者类似的工具,有须要能够理解下。

参考

SpotBugs-官网

SpotBugs-官网文档

SpotBugs-SpotBugsTask

SpotBugs-SpotBugsExtension

Gradle-spotbugs

GitHub-SpotBugs

GitHub-FindBugs

GitHub-spotbugs-gradle-plugin

FindBugs-官网