1、lombok
lombok是java开发中非常常用的开发工具,他可以帮我们使用注解的方式生成一些基本的代码,比如 get
、 set
方法,日志打印工具等等
lombok的原理比较简单,他使用java的即时编译技术,在编译阶段能够去扫描注解,并根据注解生成其对应的代码。
1.1、使用lombok
我们想要使用lombok,必须做两个工作
- 在idea中安装lombok插件
- 引入lombok依赖
1.idea安装lombok插件
我们之所以需要安装lombok插件,是因为idea本身会有代码检查,而lombok生成的代码会在编译的时候才生成,并不能过idea代码检查,因此我们需要在idea中安装lombok插件,去告诉idea我们所使用lombok生成的一些代码,虽然现在我没写,但是编译的时候会生成运行通过的。
idea在2021版本自带lombok插件,不过你也可以检查一下是否依赖
如果你灭有在idea中安装lombok依赖,那么即使你使用了lombok,idea在代码检查的时候也会报你缺失某些方法或者类,无法运行。
通过IntelliJ的插件中心安装lombok插件
2.引入lombok依赖
通常来讲我们使用maven包管理工具,版本你可以选择更新的
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
1.2、lombok的注解
1.2.1、lombok注解说明
val
:用在局部变量前面,相当于将变量声明为final@NonNull
:给方法参数增加这个注解会自动在方法内对该参数进行是否为空的校验,如果为空,则抛出NPE(NullPointerException)@Cleanup
:自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成try-finally这样的代码来关闭流@Getter/@Setter
:用在属性上,再也不用自己手写setter和getter方法了,还可以指定访问范围@ToString
:用在类上,可以自动覆写toString方法,当然还可以加其他参数,例如@ToString(exclude=”id”)排除id属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的toString方法,包含所有属性@EqualsAndHashCode
:用在类上,自动生成equals方法和hashCode方法@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
:用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有@NonNull属性作为参数的构造函数,如果指定staticName = “of”参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多@Data
:注解在类上,相当于同时使用了@ToString
、@EqualsAndHashCode
、@Getter
、@Setter
和@RequiredArgsConstrutor
这些注解,对于POJO类
十分有用@Value
:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法@Builder
:用在类、构造器、方法上,为你提供复杂的builder APIs,让你可以像如下方式一样调用Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();
更多说明参考Builder@SneakyThrows
:自动抛受检异常,而无需显式在方法上使用throws语句@Synchronized
:用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性$lock
或$LOCK
,而java中的synchronized关键字锁对象是this,锁在this或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁this或者类对象,这可能会导致竞争条件或者其它线程错误@Getter(lazy=true)
:可以替代经典的Double Check Lock样板代码@Log
:根据不同的注解生成不同类型的log对象,但是实例名称都是log,有六种可选实现类
@CommonsLog
Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);@Log
Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName());@Log4j
Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);@Log4j2
Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);@Slf4j
Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);@XSlf4j
Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
1.2.2、常用的注解
我们来了解一下我们常用的lombok注解
1.2.2.1、实体类生成
1.@Getter/@Setter
作用:生成所有属性的set方法和get方法
注意:要对属性规范命名(前两个字母小写),否则比如你的字段名为: wName
,那么lombok在生成get、set方法名字就会是: SetWName
,首字母会大写。
@Setter
@Getter
public class User {
private Long id;
private String name;
}
会为所有属性生成get和set方法:
public class User {
private Long id;
private String name;
public User() {
}
public void setId(final Long id) {
this.id = id;
}
public void setName(final String name) {
this.name = name;
}
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
2、@AllArgsConstructor 和 @NoArgsConstructor
- @AllArgsConstructor 会生成携带所有属性参数的有参构造函数
- @NoArgsConstructor 会生成无参构造函数
需要注意的是,如果类携带了有参构造,那么默认的无参构造就会被顶替,需要我们手动添加无参构造或者引入 @NoArgsConstructor才可以添加。
本质原因就是因为java类在没重写构造函数的时候本身会自带无参构造,但是当你编写有参构造的时候无参构造会被顶替。
3、@ToString
这个注解实际上就是生成ToString方法,类名和所有参数生成String
@ToString
public class User {
private Long id;
private String name;
}
会生成:
public class User {
private Long id;
private String name;
public String toString() {
return "User(id=" + this.id + ", name=" + this.getName() + ")";
}
}
4、@EqualsAndHashCode
这个注解会重写类的 equals
和 hashcode
方法。
equals和hashcode默认是将所有的属性参加进去进行对比和生成hashcode,如果你想排除某些属性可以在注解中使用 exclude
进行排除: @EqualsAndHashCode(exclude = {"id","name"})
@EqualsAndHashCode()
public class User {
private Long id;
private String name;
}
会生成:
public class User {
private Long id;
private String name;
public User() {
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof User)) {
return false;
} else {
User other = (User)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$id = this.id;
Object other$id = other.id;
if (this$id == null) {
if (other$id != null) {
return false;
}
} else if (!this$id.equals(other$id)) {
return false;
}
Object this$name = this.name;
Object other$name = other.name;
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof User;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $id = this.id;
result = result * 59 + ($id == null ? 43 : $id.hashCode());
Object $name = this.name;
result = result * 59 + ($name == null ? 43 : $name.hashCode());
return result;
}
}
5、@Data
相当于同时使用了@ToString
、@EqualsAndHashCode
、@Getter
、@Setter
和@RequiredArgsConstrutor
这些注解,对于实体类pojo这些很好用。他有以下几点需要注意:
@RequiredArgsConstrutor
实际上是代替内部属性需要ioc容器装配自动装配,当然,我们并不建议使用这种方式装配,事实上,你可以当做这个注解没用。@EqualsAndHashCode
注解会重写equals和hashcode,他们都会使用所有的属性生成,如果你想排除某些字段的对比,请手动重写或者手动添加这个注解进行排除。- 他不生成构造方法
6、@Value
是 @Data
的不可变形式,相当于为属性添加final声明,@Getter
、@FieldDefaults(makeFinal = true,level = AccessLevel.PRIVATE)
@ AllArgsConstructor
、@ToString
、 @EqualsAndHashCod
,这对一些字典类,枚举类非常好用,他有以下几点需要注意:
@FieldDefaults(makeFinal = true,level = AccessLevel.PRIVATE)
注解是用来设置字段访问级别的,level = AccessLevel.PRIVATE
表示他的访问级别是私有的@EqualsAndHashCode
注解会重写equals和hashcode,他们都会使用所有的属性生成,如果你想排除某些字段的对比,请手动重写或者手动添加这个注解进行排除。- 他会生成一个全参构造
7、@Builder
建造者模式的最佳实践,当然最好是使用在复杂对象的构造上,类似与领域驱动编程的聚合根。
但你需要注意,他会集成了 @AllArgsConstructor
生成全参构造,如果你想使用这个类的无参构造,需要声明出来,手写或者加上 @NoArgsConstructor
。
@Builder
public class User {
private Long id;
private String name;
}
生成代码如下:
public class User {
private Long id;
private String name;
User(final Long id, final String name) {
this.id = id;
this.name = name;
}
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder {
private Long id;
private String name;
UserBuilder() {
}
public UserBuilder id(final Long id) {
this.id = id;
return this;
}
public UserBuilder name(final String name) {
this.name = name;
return this;
}
public User build() {
return new User(this.id, this.name);
}
public String toString() {
return "User.UserBuilder(id=" + this.id + ", name=" + this.name + ")";
}
}
}
如上代码所见,他会生成一个 UserBuilder
内部类对象。接着,我们就可以使用建造者模式构建这个对象了:
User user = User.builder()
.id(123L)
.name("王富贵")
.build();
8、@Accessors(chain = true)
不建议使用
这个注解的作用就是将set方法改造成为链式调用的方式,因此他必须配合 @Getter
或者带这个注解的聚合注解。
他本质就是生成的set方法的返回值改成本类,因此就可以实现这个效果。但请你注意,他改造了set方法,如果你依赖的别的框架比如mybatis需要原生的set方法的时候,就会因为set方法不存在而报错,因此我们不建议使用
用法如下:
@Setter
@Accessors(chain = true)
public class User {
private Long id;
private String name;
}
生成代码如下:
public class User {
private Long id;
private String name;
public User() {
}
// 实际上就是返回结果从void变成了本类
public User setId(final Long id) {
this.id = id;
return this;
}
// 实际上就是返回结果从void变成了本类
public User setName(final String name) {
this.name = name;
return this;
}
}
我们可以使用链式调用了:
User user = new User();
user.setId(123L)
.setName("王富贵");
1.2.2.2、业务代码注解
1、@SneakyThrows
@SneakyThrows
注解能够自动的检查并向上抛出异常。
阿里规约提到,出现异常最好当前代码捕获,如果当前代码不变捕获,一定要向上抛出。
因此,我不推荐这个注解,最好在当前代码就捕获异常,并打印error日志。
@SneakyThrows()
private void read() {
File file=new File("D:hello.txt");
InputStream is = new FileInputStream(file);
}
等价于
private void read() throws FileNotFoundException {
File file=new File("D:hello.txt");
InputStream is = new FileInputStream(file);
}
// 或者try,catch环绕
private void read(){
File file=new File("D:hello.txt");
try {
InputStream is = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
他内部 value 属性支持我们设置捕获抛出什么样的类型
@SneakyThrows(value = {FileNotFoundException.class, Exception.class})
2、@Slf4j
我们常常使用 @Log
注解下的,他会自动为我们注入日志门面 slf4j
的日志输出类。
@Slf4j
public class User {
public void testLog(){
log.info("日志类自动注入了");
}
}
他会自动注入 slf4j
的日志输出类
public class User {
// lombok的注解@Slf4j构建的
private static final Logger log = LoggerFactory.getLogger(User.class);
public User() {
}
public void testLog() {
log.info("日志类自动注入了");
}
}
1.2.3、注意
我们在使用lombok的时候,需要注意一下几点
- 有些企业leader强制不使用lombok
- 一定要规范属性名称
- 生成有参构造之后,无参构造就会消失,需要手动加上无参构造注解
- @Accessor(chain = true)方法会将生成的set方法返回值变成当前class对象
1.有些企业leader强制不使用lombok
想要使用lombok必须安装lombok插件,这种方式从某种意义上来说是强依赖性质的,其他所有的开发者必须在ide上面安装了这个插件才能完美运行代码。当然,2021年开始idea默认就已经安装了lombok插件了,还是建议使用如此方便的插件的。
2.一定要规范命名名称
我们在上面了解 @Getter
和 @Setter
注解的时候就提到了,如果你没有规范的命名,他生成的get和set方法的名称可能会出现命名问题,导致底层像mybatis等等框架无法正确注入属性值。
3.无参构造生成问题
当我们使用lombok的时候,使用无参构造的注解 @AllArgsConstructor
会使java默认的有参构造失效(实际上这是java特性,不是lombok的问题)。特别是一些聚合注解,如 @data
、 @Value
、 @Builder
等等,他们均包含 @AllArgsConstructor
注解,此时我们就必须手写无参构造或者添加无参构造的注解 @NoArgsConstructor
了。
4.@Accessor(chain = true)注解谨慎使用
这个注解虽然可以将类改造成链式调用,但他本质是改造了set方法,这会造成lombok的 @Getter
注解被他改造,但你还想使用原生的set方法就不存在了。假如你还使用了mybatis等等这框架,当他面通过反射调用set方法的时候就会因为调用不到而报错。