这份附录怎么用
正文按章节逐步添加配置,读的时候容易只看到「这一节新增的那一段」。这份附录把 libraryd 最终的 Maven 配置集中放在一起,作为对照和抄写模板。
两点说明:
- 片段省略了
<modelVersion>之外的 XML 命名空间声明;实际文件需要完整的<project xmlns="http://maven.apache.org/POM/4.0.0" ...>。 - 版本号与正文一致:JDK 25、Maven 3.9.12。升级依赖时先改父 POM 的属性,不要在子模块里覆盖。
父 POM
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.plume</groupId>
<artifactId>libraryd-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>libraryd-domain</module>
<module>libraryd-persistence</module>
<module>libraryd-web</module>
<module>libraryd-app</module>
<module>libraryd-integration-tests</module>
</modules>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.outputTimestamp>2026-10-16T00:00:00Z</project.build.outputTimestamp>
<javalin.version>7.2.4</javalin.version>
<jackson.version>2.22.4</jackson.version>
<postgresql.version>42.7.14</postgresql.version>
<hikari.version>7.1.0</hikari.version>
<flyway.version>13.11.1</flyway.version>
<slf4j.version>2.0.20</slf4j.version>
<logback.version>1.6.6</logback.version>
<logstash-encoder.version>9.0</logstash-encoder.version>
<junit.version>5.14.4</junit.version>
<testcontainers.version>1.21.4</testcontainers.version>
<hibernate-validator.version>9.1.5.Final</hibernate-validator.version>
<jakarta-validation.version>3.1.1</jakarta-validation.version>
<expressly.version>6.0.0</expressly.version>
<micrometer.version>1.17.2</micrometer.version>
<otel.version>1.67.0</otel.version>
<caffeine.version>3.3.1</caffeine.version>
<jedis.version>8.0.2</jedis.version>
<jacoco.version>0.8.16</jacoco.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin</artifactId>
<version>${javalin.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikari.version}</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
<version>${flyway.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${logstash-encoder.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>${jakarta-validation.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>${expressly.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>${micrometer.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>${otel.version}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-logging</artifactId>
<version>${otel.version}</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<!-- 内部模块 -->
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-domain</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-persistence</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-app</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.16.0</version>
<configuration>
<release>${maven.compiler.release}</release>
<parameters>true</parameters>
<showWarnings>true</showWarnings>
<compilerArgs>
<arg>-Xlint:all,-processing</arg>
<arg>-Werror</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.3</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.26.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.10.5.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.5.2</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-toolchain</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.9,4.0)</version>
</requireMavenVersion>
<requireJavaVersion>
<version>[25,26)</version>
</requireJavaVersion>
<dependencyConvergence/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
几个容易忽略的点:
pluginManagement只声明版本和默认配置,不会自动执行插件。要执行的插件(如 Enforcer)必须再写进<build><plugins>。dependencyConvergence对传递依赖很严格,遇到冲突会直接失败。它的价值是逼你显式解决版本分歧,而不是让 Maven 悄悄选一个。-Xlint:all,-processing里的-processing关闭注解处理器告警,避免与 Hibernate Validator 等库的处理器冲突。第 2.1 节会解释哪些告警值得保留。
子模块 POM
domain
<parent>
<groupId>com.plume</groupId>
<artifactId>libraryd-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>libraryd-domain</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
domain 不依赖 JDBC、Javalin、Jackson。这条约束靠 code review 维持,不靠 Maven 强制;如果 domain 需要某个第三方类型,通常说明边界画错了。
persistence
<parent>
<groupId>com.plume</groupId>
<artifactId>libraryd-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>libraryd-persistence</artifactId>
<dependencies>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-domain</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
PostgreSQL 驱动用 runtime 作用域:编译期只需要 java.sql,运行时才需要驱动实现。这样能避免代码里误用 org.postgresql.* 的具体类。
web
<parent>
<groupId>com.plume</groupId>
<artifactId>libraryd-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>libraryd-web</artifactId>
<dependencies>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-domain</artifactId>
</dependency>
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
app
<parent>
<groupId>com.plume</groupId>
<artifactId>libraryd-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>libraryd-app</artifactId>
<dependencies>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-domain</artifactId>
</dependency>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-persistence</artifactId>
</dependency>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-web</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-logging</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.plume.library.AppMain</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
integration-tests
<parent>
<groupId>com.plume</groupId>
<artifactId>libraryd-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>libraryd-integration-tests</artifactId>
<dependencies>
<dependency>
<groupId>com.plume</groupId>
<artifactId>libraryd-app</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
集成测试模块本身没有主代码,测试类放在 src/test/java 下,由 Failsafe 执行。
质量插件
JaCoCo
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.60</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
覆盖率阈值不要定得太高。60% 的行覆盖率对业务模块是合理起点,domain 可以要求更高,web 的路由和配置类通常较低。按模块配置比全局一个数字更有意义。
Checkstyle
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<id>checkstyle</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<configLocation>.config/checkstyle.xml</configLocation>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<failOnViolation>true</failOnViolation>
<violationSeverity>warning</violationSeverity>
</configuration>
</execution>
</executions>
</plugin>
Checkstyle 的规则集要按团队习惯裁剪。默认 Sun 规则会要求 Javadoc 和 80 列换行,对现代项目过严;第 2.2 节给了一份以命名、导入、空块、魔法数字为主的精简规则。
SpotBugs
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<executions>
<execution>
<id>spotbugs</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<excludeFilterFile>.config/spotbugs-exclude.xml</excludeFilterFile>
<failOnError>true</failOnError>
</configuration>
</execution>
</executions>
</plugin>
threshold=Low 会报告大量低优先级问题,第一次接入时容易淹没。建议先设为 Medium 建立基线,再逐条关闭或加排除;排除文件必须写原因,否则半年后没人知道为什么忽略。
镜像插件
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<configuration>
<from>
<image>eclipse-temurin:25-jre-alpine</image>
</from>
<to>
<image>registry.example.com/libraryd</image>
<tags>
<tag>${project.version}</tag>
<tag>latest</tag>
</tags>
</to>
<container>
<mainClass>com.plume.library.AppMain</mainClass>
<ports>
<port>8080</port>
</ports>
<environment>
<LIBRARYD_HTTP_PORT>8080</LIBRARYD_HTTP_PORT>
</environment>
<jvmFlags>
<jvmFlag>-XX:MaxRAMPercentage=75</jvmFlag>
</jvmFlags>
</container>
</configuration>
</plugin>
Jib 默认把依赖、资源、应用类分成三层,改代码时只推送最后一层。MaxRAMPercentage 让 JVM 按容器内存上限计算堆大小,比固定 -Xmx 更适合容器环境。
Profile
quality
<profile>
<id>quality</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
本地开发默认只跑编译和测试,CI 用 -Pquality 启用静态分析与覆盖率门禁。这样日常反馈快,提交前再跑完整检查。
integration
<profile>
<id>integration</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
Failsafe 默认匹配 *IT.java。单元测试用 Surefire 的 *Test.java,集成测试用 *IT.java,两者分开执行。
常用诊断命令
# 查看最终生效的 POM(含父级继承与 profile)
mvn -q help:effective-pom -Doutput=/tmp/effective-pom.xml
# 查某个依赖从哪来
mvn -q dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind
# 查冲突
mvn -q dependency:tree -Dverbose -Dincludes=org.slf4j
# 只构建某个模块及其依赖
mvn -q -pl libraryd-web -am -DskipTests package
# 跳测试但保留编译
mvn -q -DskipTests package
# 只跑某个测试类
mvn -q -pl libraryd-persistence -Dtest=JdbcBookRepositoryTest test
help:effective-pom 是排查「为什么版本不是我写的那个」最快的方式。dependency:tree -Dverbose 会显示被省略的冲突路径,但输出很长,建议配合 -Dincludes 过滤。
五个常见配置错误
- 插件写在
pluginManagement里却以为会执行。 它只提供版本和默认配置,执行要写进<build><plugins>。 - 子模块重复写版本。 父 POM 已经锁定的依赖,子模块再写版本会覆盖统一管理,升级时漏改。
- Shade 没有过滤签名文件。 运行时报
Invalid signature file digest for Manifest main attributes。 - Shade 没有合并
META-INF/services。 Jackson、Flyway 等库的 SPI 文件被覆盖,运行时表现为「找不到实现」。 - JaCoCo 的
argLine被 Surefire 覆盖。 如果自定义了argLine,要写成<argLine>@{argLine} ...</argLine>,否则覆盖率数据为空。
小结
- 父 POM 负责版本、编码、编译目标与插件默认配置;子模块只声明依赖和必要的构建行为。
- Enforcer 负责工具链和依赖收敛,Checkstyle/SpotBugs 负责静态质量,JaCoCo 负责覆盖率,Shade/Jib 负责产物。
- 质量插件放在
qualityprofile,本地快、CI 严。 - 最常见的配置问题是插件没执行、版本被覆盖、Shade 资源合并与 JaCoCo
argLine。 - 排查配置先看
help:effective-pom,再看dependency:tree,不要靠猜。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。