LomboK LomboK介绍和配置 什么是LomboK Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。
官网: https://www.projectlombok.org/
Lombok的作用 通过添加注解 的方式,Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。
例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误,使代码看起来更简洁些。
Lombok的配置
1 2 3 4 5 6 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.8</version > <scope > provided</scope > </dependency >
小结
Lombox: 就是一个工具, 简化java代码开发
Lombok环境
Lombok注解 @Data @Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
1 2 3 4 5 6 7 8 9 10 11 12 @Data public class User implements Serializable { private Integer id; private String username; private String password; private String address; private String nickname; private String gender; private String email; private String status; }
@Getter/@Setter 如果觉得@Data太过残暴不够精细,可以使用@Getter/@Setter注解,此注解在属性上,可以为相应的属性自动生成Getter/Setter方法.
1 2 3 4 5 6 7 8 9 10 11 12 public class User implements Serializable { @Setter @Getter private Integer id; private String username; private String password; private String address; private String nickname; private String gender; private String email; private String status; }
如果想要自己显示的设置set方法(加一些判断逻辑什么的,也是可以的)。
lombok不会再生成响应set方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /** * Page 是分页的模型对象 * @param <T> 是具体的模块的 javaBean 类 */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class Page<T> { public static final Integer PAGE_SIZE_DEFAULT = 4; // 每页默认显示条数 private Integer pageNo; // 当前页码 @Builder.Default private Integer pageSize = PAGE_SIZE_DEFAULT; // 当前页显示数量 private Integer pageTotal; // 总页码数 private Integer pageTotalCount; // 总记录数 private List<T> items; // 当前页数据 public void setPageNo(Integer pageNo) { // 边界值有效检查 this.pageNo = pageNo < 1 ? 1 : pageNo > pageTotal ? pageTotal : pageNo; } }
看图标,暗的
@ToString 类使用@ToString注解,Lombok会生成一个toString()方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。 通过exclude属性指定忽略字段不输出,
1 2 3 4 5 6 7 8 9 10 11 12 @ToString(exclude = {"id"}) public class User implements Serializable{ private Integer id; private String username; private String password; private String address; private String nickname; private String gender; private String email; private String status; }
@xxxConstructor
@NoArgsConstructor: 无参构造器
1 2 3 4 5 6 7 8 9 10 11 @NoArgsConstructor public class User implements Serializable { private Integer id; private String username; private String password; private String address; private String nickname; private String gender; private String email; private String status; }
@AllArgsConstructor: 全参构造器
1 2 3 4 5 6 7 8 9 10 11 @AllArgsConstructor public class User implements Serializable { private Integer id; private String username; private String password; private String address; private String nickname; private String gender; private String email; private String status; }
这三个注解都是用在类上的,第一个和第三个都很好理解,就是为该类产生无参的构造方法和包含所有参数的构造方法,第二个注解则使用类中所有带有@NonNull注解的或者带有final修饰的成员变量生成对应的构造方法,当然,和前面几个注解一样,成员变量都是非静态 的,另外,如果类中含有final修饰的成员变量,是无法使用@NoArgsConstructor注解的。
三个注解都可以指定生成的构造方法的访问权限,同时,第二个注解还可以用@RequiredArgsConstructor(staticName=”methodName”)的形式生成一个指定名称的静态方法,返回一个调用相应的构造方法产生的对象,下面来看一个生动鲜活的例子:
1 2 3 4 5 6 7 8 9 10 @RequiredArgsConstructor(staticName = "sunsfan") @AllArgsConstructor(access = AccessLevel.PROTECTED) @NoArgsConstructor public class Shape { private int x; @NonNull private double y; @NonNull private String name; }
实际效果相当于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Shape { private int x; private double y; private String name; public Shape () { } protected Shape (int x,double y,String name) { this .x = x; this .y = y; this .name = name; } public Shape (double y,String name) { this .y = y; this .name = name; } public static Shape sunsfan (double y,String name) { return new Shape (y,name); } }
@builder 案例:在派生类(子类)中使用@Builder
父类(用@AllArgsConstructor注释)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Data @AllArgsConstructor public class BaseEntity implements Serializable { private Long id; private Date created; private Date updated; }
子类(提供完整的构造方法,并添加@Builder注解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Data public class TbUser extends BaseEntity { private String username; private String password; private String phone; private String email; @Builder public TbUser (Long id, Date created, Date updated, String username, String password, String phone, String email) { super (id, created, updated); this .username = username; this .password = password; this .phone = phone; this .email = email; } }
泛型实体使用Lombok builder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * Page 是分页的模型对象 * @param <T> 是具体的模块的 javaBean 类 */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class Page<T> { public static final Integer PAGE_SIZE_DEFAULT = 4; // 每页默认显示条数 private Integer pageNo; // 当前页码 @Builder.Default private Integer pageSize = PAGE_SIZE_DEFAULT; // 当前页显示数量 private Integer pageTotal; // 总页码数 private Integer pageTotalCount; // 总记录数 private List<T> items; // 当前页数据 }
使用:
1 2 3 4 5 6 7 Page.<Book>builder() .pageNo(pageNo) // 设置当前页码 .pageSize(pageSize) // 设置每页显示条数 .pageTotalCount(pageTotalCount) // 设置总记录数 .pageTotal(pageTotal) // 设置总页码数 .items(items) // 设置当前页数据 .build();
@Builder.Default 实体
1 2 3 4 5 6 7 8 9 10 11 12 13 @Data @NoArgsConstructor @AllArgsConstructor @Builder // 图书类 public class Book implements Serializable { private int id; // id private String name; // 名称 private BigDecimal price; // 价格 private String author; // 作者 private Integer sales; // 销量 private Integer stock; // 库存 private String img_path = "static/img/default.jpg"; // 图书默认地址
使用
1 2 3 4 5 6 @Test public void addBook() { Book book = Book.builder().name("陈志杰自传").author("陈志杰").price(new BigDecimal("100.00")).sales(10000).stock(0).img_path(null).build(); System.out.println("book = " + book); bookDao.addBook(book); }
默认值这个时候不生效,会被null覆盖。
原因:
@Builder将忽略类中的默认值,如果想要保留默认值那么添加@Builder.Default注解或者声明为final
声明final并不合适的,添加@Builder.Default是解决办法。
一开始我是手动写判断,笨拙
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Data @NoArgsConstructor //@AllArgsConstructor @Builder // 图书类 public class Book implements Serializable { private int id; // id private String name; // 名称 private BigDecimal price; // 价格 private String author; // 作者 private Integer sales; // 销量 private Integer stock; // 库存 private String img_path = "static/img/default.jpg"; // 图书默认地址 public Book(int id, String name, BigDecimal price, String author, Integer sales, Integer stock, String img_path) { this.id = id; this.name = name; this.price = price; this.author = author; this.sales = sales; this.stock = stock; // 判断不为空才赋值 if (StringUtils.isNotBlank(img_path)) { this.img_path = img_path; } }
这样是有效的。
接下来试下@Builder.Default
怎么用呢?
这个时候,就不能给它值了
1 2 3 4 5 6 @Test public void addBook() { Book book = Book.builder().name("陈志杰自传").author("陈志杰").price(new BigDecimal("100.00")).sales(10000).stock(0).build(); // 不build imgPath System.out.println("book = " + book); bookDao.addBook(book); }
插入成功!
@NonNull 这个注解可以用在成员方法或者构造方法的参数前面,会自动产生一个关于此参数的非空检查,如果参数为空,则抛出一个空指针异常,举个例子来看看:
1 2 3 4 public String getName (@NonNull Person p) { return p.getName(); }
实际效果相当于:
1 2 3 4 5 6 public String getName (@NonNull Person p) { if (p==null ){ throw new NullPointerException ("person" ); } return p.getName(); }
用在构造方法的参数上效果类似,就不再举例子了。
@Cleanup 这个注解用在变量前面,可以保证此变量代表的资源会被自动关闭,默认是调用资源的close()方法,如果该资源有其它关闭方法,可使用@Cleanup(“methodName”)来指定要调用的方法,就用输入输出流来举个例子吧:
1 2 3 4 5 6 7 8 9 10 public static void main (String[] args) throws IOException { @Cleanup InputStream in = new FileInputStream (args[0 ]); @Cleanup OutputStream out = new FileOutputStream (args[1 ]); byte [] b = new byte [1024 ]; while (true ) { int r = in.read(b); if (r == -1 ) break ; out.write(b, 0 , r); } }
实际效果相当于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void main (String[] args) throws IOException { InputStream in = new FileInputStream (args[0 ]); try { OutputStream out = new FileOutputStream (args[1 ]); try { byte [] b = new byte [10000 ]; while (true ) { int r = in.read(b); if (r == -1 ) break ; out.write(b, 0 , r); } } finally { if (out != null ) { out.close(); } } } finally { if (in != null ) { in.close(); } } }
是不是简化了很多。
@EqualsAndHashCode 这两个注解也比较好理解,就是生成toString,equals和hashcode方法,同时后者还会生成一个canEqual方法,用于判断某个对象是否是当前类的实例,生成方法时只会使用类中的非静态 和非transient 成员变量,这些都比较好理解,就不举例子了。
当然,这两个注解也可以添加限制条件,例如用@ToString(exclude={“param1”,“param2”})来排除param1和param2两个成员变量,或者用@ToString(of={“param1”,“param2”})来指定使用param1和param2两个成员变量,@EqualsAndHashCode注解也有同样的用法。
@Value @Data注解综合了3,4,5和6里面的@RequiredArgsConstructor注解,其中@RequiredArgsConstructor使用了类中的带有@NonNull注解的或者final修饰的成员变量,它可以使用@Data(staticConstructor=”methodName”)来生成一个静态方法,返回一个调用相应的构造方法产生的对象。这个例子就也省略了吧…
@Value注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法。
@SneakyThrows 这个注解用在方法上,可以将方法中的代码用try-catch语句包裹起来,捕获异常并在catch中用Lombok.sneakyThrow(e)把异常抛出,可以使用@SneakyThrows(Exception.class)的形式指定抛出哪种异常,很简单的注解,直接看个例子:
1 2 3 4 5 6 7 8 9 10 11 public class SneakyThrows implements Runnable { @SneakyThrows(UnsupportedEncodingException.class) public String utf8ToString(byte[] bytes) { return new String(bytes, "UTF-8"); } @SneakyThrows public void run() { throw new Throwable(); } }
实际效果相当于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class SneakyThrows implements Runnable { @SneakyThrows(UnsupportedEncodingException.class) public String utf8ToString(byte[] bytes) { try{ return new String(bytes, "UTF-8"); }catch(UnsupportedEncodingException uee){ throw Lombok.sneakyThrow(uee); } } @SneakyThrows public void run() { try{ throw new Throwable(); }catch(Throwable t){ throw Lombok.sneakyThrow(t); } } }
@Synchronized 这个注解用在类方法或者实例方法上,效果和synchronized关键字相同,区别在于锁对象不同,对于类方法和实例方法,synchronized关键字的锁对象分别是类的class对象和this对象,而@Synchronized得锁对象分别是私有静态final对象LOCK和私有final对象LOCK和私有final对象lock,当然,也可以自己指定锁对象,例子也很简单,往下看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Synchronized { private final Object readLock = new Object(); @Synchronized public static void hello() { System.out.println("world"); } @Synchronized public int answerToLife() { return 42; } @Synchronized("readLock") public void foo() { System.out.println("bar"); } }
实际效果相当于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Synchronized { private static final Object $LOCK = new Object[0]; private final Object $lock = new Object[0]; private final Object readLock = new Object(); public static void hello() { synchronized($LOCK) { System.out.println("world"); } } public int answerToLife() { synchronized($lock) { return 42; } } public void foo() { synchronized(readLock) { System.out.println("bar"); } } }
@Log 这个注解用在类上,可以省去从日志工厂生成日志对象这一步,直接进行日志记录,具体注解根据日志工具的不同而不同,同时,可以在注解中使用topic来指定生成log对象时的类名。不同的日志注解总结如下(上面是注解,下面是实际作用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @CommonsLog private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);@JBossLog private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);@Log private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());@Log4j private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);@Log4j2 private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);@Slf4j private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);@XSlf4j private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
关于lombok的注解先写到这里,当然,还有其他一些注解需要大家自己去摸索,同时lombok一直在扩展,将来肯定会加入更多的注解元素,拭目以待了。
如果你要用slf4j日志:
1 2 3 4 5 6 7 8 <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-log4j12</artifactId > <version > 1.7.25</version > </dependency > 注意导这个包,它会依赖slf4j-api和log4j。这样才是完整的。如果你只导入了slf4j-api,会报错: SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder
这样就可以使用@Slf4j了,否则点不出来info的
1 log.info("用户名或密码,登录失败");
@log;@log4j;@slf4j;....
等,好几个,要用哪个就导入哪个的jar包,然后加对应的注解即可。
如果要打印info信息,再控制台,或者输出到日志文件,还要配置
log4j.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #log4j.rootLogger 配置的是大于等于当前级别的日志信息的输出 #log4j.rootLogger 用法:(注意appenderName可以是一个或多个) #log4j.rootLogger = 日志级别,appenderName1,appenderName2,.... #log4j.appender.appenderName1定义的是日志的输出方式,有两种:一种是命令行输出或者叫控制台输出,另一种是文件方式保存 # 1)控制台输出则应该配置为org.apache.log4j.PatternLayout # 2)文本方式保存应该配置为org.apache.log4j.DailyRollingFileAppender # 3)也可以自定义 Appender类 #log4j.appender.appenderName1.layout.ConversionPattern 定义的是日志内容格式 #log4j.appender.appenderName1.file 定义了该日志文件的文件名称 #log4j.appender.appenderName1.DatePattern 定义了日志文件重新生成的时间间隔,如果设置到天,则每天重新生成一个新的日志文件。 # 旧的日志文件则以新的文件名保存,文件名称 = log4j.appender.appenderName1.file + log4j.appender.appenderName1.DatePattern #log4j.appender.appenderName1.Encoding 定义了编码格式 log4j.rootLogger = info,stdout,file log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%C{1}:%L] - %m%n log4j.appender.file = org.apache.log4j.DailyRollingFileAppender log4j.appender.file.file=D:/logs/book/info(+).log log4j.appender.file.DatePattern= '.'yyyy-MM-dd log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%C{1}:%L] - %m%n log4j.appender.file.Encoding=UTF-8
注意,如果配置文件,编码格式后面有空格,会报警告
log4j:WARN Error initializing output writer.
log4j:WARN Unsupported encoding?
如果你报错了:
1 log4j:WARN No appenders could be found for logger (com.nbchen.mybatis.d1.jdbc.JdbcTest).
因为你少加了log4j.properties配置文件
小结 注解
@Data
用在类上面的 , 生成set,get, toString, hashCode,canEqual、toString方法
@Getter
用在字段, 生成get方法
@Setter
@ToString
@xxxConstructor
用在类上面的 生成构造方法 (只能生成无参和全参的构造方法)
优缺点 优点:
能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率
让代码变得简洁,不用过多的去关注相应的方法
属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
缺点:
不支持多种参数构造器的重载