Java开源工具库使用之Lombok
文章目录
- 前言
- 一、常用注解
- 1.1 @AllArgsConstructor/@NoArgsConstructor/@RequiredArgsConstructor
- 1.2 @Builder
- 1.3 @Data
- 1.4 @EqualsAndHashCode
- 1.5 @Getter/@Setter
- 1.6 @Slf4j/@Log4j/@Log4j2/@Log
- 1.7 @ToString
- 二、踩坑
- 2.1 Getter/Setter 方法名不一样
- 2.2 @Builder 不会生成无参构造方法
- 2.3 @Builder 不能build父类属性
- 2.4 @ToString 栈溢出
- 2.5 影响单元测试覆盖率
- 三、源码探秘
- 3.1 APT与JSR 269
- 3.2 实现流程
- 3.3 源码追踪
- 四、优缺点
- 4.1 优点
- 4.2 缺点
- 参考
前言
Lombok 是一款在 Java 开发中广受欢迎的工具库,它能够显著简化 Java 代码的编写过程并减少样板代码的冗余。在面对频繁的getter和setter方法、构造函数、日志记录等重复性代码任务时,Lombok 的出现为开发者带来了极大的便利,无需手动编写这些重复性的代码,减少了代码量,提高了开发效率。
Lombok的使用非常简单,只需在项目中引入 Lombok 库,并在需要的类上添加相应的注解即可。另外,大多数流行的Java集成开发环境(IDE)也都提供了对Lombok 的支持,可以在代码编辑器中正确显示自动生成的代码, IDEA2021 已经内置 Lombok 了。
文档:https://projectlombok.org/features/
pom 依赖如下:
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version><scope>provided</scope>
</dependency>
一、常用注解
1.1 @AllArgsConstructor/@NoArgsConstructor/@RequiredArgsConstructor
这三个注解能够生成类的构造器
-
@AllArgsConstructor
能够生成由所有参数构造的构造方法@AllArgsConstructor public class Test {private Integer age;private String userName; }
生成构造方法,参数顺序为实例类中元素顺序
public class Test {private Integer age;private String userName;public Test(Integer age, String userName) {this.age = age;this.userName = userName;} }
-
@NoArgsConstructor
能够生成无参构造方法@NoArgsConstructor public class Test {private Integer age = 1;private String userName = ""; }
生成无参构造方法
public class Test {private Integer age;private String userName;public Test() {} }
-
@RequiredArgsConstructor
可以为类内final
字段和被@nonNull
修饰的字段 添加构造方法@RequiredArgsConstructor public class Test {private Integer age;private String userName;private final String password;@NonNullprivate String [] lists; }
转化为
public class Test {private Integer age;private String userName;private final String password;private @NonNull String[] lists;public Test(String password, @NonNull String[] lists) {if (lists == null) {throw new NullPointerException("lists is marked non-null but is null");} else {this.password = password;this.lists = lists;}} }
1.2 @Builder
@builder
能够生成支持Builder模式的类,提供一种灵活、可读性高且易于维护的方式来构建对象,尤其是当对象具有多个属性,且需要支持可选参数和默认值时,Builder模式特别有用.
@Builder
public class Test {@Builder.Defaultprivate Integer age = 18;private String userName;private final String password;@NonNullprivate String [] lists;
}
转化如下:
public class Test {private Integer age;private String userName;private final String password;private @NonNull String[] lists;private static Integer $default$age() {return 18;}Test(Integer age, String userName, String password, @NonNull String[] lists) {if (lists == null) {throw new NullPointerException("lists is marked non-null but is null");} else {this.age = age;this.userName = userName;this.password = password;this.lists = lists;}}public static TestBuilder builder() {return new TestBuilder();}public static class TestBuilder {private boolean age$set;private Integer age$value;private String userName;private String password;private String[] lists;TestBuilder() {}public TestBuilder age(Integer age) {this.age$value = age;this.age$set = true;return this;}public TestBuilder userName(String userName) {this.userName = userName;return this;}public TestBuilder password(String password) {this.password = password;return this;}public TestBuilder lists(@NonNull String[] lists) {if (lists == null) {throw new NullPointerException("lists is marked non-null but is null");} else {this.lists = lists;return this;}}public Test build() {Integer age$value = this.age$value;if (!this.age$set) {age$value = Test.$default$age();}return new Test(age$value, this.userName, this.password, this.lists);}public String toString() {return "Test.TestBuilder(age$value=" + this.age$value + ", userName=" + this.userName + ", password=" + this.password + ", lists=" + Arrays.deepToString(this.lists) + ")";}}
}
1.3 @Data
等价于 @Getter
, @Setter
, @RequiredArgsConstructor
, @ToString
, @EqualsAndHashCode
1.4 @EqualsAndHashCode
能够生成equals
和hashCode
方法, 可通过 callSuper = true
来调用父类的同名方法,不参与计算的属性可通过@EqualsAndHashCode.Exclude
进行排除
@EqualsAndHashCode
public class Test {private Integer age = 18;private String userName;
}
转化为
public class Test {private Integer age = 18;private String userName;public Test() {}public boolean equals(Object o) {if (o == this) {return true;} else if (!(o instanceof Test)) {return false;} else {Test other = (Test)o;if (!other.canEqual(this)) {return false;} else {Object this$age = this.age;Object other$age = other.age;if (this$age == null) {if (other$age != null) {return false;}} else if (!this$age.equals(other$age)) {return false;}Object this$userName = this.userName;Object other$userName = other.userName;if (this$userName == null) {if (other$userName != null) {return false;}} else if (!this$userName.equals(other$userName)) {return false;}return true;}}}protected boolean canEqual(Object other) {return other instanceof Test;}public int hashCode() {int PRIME = 59; // 这里IDEA反编译有bug,显示为int PRIME = true;int result = 1;Object $age = this.age;result = result * 59 + ($age == null ? 43 : $age.hashCode());Object $userName = this.userName;result = result * 59 + ($userName == null ? 43 : $userName.hashCode());return result;}
1.5 @Getter/@Setter
生成getter和setter方法,默认跳过静态字段和以$开头的字段
@Getter
@Setter
public class Test {private Integer age = 18;private String userName;
}
转化为
public class Test {private Integer age = 18;private String userName;public Test() {}public Integer getAge() {return this.age;}public String getUserName() {return this.userName;}public void setAge(Integer age) {this.age = age;}public void setUserName(String userName) {this.userName = userName;}
}
1.6 @Slf4j/@Log4j/@Log4j2/@Log
在类中生成1个字段log
,用于记录日志, 使用不同的日志框架可以使用不同的注解
@log4j2
public class Test {private Integer age = 18;private String userName;public static void main(String[] args) {log.info("{}在哪?", "我");}
}
public class Test {private static final Logger log = LogManager.getLogger(Test.class);private Integer age = 18;private String userName;public Test() {}public static void main(String[] args) {log.info("{}在哪?", new Object[]{"我"});}
}
1.7 @ToString
默认将打印所有非静态字段,可以用@ToString.Exclude
注解排除不想打印的字段
@ToString
public class Test {private Integer age;private String userName;
}
public class Test {private Integer age;private String userName;public Test() {}public String toString() {return "Test(age=" + this.age + ", userName=" + this.userName + ")";}
}
二、踩坑
2.1 Getter/Setter 方法名不一样
在类中,开头只有一个小写字母的字段,如 iPhone, 当使用 Lombok 生成 getter、setter 方法时,它生成getter和setter方法如下:
public String getIPhone() {return this.iPhone;
}
public void setIPhone(String iPhone) {this.iPhone = iPhone;
}
和在IDEA中使用快捷键生成的不一样
public String getiPhone() {return iPhone;
}public void setiPhone(String iPhone) {this.iPhone = iPhone;
}
在 SpringBoot 项目中使用 @RequestBody
接收 json 数据时,默认通过 Jackson 处理 ,而 jackson 处理实体,会从getter/setter方法获取具体的字段名,具体源码位于DefaultAccessorNamingStrategy.legacyManglePropertyName
, 如下所示:
/*** Method called to figure out name of the property, given * corresponding suggested name based on a method or field name.** @param basename Name of accessor/mutator method, not including prefix* ("get"/"is"/"set")*/
protected String legacyManglePropertyName(final String basename, final int offset)
{final int end = basename.length();if (end == offset) { // empty name, nopereturn null;}char c = basename.charAt(offset);// 12-Oct-2020, tatu: Additional configurability; allow checking that// base name is acceptable (currently just by checking first character)if (_baseNameValidator != null) {if (!_baseNameValidator.accept(c, basename, offset)) {return null;}}// next check: is the first character upper case? If not, return as ischar d = Character.toLowerCase(c);if (c == d) {return basename.substring(offset);}// otherwise, lower case initial chars. Common case first, just one charStringBuilder sb = new StringBuilder(end - offset);sb.append(d);int i = offset+1;for (; i < end; ++i) {c = basename.charAt(i);d = Character.toLowerCase(c);if (c == d) {sb.append(basename, i, end);break;}sb.append(d);}return sb.toString();
}
以上代码会将生成的 set/get/is 等方法获取字段, 将方法中 set/get/is 按照偏移量移除,然后找到第一个小写的字符,之前的大写字符都会变为小写,这就会导致问题,IPhone会变为 iphone 和字段 iPhone 不同,会导致问题
Lombok 开发者也意识到这种问题,并提供了解决方案:https://projectlombok.org/features/GetterSetter
lombok.accessors.capitalization
= [basic
|beanspec
] (default: basic)Controls how tricky cases like
uShaped
(one lowercase letter followed by an upper/titlecase letter) are capitalized.basic
capitalizes that togetUShaped
, andbeanspec
capitalizes that togetuShaped
instead.
Both strategies are commonly used in the java ecosystem, thoughbeanspec
is more common.
用 Lombok 的配置来解决。在项目resource目录下创建 lombok.config
文件,并添加以下配置项
lombok.accessors.capitalization = beanspec
2.2 @Builder 不会生成无参构造方法
当使用@Builder
后,会有生成全部参数的构造函数,但是没有无参构造方法,这对Spring IOC等框架不太友好,框架需要无参构造函数构造对象。所以,第一感觉就是再加上@NoArgsConstructor
,但是又报错了.
原因分析:如果只是@Builder,那会生成全参构造方法,加上@NoArgsConstructor
,全参构造方法就没了。翻看源码文档
If a class is annotated, then a package-private constructor is generated with all fields as arguments (as if @AllArgsConstructor(access = AccessLevel.PACKAGE) is present on the class), and it is as if this constructor has been annotated with @Builder instead. Note that this constructor is only generated if you haven’t written any constructors and also haven’t added any explicit @XArgsConstructor annotations. In those cases, lombok will assume an all-args constructor is present and generate code that uses it; this means you’d get a compiler error if this constructor is not present
翻译一下
如果一个类被注解,那么将生成一个包专用构造函数,其中所有字段都作为参数(就好像类上存在@AllArgsConstructor(access=AccessLevel.package)一样),并且就好像这个构造函数是用@Builder注解的一样。请注意,只有当您没有编写任何构造函数,也没有添加任何显式@XArgsConstructor注解时,才会生成此构造函数。在这些情况下,lombok将假设存在一个all-args构造函数,并生成使用它的代码;这意味着如果这个构造函数不存在,就会出现编译器错误。
文档说的很明白,当加上@NoArgsConstructor
时,不会生成全参构造方法,造成编译错误
解决方法:很简单,再加上@AllArgsConstructor
2.3 @Builder 不能build父类属性
有两种方案:
-
添加一个构造方法,包含父类的属性
@Data @AllArgsConstructor public class Parent {private String foo;private Integer bar; }
@ToString(callSuper = true) public class Child extends Parent {private Integer age;private String userName;@Builderpublic Child(String foo, Integer bar, Integer age, String userName) {super(foo, bar);this.age = age;this.userName = userName;} }
-
使用
@Superbuilder
, 这是实验性的 API,不知未来是否删除,慎用@Data @AllArgsConstructor @SuperBuilder public class Parent {private String foo;private Integer bar; }
@ToString(callSuper = true) @SuperBuilder public class Child extends Parent {private Integer age;private String userName;}
2.4 @ToString 栈溢出
在使用 JPA 时,实体之间为多对多关系,相互引用,在调用toString
方法是陷入无限递归,栈溢出
可以使用@ToString.Exclude
注解排除多对多引用的字段
2.5 影响单元测试覆盖率
在项目中使用了**@Data** 注解,在使用 Jacoco 对代码进行单元测试,会发现测试覆盖率比较低,一些自动生成的方法没有覆盖到
解决方法:加上以下配置,Lombok会在为由其生成的构造方法、方法、字段和类型中增加@Generated注解,然后Jacoco借助这个注解来实现更为准去的排除。
config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true
三、源码探秘
3.1 APT与JSR 269
编译时注解有以下两种方案:
-
APT(Annotation Processing Tool),自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:
- api都在com.sun.mirror非标准包下
- 没有集成到javac中,需要额外运行
-
JSR-269(Pluggable Annotation Processing API,插件式注解处理器)JDK6 开始纳入了规范,作为apt的替代方案,它解决了apt的以上两个问题。关于处理注解的包在
javax.annotation.processing
, 集成到javac中,javac 过程如下:- Parse and Enter:所有在命令行中指定的源文件都被读取,解析成语法树,然后所有外部可见的定义都被输入到编译器的符号表中。
- Annotation Processing:调用所有适当的注解处理器。如果任何注解处理程序生成任何新的源文件或类文件,则重新开始编译,直到没有创建任何新文件为止。
- Analyse and Generate:最后,解析器创建的语法树将被分析并转换为类文件。在分析过程中,可能会发现对其他类的引用。编译器将检查这些类的源和类路径,如果在源路径上找到它们,也会编译这些文件,尽管它们不需要进行注解处理。
3.2 实现流程
在Javac 解析成 AST(Abstract Syntax Tree, 抽象语法树)之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成 JVM 可执行的字节码Class文件。
具体流程如下:
- 在编译Java源代码时,Java编译器会调用注解处理器API。注解处理器会扫描源代码中的注解,找到Lombok相关的注解。
- 注解处理器:Lombok的注解处理器会解析并处理这些注解。它会通过解析AST来了解源代码的结构,并根据注解生成相应的代码。
- 代码生成:根据注解的类型,Lombok的注解处理器会生成与注解相关的代码片段。例如,
@Getter
注解会自动生成对应属性的getter方法,@Setter
注解会自动生成对应属性的setter方法。 - 代码替换:生成的代码片段将会替换原始源代码中与注解相关的部分。这意味着在编译后的字节码中,生成的代码将取代原始代码,从而实现了代码的增强和简化。
- 编译结果:最终,通过注解处理器的处理,源代码中标记了Lombok注解的部分将会被替换为生成的代码。这些生成的代码将包含在编译后的类文件中,以便在运行时使用。
3.3 源码追踪
打开 lombok.jar 文件,会发现不包含许多.class文件,而是包含名为.SCL.lombok的文件。其实.SCL.lombok文件是.class文件, Lombok 的构建脚本在生成 jar 文件时重命名它们,而 ShadowClassLoader 能够加载这些类,并且首字母缩略词 SCL 似乎来自于此,似乎这样做的原因只是"避免使用基于 SC L的 jar 污染任何项目的命名空间
lombok jar包从maven下载源码,有部分代码找不到源码,IDEA反编译为空,暂未找到解决方法
下面以@Get
注解为例,查看 lombok 是如何生成getter方法的:
-
首先找到的类是
LombokProcessor
这个类,它继承了AbstractProcessor
, 我们知道在自定义一个 APT 的时候需要继承AbstractProcessor
,并实现其最核心的 process 方法来对当前轮编译的结果进行处理,在 Lombok 中也不例外,Lombok 也是通过一个顶层的Processor
来接收当前轮的编译结果,而这个 Processor 就是LombokProcessor
重点关注process
方法的这一段transformer.transform(prio, javacProcessingEnv.getContext(), cusForThisRound, cleanup);
-
JavacTransformer.transform具体如下
public void transform(long priority, Context context, List<JCCompilationUnit> compilationUnits, CleanupRegistry cleanup) {for (JCCompilationUnit unit : compilationUnits) {if (!Boolean.TRUE.equals(LombokConfiguration.read(ConfigurationKeys.LOMBOK_DISABLE, JavacAST.getAbsoluteFileLocation(unit)))) {JavacAST ast = new JavacAST(messager, context, unit, cleanup);ast.traverse(new AnnotationVisitor(priority));handlers.callASTVisitors(ast, priority);if (ast.isChanged()) LombokOptions.markChanged(context, (JCCompilationUnit) ast.top().get());}} }
获取 AST , traverse 遍历
-
继续追踪,找到注解,根据注解位置处理
public void traverse(JavacASTVisitor visitor) {switch (this.getKind()) {case COMPILATION_UNIT:visitor.visitCompilationUnit(this, (JCCompilationUnit) get());ast.traverseChildren(visitor, this);visitor.endVisitCompilationUnit(this, (JCCompilationUnit) get());break;case TYPE:visitor.visitType(this, (JCClassDecl) get());ast.traverseChildren(visitor, this);visitor.endVisitType(this, (JCClassDecl) get());break;case FIELD:visitor.visitField(this, (JCVariableDecl) get());ast.traverseChildren(visitor, this);visitor.endVisitField(this, (JCVariableDecl) get());break;case METHOD:visitor.visitMethod(this, (JCMethodDecl) get());ast.traverseChildren(visitor, this);visitor.endVisitMethod(this, (JCMethodDecl) get());break;case INITIALIZER:visitor.visitInitializer(this, (JCBlock) get());ast.traverseChildren(visitor, this);visitor.endVisitInitializer(this, (JCBlock) get());break;case ARGUMENT:JCMethodDecl parentMethod = (JCMethodDecl) up().get();visitor.visitMethodArgument(this, (JCVariableDecl) get(), parentMethod);ast.traverseChildren(visitor, this);visitor.endVisitMethodArgument(this, (JCVariableDecl) get(), parentMethod);break;case LOCAL:visitor.visitLocal(this, (JCVariableDecl) get());ast.traverseChildren(visitor, this);visitor.endVisitLocal(this, (JCVariableDecl) get());break;case STATEMENT:visitor.visitStatement(this, get());ast.traverseChildren(visitor, this);visitor.endVisitStatement(this, get());break;case ANNOTATION:switch (up().getKind()) {case TYPE:// @Getter放在类上会执行这段visitor.visitAnnotationOnType((JCClassDecl) up().get(), this, (JCAnnotation) get());break;case FIELD:visitor.visitAnnotationOnField((JCVariableDecl) up().get(), this, (JCAnnotation) get());break;case METHOD:visitor.visitAnnotationOnMethod((JCMethodDecl) up().get(), this, (JCAnnotation) get());break;case ARGUMENT:JCVariableDecl argument = (JCVariableDecl) up().get();JCMethodDecl method = (JCMethodDecl) up().up().get();visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation) get());break;case LOCAL:visitor.visitAnnotationOnLocal((JCVariableDecl) up().get(), this, (JCAnnotation) get());break;case TYPE_USE:visitor.visitAnnotationOnTypeUse(up().get(), this, (JCAnnotation) get());break;default:throw new AssertionError("Annotion not expected as child of a " + up().getKind());}break;case TYPE_USE:visitor.visitTypeUse(this, get());ast.traverseChildren(visitor, this);visitor.endVisitTypeUse(this, get());break;default:throw new AssertionError("Unexpected kind during node traversal: " + getKind());} }
-
上述 JavacASTVisitor
public class JavacASTAdapter implements JavacASTVisitor {... }
private class AnnotationVisitor extends JavacASTAdapter {private final long priority;AnnotationVisitor(long priority) {this.priority = priority;}@Override public void visitAnnotationOnType(JCClassDecl type, JavacNode annotationNode, JCAnnotation annotation) {JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get();// 执行这段handlers.handleAnnotation(top, annotationNode, annotation, priority);}... }
-
上述handlers时HandlerLibrary类型, HandlerLibrary 中 handleAnnotation如下
public void handleAnnotation(JCCompilationUnit unit, JavacNode node, JCAnnotation annotation, long priority) {TypeResolver resolver = new TypeResolver(node.getImportList());String rawType = annotation.annotationType.toString();String fqn = resolver.typeRefToFullyQualifiedName(node, typeLibrary, rawType);if (fqn == null) return;List<AnnotationHandlerContainer<?>> containers = annotationHandlers.get(fqn);if (containers == null) return;for (AnnotationHandlerContainer<?> container : containers) {try {if (container.getPriority() == priority) {if (checkAndSetHandled(annotation)) {// 各个注解handler调用各自的handle方法container.handle(node);} else {if (container.isEvenIfAlreadyHandled()) container.handle(node);}}} catch (AnnotationValueDecodeFail fail) {fail.owner.setError(fail.getMessage(), fail.idx);} catch (Throwable t) {String sourceName = "(unknown).java";if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName();javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t);}}}
-
@Get 注解相关handler类为 HandleGetter,重要的handle方法如下:
public void handle(AnnotationValues<Getter> annotation, JCAnnotation ast, JavacNode annotationNode) {handleFlagUsage(annotationNode, ConfigurationKeys.GETTER_FLAG_USAGE, "@Getter");Collection<JavacNode> fields = annotationNode.upFromAnnotationToFields();// 将@Getter注解删除deleteAnnotationIfNeccessary(annotationNode, Getter.class);// 删除lombok 引用包deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel");JavacNode node = annotationNode.up();Getter annotationInstance = annotation.getInstance();AccessLevel level = annotationInstance.value();// 判断lazy属性boolean lazy = annotationInstance.lazy();if (lazy) handleFlagUsage(annotationNode, ConfigurationKeys.GETTER_LAZY_FLAG_USAGE, "@Getter(lazy=true)");if (level == AccessLevel.NONE) {if (lazy) annotationNode.addWarning("'lazy' does not work with AccessLevel.NONE.");return;}if (node == null) return;List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode);// 根据在字段,还是类生成getter方法switch (node.getKind()) {case FIELD:createGetterForFields(level, fields, annotationNode, true, lazy, onMethod);break;case TYPE:if (lazy) annotationNode.addError("'lazy' is not supported for @Getter on a type.");generateGetterForType(node, annotationNode, level, false, onMethod);break;} }
四、优缺点
4.1 优点
- 最大的优点就是减少样板代码的编写,提高开发效率
- 通过使用 Lombok,当类的属性发生变化时,不需要手动更新相应的 getter、setter、equals 和 hashCode 方法等,Lombok 会自动帮助生成更新后的代码,提高代码的维护性
- 大多数主流的 Java IDE(如 IntelliJ IDEA、Eclipse)都对 Lombok 提供了良好的支持,可以正确地识别和处理 Lombok 的注解,帮助开发者在开发过程中更好地理解和调试代码
- 避免一些工具不支持 Lombok,提供delombok,将被 Lombok 处理后的字节码重新翻译为java源代码
4.2 缺点
-
在使用Lombok过程中,如果对于各种注解的底层原理不理解的话,很容易产生意想不到的结果
举一个简单的例子,我们知道,当我们使用@Data定义一个类的时候,会自动帮我们生成equals()方法 。但是如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,会默认是@EqualsAndHashCode(callSuper=false),这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性
-
同样的,尽管 Lombok 自动生成的代码可以减少重复性代码,但有时候也可能会导致可读性下降。由于生成的代码被隐藏起来,其他开发人员可能不太容易理解代码的实际逻辑
-
调试困难:由于Lombok会修改源代码,导致在调试时可能无法准确地查看和追踪生成的代码。这可能会对代码调试和排错造成一定的困扰
-
版本兼容性:Lombok的注解处理器会直接修改Java源文件,这使得在不同版本的Java编译器和IDE之间使用Lombok可能存在兼容性问题。当你在不同环境中编译或构建项目时,可能需要额外考虑Lombok的版本兼容性
-
项目编译变慢了
参考
- 这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题
- Lombok 原理分析
相关文章:
Java开源工具库使用之Lombok
文章目录 前言一、常用注解1.1 AllArgsConstructor/NoArgsConstructor/RequiredArgsConstructor1.2 Builder1.3 Data1.4 EqualsAndHashCode1.5 Getter/Setter1.6 Slf4j/Log4j/Log4j2/Log1.7 ToString 二、踩坑2.1 Getter/Setter 方法名不一样2.2 Builder 不会生成无参构造方法2…...
uboot启动流程涉及reset函数
一. uboot启动流程中函数 之前了解了uboot链接脚本文件 u-boot.lds。 从 u-boot.lds 中我们已经知道了入口点是 arch/arm/lib/vectors.S 文件中的 _start。 本文了解 一下,uboot启动过程中涉及的 reset 函数。本文继上一篇文章学习,地址如下ÿ…...
端口被占用怎么解决
第一步:WinR 打开命令提示符,输入netstat -ano|findstr 端口号 找到占用端口的进程 第二步: 杀死使用该端口的进程,输入taskkill /t /f /im 进程号( !!!注意是进程号,不…...
python reportlab 生成多页pdf
多页 from reportlab.pdfgen import canvas from reportlab.platypus import (SimpleDocTemplate, Paragraph, PageBreak, Image, Spacer, Table, TableStyle) from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY from reportlab.lib.styles import P…...
word 多级目录的问题
一、多级标题自动编号 --> 制表符 -> 空格 网址: 【Word技巧】2 标题自动编号——将多级列表链接到样式 - YouTube 二、多级列表 --> 正规形式编号 网址:Word 教学 - 定框架:文档格式与多级标题! - YouTube 三、目…...
python使用mitmproxy和mitmdump抓包之拦截和修改包(四)
我认为mitmproxy最强大的地方,就是mitmdump可以结合python代理,灵活拦截和处理数据包。 首先,mitmdump的路径如下:(使用pip3 install mitmproxy安装的情况,参考我的文章python使用mitmproxy和mitmdump抓包…...
邓俊辉《数据结构》→ “2.6.5 二分查找(版本A)”之“成功查找长度”递推式推导
【问题描述】 邓俊辉的《数据结构(C语言版)(第3版)》(ISBN:9787302330646)中,开始于第48页的“2.6.5 二分查找(版本A)”内容在第50页详述了“成功查找长度”的…...
Linux文件查找,别名,用户组综合练习
1.文件查看: 查看/etc/passwd文件的第5行 [rootserver ~]# head -5 /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologi…...
【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现【更新中】
【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现 本文介绍基于libsvm代理模型算法的特征排序方法合集,包括: 1.基于每个特征预测精度进行排序(libsvm代理模型) 2.基于相关系数corr的…...
第三章 图标辅助元素的定制
第三章 图标辅助元素的定制 1.认识图表常用的辅助元素 图表的辅助元素是指除了根据数据绘制的图形之外的元素,常用的辅助元素包括坐标轴、标题、图例、网格、参考线、参考区域、注释文本和表格,它们都可以对图形进行补充说明。 上图中图表常用辅…...
【前端】ECMAScript6从入门到进阶
【前端】ECMAScript6从入门到进阶 1.ES6简介及环境搭建 1.1.ECMAScript 6简介 (1)ECMAScript 6是什么 ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标ÿ…...
Android Shape设置背景
设置背景时,经常这样 android:background“drawable/xxx” 。如果是纯色图片,可以考虑用 shape 替代。 shape 相比图片,减少资源占用,缩减APK体积。 开始使用。 <?xml version"1.0" encoding"utf-8"?…...
什么是GraphQL?它与传统的REST API有什么不同?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是GraphQL?⭐ 与传统的REST API 的不同⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣…...
如何定时备份使用Docker构建的MySQL容器中的数据库
👨🏻💻 热爱摄影的程序员 👨🏻🎨 喜欢编码的设计师 🧕🏻 擅长设计的剪辑师 🧑🏻🏫 一位高冷无情的编码爱好者 大家好,我是 DevO…...
Java【手撕链表】LeetCode 143. “重排链表“, 图文详解思路分析 + 代码
文章目录 前言一、两数相加1, 题目2, 思路分析2,1 找到中间结点2.2, 逆序后半段链表2.3, 合并两个链表 3, 代码 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: 📕 JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管…...
C语言 cortex-A7核 按键中断 实验【重点】
一、KEY1 include/key.h #ifndef __KEY_H__ #define __KEY_H__#include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_exti.h" #include "stm32mp1xx_gic.h"//RCC/GPIO章节初始化 void hal_rcc_gpio_init…...
freertos中函数调用和启动第一个任务(栈相关!!!!!!)
本内容仅就一些较难理解的点讲解,请结合其它文章实用 在函数调用时,m3的处理器使用r0-r3共四个寄存器传参,其余的使用栈传参。 但是,如果传入的参数是全局变量,则不需传参,因为全局变量在函数内部是可见的…...
【PHP】如何关闭buffer实时输出内容到前端
前言 默认情况下,我们在PHP里使用echo等函数输出的内容,是不会马上发送给前端的,原因是有 buffer 的存在,buffer又分两处,一处是PHP本身的buffer,另一处是Nginx的buffer。只有当buffer满了之后,…...
Scala第二章节
Scala第二章节 scala总目录 章节目标 掌握变量, 字符串的定义和使用掌握数据类型的划分和数据类型转换的内容掌握键盘录入功能理解Scala中的常量, 标识符相关内容 1. 输出语句和分号 1.1 输出语句 方式一: 换行输出 格式: println(里边写你要打印到控制台的数据);方式二…...
Spring修炼之路(2)依赖注入(DI)
一、概念 依赖注入(Dependency Injection,DI)。 测试pojo类 : Address.java 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 . 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 . 二、 注入方式 2.1构造器注入 我们在之前的案例已经…...
编写Android.mk / Android.bp 引用三方 jar 包,aar包,so 库
一.前言 在Android10之后,所有项目工程中,官方推荐使用Android.bp去编译构建,以前使用Android.mk构建的项目随着版本迭代升级,慢慢需要变更为Android.bp, 两者的语法都需要去了解并熟练使用。 笔者之前写过Android.mk的…...
【kylin】【ubuntu】搭建本地源
文章目录 一、制作一个本地源仓库制作ubuntu本地仓库制作kylin本地源 二、制作内网源服务器ubuntu系统kylin系统 三、使用内网源ubuntukylin 一、制作一个本地源仓库 制作ubuntu本地仓库 首先需要构建一个本地仓库,用来存放软件包 mkdir -p /path/to/localname/pac…...
为什么 Go 语言 struct 要使用 tags
在 Go 语言中,struct 是一种常见的数据类型,它可以用来表示复杂的数据结构。在 struct 中,我们可以定义多个字段,每个字段可以有不同的类型和名称。 除了这些基本信息之外,Go 还提供了 struct tags,它可以用…...
WebGL笔记:WebGL中JS与GLSL ES 语言通信,着色器间的数据传输示例:用鼠标控制点位
用鼠标控制点位 <canvas id"canvas"></canvas><!-- 顶点着色器 --> <script id"vertexShader" type"x-shader/x-vertex">attribute vec4 a_Position;void main() {// 点位gl_Position a_Position;// 尺寸gl_PointSize…...
算法 主持人调度-(双指针+贪心)
牛客网: BM96 题目: 一个主持人只能参加一个活动,至少需要多少主持人 思路: 对start, end排序从小到大;初始化指针l, r 0, 0;start[r]< end[l]时需要累加人数同时r,否则l,r同时移动;直至不再满中l<n &&am…...
Elasticsearch 集群时的内部结构是怎样的?
Apache Lucene : Flush, Commit Elasticsearch 是一个基于 Apache Lucene 构建的搜索引擎。 它利用 Lucene 的倒排索引、查询处理和返回搜索结果等功能来执行搜索。 它还扩展了 Lucene 的功能,添加分布式处理功能以支持大型数据集的搜索。 让我们看一下 Apache Luc…...
IoTDB 在国际数据库性能测试排行榜中位居第一?测试环境复现与流程详解第一弹!...
最近我们得知,Apache IoTDB 多项性能表现位居 benchANT 时序数据库排行榜(Time Series: DevOps)性能排行第一名!(榜单地址:https://benchANT.com/ranking/database-ranking) benchANT 位于德国&…...
react项目优化
随着项目体积增大,打包的文件体积会越来越大,需要优化,原因无非就是引入的第三方插件比较大导致,下面我们先介绍如何分析各个文件占用体积的大小。 1.webpack-bundle-analyzer插件 如果是webpack作为打包工具的项目可以使用&…...
青藏高原1-km分辨率生态环境质量变化数据集(2000-2020)
青藏高原平均海拔4000米以上,人口1300万,是亚洲九大河流的源头,为超过15亿人口提供淡水、食物和其他生态系统服务,被誉为地球第三极和亚洲水塔。然而,在该地区的人与自然的关系的研究是有限的,尤其是在精细…...
Nature Communications | 张阳实验室:端到端深度学习实现高精度RNA结构预测
RNA分子是基因转录的主要执行者,也是细胞运作的隐形功臣。它们在基因表达调控、支架构建以及催化活性等多个生命过程中都扮演着关键角色。虽然RNA如此重要,但由于实验数据的缺乏,准确预测RNA 的三维空间结构仍然是目前计算生物学面临的重大挑…...
西安网站建设首选/优化系统的软件
原标题:「数控干货」基于UG CLS文件使用 C 语言制作智能后处理工具1 前言UG 后处理操作是 UGCAM 数控加工工作中一个重要环节,主要任务是把在 UG 加工环境下生成的加工刀位文件转换成机床可接受的数控代码文件。UG 本身提供了强大的 Post Builder 后处理…...
手机网站开发隐藏网址/网页设计费用报价
现在企业正常需要考虑两种类型的云计算,公有云和私有云。人们自然需要一些术语来描述公共云和私有云之间的应用程序和数据传输,并将此架构定义为混合云。尽管公共云和私有云独立运行,但这种数据共享可以通过使用加密连接来实现。这种加密的高…...
闵行区做网站/哔哩哔哩推广网站
一.Core标签库 • 核心标签库主要包括通用标签、条件标签、迭代标签和与URL相关的标签。 • 在使用Core标签库的JSP文件的开始部分,添加代码: <%taglib uri"http://java.sun.com/jsp/jstl/core" prefix"c"…...
如何做网站营销/上海百度竞价
makefile简介 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂…...
建设银行网站收费吗/郴州网站建设网络推广渠道
Kali Linux中暂时不能解析域名环境:kali linux这个问题是再用apt install命令安装软件时发现的用ping www.xxxxxxxx.com 再次确认无法解析域名参考:https://www.cnblogs.com/zylq-blog/p/6654586.html kali域名解析错误解决http://ddrv.cn/a/177111 …...
梅花手表网站/百度网盘官网入口
1. 运算符优先级与括号 #define Cube(a) a*a*a 无法解决 Cube(11) ⇒ 11*11*11 ⇒ 4,期待的应当是 8,故将其改造为 #define Cube(a) (a)*(a)*(a) 如此,自身运算的优先级是能解决了,和其他表达式结合时便又存在先运算和后运算的算…...