你是否因为 Java 项目里的上千个文件一键格式化还得清理掉所有无效 import而头大
最不可能的是导入包的顺序调整规范化
还有后面其他同事提交的又搞出问题
我就去搜了搜,居然有maven插件可以搞定,maven插件这东西强的离谱,就像浏览器插件一样,花样百出
试了好几种方法都踩坑,要么插件不兼容报错,要么只能做一半,直到我遇到了这个神仙组合,直接原地起飞!
🚀 插件一引!一键搞定!
而且效果直接拉满:
- 代码格式秒对齐:标准 Java K&R 风格, 有模版可自定义配置
- 无效 import 一键清空:所有没用到的导入全被清理干净,代码瞬间清爽
- 零报错!零兼容问题:不管是 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、调格式了,省下来的时间摸鱼不香吗?
赶紧去试试,再也不用为不统一的代码格式发愁了.