MapStruct注解处理器需用annotationProcessor声明,核心库用implementation,二者版本必须一致;Lombok需同时配置compileOnly和annotationProcessor;须指定Java源/目标版本;IDE需启用注解处理或使用Gradle编译。

Gradle里annotationProcessor和compileOnly必须配对用
MapStruct的注解处理器在编译期运行,它不参与运行时,所以mapstruct-processor不能声明为implementation或runtimeOnly。否则Gradle会把它丢进运行时类路径,但编译器根本找不到处理器——结果就是@Mapper接口没生成实现类,运行时报NoClassDefFoundError或直接NullPointerException。
正确做法是:核心库用implementation,处理器用annotationProcessor,且两者版本严格一致:
dependencies {
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
}
如果你用了Lombok,还要加一条compileOnly(不是annotationProcessor):
-
compileOnly 'org.projectlombok:lombok:1.18.32'—— 否则Lombok生成的getter/setter对MapStruct不可见 -
annotationProcessor 'org.projectlombok:lombok:1.18.32'—— 让Lombok自己也参与注解处理
Java版本和maven-compiler-plugin等效配置别漏掉
Gradle默认用JDK自带编译器,但MapStruct 1.5+要求明确指定源码/目标字节码级别,否则可能触发NullPointerException在DefaultVersionInformation.createManifestUrl——这其实是编译器没把Manifest资源正确打包进processor jar导致的。
在build.gradle里补上:
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
如果你项目还显式用了org.gradle.internal.jvm.Jvm之类旧插件,得换成标准java插件,并确认gradle.properties里没设org.gradle.java.home指向一个不带tools.jar的JRE(比如某些精简版JDK)。
@Mapper接口没生成实现类?先检查IDE是否绕过Gradle编译
IntelliJ IDEA默认启用“Build project automatically”,但它用的是自己的编译器(JPS),不读取annotationProcessor配置。结果就是你改了@Mapper,IDE里没报错,但运行时找不到MyMapperImpl。
解决方法只有两个:
- 关掉IDE自动构建,在终端跑
./gradlew compileJava,然后刷新项目 - 或者在IDEA里打开
Settings → Build → Compiler → Annotation Processors,勾选Enable annotation processing,并设为Obtain processors from classpath
注意:哪怕配置正确,首次生成也要手动触发一次完整编译,否则缓存里还是空的。
Spring项目里用@Mapper(componentModel = "spring")要小心循环依赖
当你写@Mapper(componentModel = "spring"),MapStruct会生成带@Component的实现类,交给Spring管理。但如果你同时又在@Configuration里手动@Bean注册了同一个Mapper,就会触发Spring的循环依赖检测(尤其在构造器注入场景)。
更隐蔽的问题是:如果Mapper接口里引用了某个@Service,而那个@Service又依赖这个Mapper,编译能过,启动直接BeanCurrentlyInCreationException。
建议做法:
- 纯DTO转换逻辑,不要在
@Mapper里uses任何Spring Bean - 真需要调用Service,改用
@BeforeMapping或@AfterMapping方法,把Bean通过参数传入 - 避免
componentModel = "spring"和Mappers.getMapper()混用,二者生命周期完全不同
生成的Impl类名默认是接口名+Impl,但Spring扫描时不会认MyMapperImpl为Bean——除非你加了componentModel = "spring"。这点容易被忽略,一查ApplicationContext里没有这个Bean就懵了。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











