哇!绝了!原来这么简单!我的 Java 项目代码终于被 “拯救” 了!

154 阅读2分钟

你是否因为 Java 项目里的上千个文件一键格式化还得清理掉所有无效 import而头大

最不可能的是导入包的顺序调整规范化

还有后面其他同事提交的又搞出问题

我就去搜了搜,居然有maven插件可以搞定,maven插件这东西强的离谱,就像浏览器插件一样,花样百出

试了好几种方法都踩坑,要么插件不兼容报错,要么只能做一半,直到我遇到了这个神仙组合,直接原地起飞!

🚀 插件一引!一键搞定!

而且效果直接拉满:

  1. 代码格式秒对齐:标准 Java K&R 风格, 有模版可自定义配置
  2. 无效 import 一键清空:所有没用到的导入全被清理干净,代码瞬间清爽
  3. 零报错!零兼容问题:不管是 IDEA 还是命令行,一键执行不翻车

📝 终极配置!复制即用!

我把最终版配置整理好了,这一段复制到 pom.xml 里就行

xml

<build>
    <plugins>
        <!-- Spotless:核心神器!格式化+清理无效import + import排序 -->
        <plugin>
            <groupId>com.diffplug.spotless</groupId>
            <artifactId>spotless-maven-plugin</artifactId>
            <executions>
                <execution>
                    <phase>process-sources</phase>
                    <goals>
                        <goal>apply</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <java>
                    <!-- 使用 Eclipse 格式化器(支持标准 Java K&R 风格) -->
                    <eclipse>
                        <file>${maven.multiModuleProjectDirectory}/spotless.xml</file>
                    </eclipse>

                    <!-- 自动删除所有未使用的import -->
                    <removeUnusedImports />
                    <!-- 按Spring规范排序import -->
                    <importOrder>
                        <order>java</order>
                        <order>javax</order>
                        <order>*</order>
                        <order>org.springframework</order>
                    </importOrder>
                </java>
            </configuration>
        </plugin>
    </plugins>
</build>

spotless.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="13">
<profile kind="CodeFormatterProfile" name="XLinks Java Style" version="13">
<!-- 基础设置 -->
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="150"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="150"/>

<!-- 花括号位置 - K&R 风格(不换行) -->
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>

<!-- 注释格式化 - 禁用自动格式化 -->
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="false"/>

<!-- 空行设置 -->
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>

<!-- 空格设置 -->
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>

<!-- 换行设置 - 避免过度换行 -->
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_if_empty"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_if_empty"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_if_empty"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_if_empty"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_if_empty"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>

<!-- 对齐和换行策略 - 不强制换行 -->
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="80"/>

<!-- 不要在extends/implements后强制换行 -->
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="false"/>

<!-- 其他设置 -->
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>

<!-- 不要过度缩进 -->
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
</profile>
</profiles>

⚡ 一键执行命令!效率拉满!

重新编译项目,你的 Java 项目直接焕然一新:

mvn clean compile

补充

用 Spotless 统一 import 顺序时,有一个常见需求:把公司内部包(如 cn.iotab)单独排到所有第三方包之后,形成一个"内部包"分组。

然而,直觉上的写法根本不生效。本文记录这个问题的根因和正确解法。

问题:* 之后的规则被忽略

很多人会这样写:

<importOrder>
  <order>cn.iotab</order>       <!-- 期望排在最后,但实际不生效 -->
  <order>*</order>                         <!-- 匹配所有第三方包 -->
  <order>org.springframework</order>
  <order>javax</order>
  <order>java</order>
</importOrder>

根本原因: Spotless 的 importOrder 是按顺序匹配、先到先得的。* 会把所有未被前面规则匹配的包一次性"吃掉",* 之后的任何规则都不再有机会被执行。

解法:使用 .importorder 文件

Spotless 支持 Eclipse 格式的 .importorder 文件,这种格式以数字为键、包前缀为值,并且支持在通配符(空字符串)之后继续定义特定包。

第一步:创建 spotless.importorder 文件

在项目根目录(与顶层 pom.xml 同级)创建文件:

# spotless.importorder
0=java
1=javax
2=org.springframework
3=              # 空字符串 = 其他所有第三方包(相当于 *)
4=cn.iotab      # 数字更大 = 排在后面,内部包单独成组

数字表示分组优先级,数字越大排越后。同一数字下的包按字母序排列。 3= 的空值等价于通配符,匹配所有未被其他规则覆盖的包。

第二步:修改 pom.xml 配置

将 <importOrder> 里的 <order> 列表改为引用文件:

<!-- 代码格式化插件 - Spotless -->
            <plugin>
                <groupId>com.diffplug.spotless</groupId>
                <artifactId>spotless-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>process-sources</phase>
                        <goals>
                            <goal>apply</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <java>
                        <!-- 使用 Eclipse 格式化器(支持标准 Java K&R 风格) -->
                        <eclipse>
                            <file>${maven.multiModuleProjectDirectory}/spotless.xml</file>
                        </eclipse>

                        <!-- 自动删除未使用的 import -->
                        <removeUnusedImports />

                        <!-- import 排序 -->
                        <importOrder>
                            <file>${maven.multiModuleProjectDirectory}/spotless.importorder</file>
                        </importOrder>

                        <!-- 支持 @formatter:off 和 @formatter:on 注释来禁用特定代码块的格式化 -->
                        <toggleOffOn />
                    </java>
                </configuration>
            </plugin>

第三步:文件放在哪里?

如果是多模块 Maven 项目,推荐把文件放在根目录,所有子模块共享同一份配置:

my-project/
├── pom.xml                    # 父 pom
├── spotless.importorder       # 放这里
    spotless.xml               # 放这里
├── common/
│   └── pom.xml
└── api/
    └── pom.xml

${maven.multiModuleProjectDirectory}  是 Maven 3.3.1+ 内置变量,指向包含最顶层 .mvn/ 目录的根模块路径,不需要手动填写,Maven 运行时自动解析。

如果只是单模块项目,用 ${project.basedir}/spotless.importorder 并把文件放在该模块下即可。

最终效果

格式化后,import 分组顺序如下:

// 第 0 组
import java.util.List;
import java.util.Map;

// 第 1 组
import javax.validation.Valid;

// 第 2 组
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;

// 第 3 组(其他第三方包)
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;

// 第 4 组(内部包,排在最后)
import cn.iotab.*;

内部包成功排到最后,并与其他第三方包之间保留一个空行分隔。


总结

Spotless 的 <order> 列表是顺序匹配的,*(通配符)一旦命中就终止后续匹配,无法实现"通配符之后再指定特定包"的效果。改用 Eclipse 格式的 .importorder 文件,通过数字键控制分组顺序,就能轻松实现任意排列方式。

💡 最后

本来以为要折腾半天的事,结果这么简单就解决了。

代码瞬间变得干净整洁,再也不用手动一个个删 import、调格式了,省下来的时间摸鱼不香吗?

赶紧去试试,再也不用为不统一的代码格式发愁了.