You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写Java Checkstyle检查:验证类后缀与所属包的匹配性

编写Checkstyle规则验证类名后缀与包的匹配关系

核心思路

自定义Checkstyle检查类,继承AbstractCheck,针对类定义节点(CLASS_DEF)提取类名和所属包名,通过预定义的类名后缀-包名后缀映射验证是否匹配:

  • 提取类名的后缀部分,匹配对应的包名后缀规则
  • 若包名不符合对应规则,抛出违规提示

具体实现步骤

1. 自定义检查类代码

创建Java类,实现Checkstyle的检查逻辑:

import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
import java.util.HashMap;
import java.util.Map;

public class SuffixToPackageCheck extends AbstractCheck {

    // 配置类名后缀与包名后缀的映射,可通过配置文件修改
    private Map<String, String> suffixPackageMap = new HashMap<>();

    public void setSuffixPackageMap(Map<String, String> suffixPackageMap) {
        this.suffixPackageMap = suffixPackageMap;
    }

    @Override
    public int[] getDefaultTokens() {
        return new int[]{TokenTypes.CLASS_DEF};
    }

    @Override
    public int[] getAcceptableTokens() {
        return getDefaultTokens();
    }

    @Override
    public int[] getRequiredTokens() {
        return getDefaultTokens();
    }

    @Override
    public void visitToken(DetailAST ast) {
        // 获取类名
        DetailAST classNameAst = ast.findFirstToken(TokenTypes.IDENT);
        String className = classNameAst.getText();

        // 获取包名
        String packageName = getCurrentPackageName();
        if (CommonUtil.isBlank(packageName)) {
            return; // 无包名的类跳过检查
        }

        // 遍历映射,检查类名后缀是否匹配
        for (Map.Entry<String, String> entry : suffixPackageMap.entrySet()) {
            String classSuffix = entry.getKey();
            String requiredPackageSuffix = entry.getValue();

            if (className.endsWith(classSuffix)) {
                if (!packageName.endsWith(requiredPackageSuffix)) {
                    log(classNameAst.getLineNo(),
                        "类名以{}结尾的类必须位于以{}结尾的包中",
                        classSuffix, requiredPackageSuffix);
                }
                // 一个类匹配一个后缀规则即可,无需继续检查
                return;
            }
        }
    }

    private String getCurrentPackageName() {
        DetailAST packageAst = getRootAST().findFirstToken(TokenTypes.PACKAGE_DEF);
        if (packageAst == null) {
            return "";
        }
        DetailAST packageIdent = packageAst.findFirstToken(TokenTypes.DOT);
        if (packageIdent == null) {
            return packageAst.findFirstToken(TokenTypes.IDENT).getText();
        }
        return packageIdent.getText();
    }
}

2. 配置Checkstyle规则

在checkstyle.xml中注册自定义检查,并配置后缀与包的映射:

<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
        "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
    <module name="TreeWalker">
        <!-- 注册自定义检查类 -->
        <module name="com.yourpackage.SuffixToPackageCheck">
            <!-- 配置映射:类名后缀 -> 要求的包名后缀 -->
            <property name="suffixPackageMap">
                <map>
                    <entry key="Utils" value=".util"/>
                    <entry key="Service" value=".service"/>
                    <entry key="Repository" value=".repository"/>
                </map>
            </property>
        </module>
    </module>
</module>

3. 测试验证

  • 合法示例:DateTimeUtils类位于com.example.util包下,不会触发违规
  • 违规示例:DateTimeUtils类位于com.example.common包下,会抛出类似如下提示:

    类名以Utils结尾的类必须位于以.util结尾的包中

注意事项

  • 若需要支持正则表达式匹配后缀,可修改endsWith为正则匹配逻辑
  • 确保自定义检查类在Checkstyle的类路径中,否则配置文件会无法加载
  • 可通过配置文件灵活扩展更多后缀与包的映射规则

内容的提问来源于stack exchange,提问作者mdenci

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 13:45:16