
简介这是一套面向Java开发与代码质量管理人员的SonarQube自定义规则工程包适合需要在持续集成中落地特定编码规范、扩展静态代码分析能力的团队与个人。资源围绕sonar-java展开提供在标准规则集之外按项目需求定制检测逻辑的完整工程结构可用于代码风格、潜在缺陷与安全问题的精细化扫描。压缩包共485个文件约747.36MB以xml配置、java源码、json数据、jar依赖与class字节码为主辅以html文档、md说明及sh构建脚本涵盖Maven的pom.xml、IntelliJ IDEA工程文件与Git版本元数据目录组织清晰便于二次开发与规则调试。目前已有592人学习下载。通过阅读源码与构建脚本读者可掌握自定义规则的实现方式、依赖组织与打包流程并借鉴其工程结构快速搭建自己的SonarJava规则集提升代码可维护性。1. 从 sonar-java-custom-rules.zip 说起为什么团队最终都要自己写规则第一次拿到sonar-java-custom-rules.zip这类压缩包时很多人会以为它是一份“插件成品”解压、丢进 SonarQube 插件目录、重启就能多出一堆检查项。实际拆开看它通常是一个自定义规则模板工程里面是 Java 写的规则实现、规则元数据、测试用例和构建脚本需要你自己编译成 jar再注册到 SonarQube 的 Java 分析器里。它解决的不是“装个插件”的问题而是“官方规则覆盖不到我们团队的编码约定”的问题——比如禁止在 Controller 里直接new线程池、要求所有对外接口必须带幂等键、禁止用System.currentTimeMillis()做业务时间戳。这类约束官方规则库不会替你写只能自己动手。适合谁适合已经跑通 SonarQube、被重复代码评审折磨过、想把这部分经验固化成自动检查的后端团队。下面按“先立住原理、再动手复现、最后讲坑”的顺序拆开讲。2. 自定义规则到底怎么被 SonarQube 执行从源码到 Issue 的链路2.1 规则不是正则而是挂在 AST 上的访问器很多人第一反应是“写个正则匹配不就行了”。在 SonarJava 里规则的核心是JavaFileScanner和JavaCheck分析器会先把.java文件解析成抽象语法树AST然后按节点类型回调你的访问器。你写的是“当遇到方法调用节点时判断它是不是目标方法”而不是“在文本里找字符串”。这个区别决定了三件事一是能拿到类型信息MethodInvocationTree的symbolType二是能精确定位到行列号三是不会被注释和字符串里的同名文本误伤。正则方案在// 禁止 new ThreadPoolExecutor这种注释上就会翻车AST 方案不会。2.2 一次完整分析的调用顺序理解调用顺序排错时才知道该在哪打日志。典型链路是SonarQube 扫描器把源码交给 SonarJava 分析器 → 分析器构建 AST 和语义模型symbol table→ 遍历节点时依次调用已注册规则的scanFile/visitNode→ 规则通过context.reportIssue上报问题 → 分析器汇总成 Issue 写回服务端。关键点是语义模型构建在规则执行之前所以你在规则里能安全地调用symbolType()、methodSymbol()这类方法但如果你在scanFile一开始就急着取类型而此时节点还没被访问到就会拿到空值。常见做法是把判断逻辑放在具体的visitMethodInvocation里而不是scanFile里。2.3 规则元数据决定了它能不能被激活规则实现只是“逻辑”能不能在质量配置里被勾选取决于元数据。SonarJava 自定义规则通常通过注解或rules.json描述规则 key、名称、严重级别、类型BUG / CODE_SMELL / VULNERABILITY、描述、标签。少写一个字段规则可能编译通过但激活时报错。下面这张表是我整理的最小元数据字段清单缺一个都可能在注册阶段出问题。字段作用常见取值ruleKey规则唯一标识团队前缀 语义名如TeamAvoidNewExecutorname展示名称禁止在业务代码中直接创建线程池severity默认严重级别BLOCKER / CRITICAL / MAJOR / MINORtype问题类型CODE_SMELL / BUG / VULNERABILITYdescription规则说明讲清为什么禁、怎么改tags分类标签如convention、performance3. 把 zip 跑起来本地编译、注册、验证的最小闭环3.1 解压后先看清工程结构解压sonar-java-custom-rules.zip后别急着改代码先确认目录职责。典型结构是src/main/java放规则实现src/main/resources放规则元数据和描述文件src/test/java放测试用例根目录是pom.xml或build.gradle。先跑一次原始构建确认模板本身能编译再动自己的规则。这一步能排除“环境问题”和“代码问题”混在一起的血泪经验。# 解压并进入工程目录 unzip sonar-java-custom-rules.zip -d sonar-java-custom-rules cd sonar-java-custom-rules # 先跑一次原始构建确认模板可用 mvn -q clean package -DskipTests # 产物通常在 target/ 下形如 sonar-java-custom-rules-*.jar ls target/*.jar上面命令的逻辑clean package保证从干净状态构建-DskipTests先跳过测试快速验证编译链路。参数说明如果团队用 Gradle把mvn换成./gradlew build即可产物路径在build/libs。注意别在第一次构建就加-o离线参数依赖没下全时会直接失败反而掩盖真实问题。3.2 写一条最小规则禁止直接 new 线程池下面这条规则检测new ThreadPoolExecutor(...)和Executors.newFixedThreadPool(...)这类调用属于团队里最常见的“统一线程池入口”约束。代码里保留了关键注释方便你替换成自己的目标方法。// 规则实现继承 BaseTreeVisitor 并实现 JavaFileScanner public class AvoidNewExecutorCheck extends BaseTreeVisitor implements JavaFileScanner { private JavaFileScannerContext context; Override public void scanFile(JavaFileScannerContext context) { // 保存上下文后续 reportIssue 需要它 this.context context; // 触发 AST 遍历会回调下面的 visitNewClass / visitMethodInvocation scan(context.getTree()); } Override public void visitNewClass(NewClassTree tree) { // 判断是否是 ThreadPoolExecutor 的直接实例化 if (tree.symbolType().is(java.util.concurrent.ThreadPoolExecutor)) { // 上报问题参数依次为节点、消息、行号 context.reportIssue(this, tree, 禁止直接 new ThreadPoolExecutor请使用统一线程池工厂); } super.visitNewClass(tree); } Override public void visitMethodInvocation(MethodInvocationTree tree) { // 拦截 Executors.newFixedThreadPool 等工厂方法 if (tree.symbolType().is(java.util.concurrent.ExecutorService) tree.methodSymbol().owner().type().is(java.util.concurrent.Executors)) { context.reportIssue(this, tree, 禁止使用 Executors 工厂方法请使用统一线程池工厂); } super.visitMethodInvocation(tree); } }逻辑说明scanFile是入口scan(context.getTree())才会真正遍历visitNewClass和visitMethodInvocation是回调点。参数说明tree.symbolType().is(...)做类型全限定名匹配比字符串匹配可靠context.reportIssue的第一个参数是规则实例第二个是节点第三个是提示文案。注意super.visit...必须调用否则子节点不会被继续遍历规则会漏报。3.3 注册规则并本地验证规则写完只是第一步还要在元数据里注册否则分析器根本不知道它的存在。下面用一段配置片段说明注册位置和字段对应关系。{ ruleKey: TeamAvoidNewExecutor, name: 禁止在业务代码中直接创建线程池, severity: MAJOR, type: CODE_SMELL, description: 直接创建线程池会导致资源不可控请使用统一线程池工厂。, tags: [convention] }逻辑说明ruleKey必须和规则类里声明的 key 一致否则激活时报“规则不存在”。参数说明severity是默认级别团队可以在质量配置里覆盖type决定它出现在哪个分类下。注册完成后重新mvn package把新 jar 替换到 SonarQube 的插件目录重启服务在“规则”页搜索TeamAvoidNewExecutor能搜到就说明注册成功。接着在一个测试项目上跑一次扫描确认能报出预期 Issue。4. 参数、阈值与测试让规则从“能跑”到“敢用”4.1 规则参数不是硬编码要能配置把阈值写死在代码里规则很快就会因为“太吵”被关掉。SonarJava 支持通过RuleProperty声明可配置参数比如允许的最大线程数、白名单包名。下面是一个参数声明的例子。// 声明可配置参数白名单包名前缀 RuleProperty( key allowedPackagePrefix, description 允许直接创建线程池的包名前缀多个用逗号分隔, defaultValue com.team.infra ) public String allowedPackagePrefix com.team.infra;逻辑说明RuleProperty让参数出现在 SonarQube 的规则配置界面团队可以按项目调整。参数说明defaultValue保证不配置时也有合理默认值多个值用逗号分隔后在规则里split(,)再逐个前缀匹配。注意参数名一旦发布就不要改改了会导致已有质量配置里的值失效这是很多人踩过的坑。4.2 用测试用例锁住行为边界规则最容易出问题的地方是“误报”和“漏报”。SonarJava 提供了JavaCheckVerifier做单元测试用注释标记期望报错的行。下面是一个测试片段。Test public void testAvoidNewExecutor() { // 期望在第 12 行报出问题 JavaCheckVerifier.verify(src/test/files/AvoidNewExecutor.java, new AvoidNewExecutorCheck()); }逻辑说明verify会解析测试文件并比对实际报错行与注释标记是否一致。参数说明测试文件里用// Noncompliant标记期望报错行用// Compliant标记不应报错行。注意测试文件本身也要能编译通过否则解析阶段就失败报错信息会指向语法而不是规则逻辑容易误导排查方向。4.3 严重级别和标签怎么定才不吵严重级别定得太高规则一上线就阻塞流水线团队会直接把它关掉定得太低又没人看。我的经验是会导致线上故障的定BLOCKER或CRITICAL编码约定类定MAJOR风格类定MINOR。标签用来做分类过滤比如convention、performance、security。下面这张表是我常用的映射参考。规则性质建议级别建议类型可能引发线上故障BLOCKER / CRITICALBUG团队强制约定MAJORCODE_SMELL性能隐患MAJORCODE_SMELL风格统一MINORCODE_SMELL5. 避坑与排查自定义规则上线前后最容易翻车的几件事5.1 规则编译通过但激活时报“规则不存在”现象jar 已放进插件目录重启后规则页搜不到或激活质量配置时报规则 key 不存在。原因通常是元数据里的ruleKey和规则类里声明的 key 不一致或者元数据文件没被打进 jar。解决解压产物 jar确认rules.json或对应资源文件在META-INF或资源目录下再核对两处 key 是否逐字符一致大小写敏感。5.2 规则误报注释和字符串里的同名文本现象代码里只是注释写了new ThreadPoolExecutor规则也报错。原因是用文本匹配而不是 AST 匹配或者虽然用了 AST 但判断条件写在了错误的节点上。解决确认逻辑挂在visitNewClass这类节点回调上而不是scanFile里做字符串查找用symbolType()做类型判断不要用tree.toString()做文本判断。5.3 规则漏报子节点里的目标调用现象目标调用写在if块或 lambda 里规则不报。原因是在回调里忘了调用super.visit...导致遍历提前终止。解决每个重写的visit方法末尾都补上super.visitXxx(tree)如果确实要跳过某类子树也要明确注释原因避免后人误删。5.4 规则上线后太吵被集体关闭现象规则上线第一天报出上千个 Issue团队直接把它从质量配置里移除。原因是没有做存量代码的过渡策略阈值定得太严。解决先以MINOR级别上线观察一段时间统计真实命中量对存量代码用白名单包名前缀过渡只对新代码生效确认误报率可接受后再提升级别。5.5 升级 SonarJava 版本后规则编译失败现象原本能编译的规则升级分析器版本后报 API 不存在或方法签名变化。原因SonarJava 的 API 在不同大版本间有调整Tree接口和symbolType相关方法可能变化。解决锁定pom.xml里sonar-plugin-api和java-frontend的版本升级前先看变更说明把规则实现和 API 版本绑定不要盲目追新。6. 进阶把规则做成团队可维护的资产规则写到一定数量后真正的挑战不是“怎么写”而是“怎么让团队愿意用、能持续维护”。我一般会做三件事。第一给规则分目录convention、performance、security各一个包规则类名带前缀搜索和定位都快。第二给每条规则配一个“反例 正例”的测试文件测试文件本身就是最好的文档新人看测试就知道规则在管什么。第三把规则 key 和团队编码规范文档做双向映射文档里每条约定后面标注对应的规则 key评审时直接引用避免“规范写了但没人查”。验证规则是否真的生效不要只看规则页能不能搜到要在一个真实的小项目上跑一次完整扫描确认 Issue 数量、位置、提示文案都符合预期。我习惯在本地用sonar-scanner指向一个测试项目扫描后看web端的 Issue 列表而不是只看构建日志里的“分析成功”。日志说成功不代表规则被触发。最后一个具体技巧规则描述里一定要写“怎么改”而不只是“禁止什么”。比如“禁止直接 new ThreadPoolExecutor请使用ThreadPoolFactory.get()”。只写禁止开发者看到 Issue 会烦躁写了替代方案规则就从“找茬”变成了“指路”。这是我把规则推给团队时最有用的一条经验。希望帮到你。本文还有配套的精品资源点击获取