尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Lombok核心三注解:@Data与构造器详解

Lombok核心三注解:@Data与构造器详解 写Java实体类的人大概率都逃不过Lombok。而Lombok里出现频率最高的三个注解就是Data、NoArgsConstructor和AllArgsConstructor。这篇文章我就把这三个注解从头到尾讲透它们各自帮我们做了什么事、底层是怎么实现的、适合用在什么场景、有哪些坑是你没注意到的最后再给出一套在IntelliJ IDEA里从依赖到插件的完整实操流程。不管你是刚接触Lombok的新手还是用了好几年但只停留在“会加注解”层面的老手这篇应该都能给你一些新东西。先说点实在的。很多网上教程喜欢把Lombok吹成“简化代码的利器”这个说法没错但没说到点子上。真正的重点在于Lombok把Java开发里最机械、最重复、最没技术含量的那部分“样板代码”用编译期生成的方式替你写掉了。它和手写getter/setter的唯一区别是你不是在源码里看到那些方法而是在编译后的字节码里看到它们。仅此而已。理解了这句你就能避开网上大半关于Lombok的玄学问题。1. 为什么项目里全是DataLombok的定位与核心机制1.1 样板代码之痛与Lombok的解题思路写一个简单的User类id、name、age、active四个字段手写的话需要getter、setter各四个再加equals、hashCode、toString和至少一个构造器。你随手在IDE里点一下Generate机器就替你生成了但随之而来的问题是一旦字段变了这些方法全部要同步改一遍。如果项目里有几十个这样的实体类每次需求变动就是一次大规模机械化修改代码review时扫一眼全是套路。Lombok的思路很有意思我们不手写这些方法也不在源码里显示它们而是通过注解告诉Lombok“这个类需要什么方法”让Lombok在编译阶段把方法“注入”到类里。于是源码里只剩字段和注解非常干净而编译出来的.class文件里方法一个不少运行时和手写版本完全等价。我见过有人用IDE的模板和Live Template来解决同一个问题但模板的问题是“生成之后就和源码绑定了”以后改动字段还得回去改生成代码。Lombok则是“实时跟着字段走”——字段改了编译时重新生成的方法就是新的不存在同步维护的问题。这才是Lombok相比其他方案最核心的优势。1.2 Lombok工作真相编译期改代码不是运行时魔法这里有非常多的人误会Lombok的原理。有同事问我“Lombok是不是用了反射运行时动态生成方法” 不是。Lombok用的是Java编译规范里早就提供的注解处理器Annotation Processor机制在javac把源码编译成字节码的过程中Lombok会介入抽象语法树AST把你需要的getter、setter、构造器、toString等方法节点直接添加到类节点里然后javac继续编译这个被修改过的语法树最终产出的字节码里就包含了这些方法。所以它和反射、动态代理完全是两码事编译出来的方法就是普通方法常量池里写着、字节码里编译好了调用的时候就是普通的invokevirtual没有任何多余开销。这也是Lombok敢说自己“零运行时依赖”的原因运行的时候classpath里根本没有lombok.jar都行因为编译产物里已经什么都没有引用了。这个事还有一个实际推论既然是在编译期改AST那么IDE必须“知道”代码里有哪些方法才能提供智能提示和跳转。IDE是拿着磁盘上的源码做静态分析的看不到编译生成的字节码所以必须装Lombok插件让IDE也模拟一遍“生成方法”的逻辑才能正确识别getter、setter。很多人装了Lombok依赖没装插件代码一编译就报“找不到符号”根源就是这个。1.3 为什么这组注解是“黄金组合”你在网上随便找Lombok示例十个里有八个会写成Data NoArgsConstructor AllArgsConstructor public class User { // 字段 }这个组合不是随手写的是实践里趟出来的最佳拍档。单独用Data时它会帮你生成getter、setter、toString、equals、hashCode以及一个“必选参数构造器”。注意这个“必选参数构造器”不是无参构造器它只包含final字段和标记了NonNull的字段。如果你的类里没有任何final字段那它实际上就是一个无参构造器可一旦有人给某个字段加了final构造器签名就变了很多依赖无参构造器的框架瞬间崩溃。所以项目里做实体类时我习惯固定写上NoArgsConstructor和AllArgsConstructor。无参构造器用来满足Jackson、Hibernate、MyBatis这类工具和框架的实例化需求全参构造器则用来在测试或业务代码里一行代码创建完整对象。三个注解各管一摊配合起来才没有盲区。2. 三个核心注解逐一说透生成逻辑、适用场景与隐藏坑点2.1 Data它到底帮你写了哪些代码Data是一个“组合注解”它本身不直接干活而是等价于同时标注了以下五个注解Getter、Setter、ToString、EqualsAndHashCode、RequiredArgsConstructor。也就是说给一个类标注Data就等于同时声明了这个类需要上述所有能力。先说生成规则。Data会为类里的每个非static字段生成getter和setter对于boolean基本类型字段getter叫isXxx而不是getXxx比如 boolean active 对应的读方法是isActive()对于Boolean包装类型生成的则是getActive()。这个细节很多人没注意写反射或者写JSON序列化框架的扩展逻辑时一旦搞错方法名排查起来挺费劲。我用一个表把命名规则说清楚字段声明生成的读方法生成的写方法private String namename()setName(String name)private int agegetAge()setAge(int age)private boolean activeisActive()setActive(boolean active)private Boolean flaggetFlag()setFlag(Boolean flag)然后toString、equals、hashCode这三个方法会默认使用所有非static、非transient字段。这一点也有坑如果你的类里有一个不想参与toString或equals的字段比如一个日志对象或者一个懒加载代理你需要用ToString.Exclude或EqualsAndHashCode.Exclude把它排除掉。Data还有一层容易被忽略的能力允许单个字段上用Setter(AccessLevel.PRIVATE)之类来收缩权限。比如id字段不允许外部修改但其他字段可以正常set就可以在id上用Setter(AccessLevel.PRIVATE)。这比单独手写一堆getter/setter灵活得多。还有一个实战中经常踩坑的点继承。如果当前类继承了一个父类父类有自身的业务字段那么Data生成的equals/hashCode默认只比较当前类自己的字段不比较父类字段。这在很多业务场景下会导致一个逻辑Bug两个对象子类字段完全一样但父类字段不一样equals判断却是相等的。解决办法是用EqualsAndHashCode(callSuper true)显式要求把父类的字段也纳入比较写法和含义都很直白。2.2 NoArgsConstructor无参构造器为什么不能随便省NoArgsConstructor的作用就是生成一个无参构造器。它看起来最简单但包含的细节和坑点其实不少。第一点是和final字段的冲突。如果类里有一个没在声明处初始化的final字段那这个类根本无法有一个合法的无参构造器——因为final字段必须在构造器里被赋值。此时直接标NoArgsConstructor编译会直接报错。如果确实需要无参构造器同时又要保留final字段让它在后续被赋值Lombok提供了force属性NoArgsConstructor(force true)。它的行为是把没有被显式初始化的final字段在无参构造器里强制赋予默认值比如int给0、boolean给false、引用类型给null。不过我要多说一句forcetrue本质上是在“绕过Java语言规则”它能编译过但语义上final字段被悄悄赋了默认值这和你预期的“不可变”是矛盾的。我建议实体类里尽量少用final字段如果确有不可变需求这个类往往不适合作为被框架管理的实体而是应该作为值对象单独设计。第二点是访问级别。很多框架尤其是JPA、Hibernate要求实体类有一个protected甚至是public的无参构造器。Lombok默认生成public构造器但有时候你不想外部直接new一个“空壳对象”可以写成NoArgsConstructor(access AccessLevel.PROTECTED)。这样既让框架能反射实例化也阻止了业务代码无意中创建未初始化的实体。这是一个容易被忽略但非常实用的写法。第三点是关于“为什么要无参构造器”。项目中用Jackson做JSON反序列化、用MyBatis做结果集映射、用JPA代理延迟加载底层往往需要先调用无参构造器创建对象再通过反射填充字段。因此一旦实体类里手写了带参构造器又没补上无参构造器运行时就会冒出各种NoSuchMethodException或者奇怪的实例化失败。别慌这个问题的解药就是NoArgsConstructor。2.3 AllArgsConstructor全参构造器的组合玩法AllArgsConstructor生成一个包含类中所有字段的构造器参数顺序遵循字段声明顺序。它最大的价值是当你需要一个“完整对象”时可以一行代码完成创建而不是new完之后再连续调用好多个setter。这个注解还提供了一个静态工厂方法的玩法AllArgsConstructor(staticName of)。加上这个属性后Lombok不会再生成传统的构造器而是生成一个public static的方法of()它的参数和原构造器一致内部再调用private构造器完成创建。于是创建对象时你可以写成User.of(1L, 张三, 18, true)代码语义会比new User(...)更清晰。这个风格在很多团队代码规范里非常受欢迎。它和Builder的配合也值得一提。Builder单独标注时Lombok会隐式生成一个包级私有的全参构造器供builder内部使用。如果这个类同时标注了AllArgsConstructor两边的行为会协调起来你可以既享受builder的链式写法也能直接new全参构造器二者互不冲突。实际项目中我经常同时写AllArgsConstructor和Builder日常单元测试里new完整对象非常方便业务代码里用builder构建对象避免参数过多时看花眼。但我必须提醒一个容易引发争议的点如果你只有AllArgsConstructor而没有NoArgsConstructor同时你的类里没有无参构造器此时一旦被JSON反序列化或ORM接入大概率会挂。原因就是我上面说的框架需要无参实例化。所以强烈建议“要么两个构造器注解一起写要么明确你的类不会被框架实例化”。这也是为什么黄金组合三个注解总是成对出现。3. 从零到一Idea里把Lombok完整跑通3.1 依赖引入Maven和Gradle的正确姿势先看Maven项目。Lombok的Maven坐标是org.projectlombok:lombok。加依赖的时候有一个常见误区要不要指定scope我建议用provided或optional让这个依赖只在编译阶段存在不进入最终打出的jar或war包。原因很简单前面讲了Lombok在运行时根本不需要打进发布物里只是白白增加体积。在Spring Boot项目里还有一个便利Spring Boot的parent pom把Lombok版本统一管理了你只需要写group和artifactId不用写version。如果你的项目是普通Maven项目或者依赖没有统一管理那一定要手动指定一个稳定版本。我写这篇时用得比较多的版本是1.18.30它在JDK 8到JDK 21之间都工作得比较稳定。Gradle项目的写法也很简单用compileOnly注解dependencies { compileOnly org.projectlombok:lombok:1.18.30 annotationProcessor org.projectlombok:lombok:1.18.30 }第二行annotationProcessor是Spring Boot官方文档推荐我顺手加的。对于Gradle项目来说annotationProcessor是真正触发注解处理的关键配置只写compileOnly可能导致IDE里一切正常但命令行跑gradle build时方法没生成。这是Gradle新手比较常见的坑。3.2 IDEA插件安装在线与离线两种方案依赖加上之后打开IntelliJ IDEA通常会在类上标Data的地方看到一个“无法解析符号getXxx”的红色报错因为IDE还没理解Lombok。解决的唯一办法是装插件。在线安装很简单File - Settings - Plugins搜索“Lombok”找到写着“Lombok”的插件点Install后重启IDEA。装完之后还要检查一项Settings - Build, Execution, Deployment - Compiler - Annotation Processors确保“Enable annotation processing”是勾选状态。这一步很多人漏了漏掉的结果就是代码标红但编译能过或者反之。离线安装的场景也常见尤其是公司内网环境或者外网下载插件一直失败的情况。方法是先在能联网的机器上到JetBrains插件市场搜“Lombok”下载对应的zip安装包然后把zip拷到目标机器上。在IDEA的Plugins面板里点击齿轮图标选择Install Plugin from Disk...选中这个zip重启IDEA即可。这个方式不依赖在线商店只要你手里有zip包就能装实测非常稳。装完插件后还有一个小技巧用IDEA的Structure面板查看类的方法列表能直接看到Xxx生成的那些方法。如果Structure里能看到getXxx这些说明插件生效了看不到就说明插件或者Annotation Processing有问题。这个检查方式比编译一次快得多也是我平时判断Lombok是否生效的第一手段。3.3 一个可运行的标准实体类Demo下面用一段代码演示一下三个注解的配合效果。这是一个用户实体类包含四个不同特征字段Data NoArgsConstructor AllArgsConstructor public class User { private Long id; private String name; private Integer age; private boolean active; }保存之后打开IDEA的Structure面板你应该能看到系统自动“看到”的方法列表getId、setId、getName、setName、getAge、setAge、isActive、setActive、equals、hashCode、toString、无参构造器User()、全参构造器User(Long, String, Integer, boolean)。注意boolean字段active生成的是isActive()而不是getActive()。如果你对Lombok生成的方法内容有疑虑还有一招叫做Delombok。在IDEA里选中类名右键 - Refactor - DelombokIDEA会用插件把注解展开成真实的Java代码替换掉注解。展开后你能看到类似这样的内容public User() {} public User(Long id, String name, Integer age, boolean active) { this.id id; this.name name; this.age age; this.active active; } public Long getId() { return id; } public void setId(Long id) { this.id id; } // ... 其余方法这个功能在你想搞清楚Lombok到底“变”出什么代码或者和同事评审代码、排查问题时特别有用。用完记得撤销因为展开后源码就失去“自动随字段变化”的能力了。4. 常见问题与排查技巧实录4.1 IDE不识别、getter/setter找不到符号怎么办这个问题的现象很经典代码里标注了Data但写user.getName()时IDEA标红提示找不到方法有时候连编译都过不了报“找不到符号”。按顺序排查三件事。第一插件是否安装并启用。你去Plugins里看一眼Lombok插件在不在、有没有勾选。没有就装装完重启。第二Plugin装好了还标红检查Annotation Processing有没有勾选。位置在Settings - Build, Execution, Deployment - Compiler - Annotation Processors。我遇到过好几个项目插件装了但这里没开IDE一直报错。勾上之后重新构建通常就好了。第三如果插件和Annotation Processing都正常但IDEA还是标红试着File - Invalidate Caches / Restart清一下缓存。这个操作会重建索引经常能解决一些IDE状态错乱的问题。我做过的项目里十次这种报错有八次是前两步就能解决剩下两次就是清缓存解决。还有一个容易忽视的地方多模块项目里依赖是不是真的加到了出问题的那个模块很多人会在父pom里声明依赖但子模块没有继承或者依赖作用域配置错了。用IDEA的Maven面板看一眼该模块的依赖树Lombok在不在里面一目了然。4.2 编译器不兼容报错You arent using a compiler supported by lombok另一个高频报错长这样java: You arent using a compiler supported by lombok, so lombok will not work with your compiler(s).这个错误我第一次遇到时也懵了依赖有、插件有、注解处理器也开着怎么就不支持了后来查明白了本质是JDK版本和Lombok版本不匹配。Java每年发布新版本javac的编译器内部实现会变而Lombok是通过操作AST工作的版本太旧的Lombok不认识新版编译器的内部结构就会直接罢工并给出这个提示。解决思路非常简单升级Lombok到最新版本。比如当时项目用的是1.18.20JDK升级到21后就开始报这个错把Lombok升到1.18.30就好了。如果你在用很新的JDK比如JDK 22或更新的版本我建议直接去Lombok官网看release notes确认自己用的Lombok版本明确支持该JDK不要用“差不多就行”的心态去赌。额外提醒一个细节有时候报错信息里的编译器和你看的JAVA_HOME不是同一个。很多开发机配了多个JDKIDE里配置的JDK版本和命令行mvn用的是同一个吗不同的话Lombok处理时看到的javac版本和你预期完全不同。排查时可以在IDE的Terminal里执行java -version和javac -version检查两边的版本一致性。4.3 运行时反序列化失败等隐蔽问题有一种问题最烦人编译、启动、跑客户端请求都正常但只要一执行JSON反序列化或者从数据库查记录映射成对象就报各种奇奇怪怪的实例化异常。这种问题十有八九是缺无参构造器。典型场景实体类里手写了一个全参构造器或者只标了AllArgsConstructor没有NoArgsConstructor。Jackson在反序列化时默认会找无参构造器来创建对象找不到就报错。解决方法是给类加上NoArgsConstructor或者用JsonCreator之类的方案。这个坑我印象太深了因为代码编译完全没问题IDE也不报错只有跑到具体业务分支时才爆而且错误信息有时特别抽象没有经验的话排查半天。类似的隐蔽问题还有Data加继承导致的equals/hashCode行为异常。两个子类对象字段一样但父类不同equals返回true业务上该相等的没判断出来该去重的没去掉。这个问题的处理我在前面提过用EqualsAndHashCode(callSuper true)。如果你确实不需要父类字段参与比较那就得在代码注释里写明避免后来接手的人误改。再比如Lombok和Java里record关键字的关系。随着Java 17普及很多新代码直接用record来做不可变数据载体record天生自带构造器、equals、hashCode、toString。这种情况下你不需要、也不应该再用Data因为两者语义重叠且record字段都是final的跟NoArgsConstructor天然冲突。如果你在做新项目要分清哪些场景用record哪些场景用实体类配Lombok不要一套组合用到黑。4.4 离线插件安装与版本管理的实操心得最后把离线插件这事的完整操作流程再给一遍因为真的很多人问。你在一台能上网的电脑上访问JetBrains插件仓库搜索Lombok下载列表里那个适合你IDEA版本的zip。注意下载时要看IDEA版本兼容范围IntelliJ IDEA的版本年份不同插件要求的IDEA版本也不同装错版本有可能装上但不起作用IDEA甚至会提示插件不兼容。然后在目标机器上Settings - Plugins - 右上角齿轮 - Install Plugin from Disk... - 选择刚才的zip - 重启IDEA。重启后确认插件列表里有Lombok然后按照4.1的方法验证是否生效。Lombok版本管理方面我多说一句在项目里不要放任每个模块写不同的Lombok版本否则升级JDK的时候会遭遇“一部分模块能用一部分模块报编译器不支持”的割裂局面。建议在父pom的properties里统一定一个lombok.version变量所有子模块引用它。这样以后换新JDK只需要改一处版本号再统一做一次回归编译省心很多。我个人在实际操作中的体会是Lombok这东西技术和配置层面都不复杂真正容易出问题的永远是对“编译期生成”这个概念的误解。一旦你理解到它在编译后才让方法出现IDE插件、Annotation Processing、无参构造器这些曾经玄学般的问题其实都会瞬间变得合理。我在团队里带新人时会把Delombok当成基础练习让他们做一遍因为亲眼看到“魔法背后的代码”之后很多疑问根本不用再问。如果你现在还被某个Lombok报错困住建议先把我的排查顺序走一遍再不行就打开Structure面板看看方法到底生成了没有——大多数答案都藏在这里。
返回列表