Lombok入门到原理

Lombok入门到原理

首先搞清楚lombok是什么东西,只是一个Java开发插件,平常在我们写代码的时候,会出现代码量很长,比如在POJO类中有get、set方法,在其他类中,还得写无参的方法甚至某些对应参数的构造方法等等,这种代码工作很重要是必要的但是很简单,代码显得臃肿浪费时间在这种工作上没有必要,那么现在就有这么一款工具帮我们去简化消除这种臃肿,去缩短我们打代码的时间来去做更多事情

优点:
1、提高编码效率;
2、使代码更简洁;
3、消除冗长代码;
4、避免修改字段名字时忘记修改方法名(例如构造方法)
5、提高下“逼格”
缺点:
代码可读性、可调性降低

IDE项目中使用:
只需要动动小手打开IDEA,打开File->Setting->Plugins搜索Lombok,然后install就可以啦
当然,要想在项目中使用Lombok,必须在pom.xml中添加依赖

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.16.8</version>
</dependency>

lombok相关注解及作用

@Getter 和 @Setter:作用在类上或成员变量上,生成对应的 setter 和 getter 方法
(1)如果用在属性上:则只为该属性提供 setter 和 getter 方法
(2)如果是用在类上:则为这个类所有属性供 setter 和 getter方法

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor:作用于类上,用于生成构造函数。有staticName、access等属性。
staticName属性一旦设定,将采用静态方法的方式生成实例,access属性可以限定访问权限。

@NoArgsConstructor:生成无参构造器
​ (1)当类中有 final 字段没有被初始化时,编译器会报错如果设置@NoArgsConstructor(force = true),参数意味着所有final字段被初始化为 0 / false / null;
​ (2)对于具有约束的字段(例如 @NonNull 字段),不会生成检查或分配。
@RequiredArgsConstructor:生成包含final和@NonNull注解的成员变量的构造器
​ (1)参数只能是以final修饰的未经初始化的字段或者是以@NonNull 注解的未经初始化的字段;
​ (2)该注解还可以用@RequiredArgsConstructor(staticName=“methodName”)的形式生成一个指定名称的静态方法,返回一个调用相应的构造方法产生的对 象
@AllArgsConstructor:为类中的每个字段生成一个带有1个参数的构造函数
​ (1)对标记为的字段@NonNull对进行空参校验
​ (2)默认生成的方法是 public 的,如果要修改方法修饰符可以设置 AccessLevel 的值。

@ToString :作用在类上,生成对应的 toString 方法
(1)默认情况下,它会按顺序(以逗号分隔)打印这个类名称以及每个字段。
(2)可以这样设置不包含哪些字段:@ToString(exclude = “id”) 或者 @ToString(exclude = {“id”,“name”})

@EqualsAndHashCode :作用在类上,生成对应的 equals 和 hashCode 方法

@Data :复合注解,用在类上,使用后会生成:默认的无参构造函数、所有属性的 getter、所有非 final 属性的 setter 方法,并重写 toString、equals、hashcode 方法

@Value :@Value 注解和 @Data 类似,区别在于它会把所有成员变量默认定义为 private final 修饰,并且不会生成 set() 方法

@NonNull :注解在属性上,标识属性是不能为空,为空则抛出异常。换句话说就是进行空值检查

@Cleanup :用于关闭并释放资源,可以用在 IO 流上;

例子:
使用@Cleanup注解:

public class CleanupExample {
   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[10000];
    	while (true) {
       		int r = in.read(b);
       		if (r == -1) break;
        	out.write(b, 0, r);
    	}
   }
}

相当于:

public class CleanupExample {
  	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();
      		}
   		}
  	}
}

@Log :该注解用在类上,可以省去从日志工厂生成日志对象这一步,直接进行日志记录,具体注解根据日志工具的不同而不同
不同的日志注解:

@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.XLoggerFactory.getXLogger(LogExample.class);

例子:

// 使用注解
@Log
public class LogExample {
	public static void main(String... args) {
    	log.error("Something's wrong here");
	}
}

// 不使用注解
public class LogExample {
   	private static final java.util.logging.Logger log =
        java.util.logging.Logger.getLogger(LogExample.class.getName());

	public static void main(String... args) {
    	log.error("Something's wrong here");
	}
}

@NonNull:主要作用于成员变量和参数中,标识不能为空,否则抛出空指针异常。
例子:
使用@NonNull注解:

import lombok.NonNull; 
public class NonNullExample {
	private String name;
	
    public NonNullExample(@NonNull User user) {
    	this.name = user.getName();
	}
}

相当于:

public class NonNullExample {
	private String name;

	public NonNullExample(User user) {
    	if (user == null) {
        	throw new NullPointerException("user");
    	}
   		this.name = user.getName();
	}
}

测试用例:

User user = null;
try {
	NonNullExample example = new NonNullExample(user);
}catch (NullPointerException ex) {
	return ex.toString();
}		

@Buildler :现在比较推崇的一种构建值对象的方式。该描述符用于将类改造成 builder(建造者)模式,用在类、方法或者构造函数上
例子:
使用后:

@Builder
public class BuilderDemo {
	private final String firstname;
	private final String lastname;
	private final String email;
}

相当于:

package com.semlinker.lombok;

public class BuilderDemo {
	private final String firstname;
	private final String lastname;
   	private final String email;

	BuilderDemo(String firstname, String lastname, String email) {
   		this.firstname = firstname;
   		this.lastname = lastname;
    	this.email = email;
	}

	public static BuilderDemo.BuilderDemoBuilder builder() {
    	return new BuilderDemo.BuilderDemoBuilder();
	}

	public static class BuilderDemoBuilder {   
        private String firstname;
    	private String lastname;
   		private String email;

    	BuilderDemoBuilder() {
   		}

    	public BuilderDemo.BuilderDemoBuilder firstname(String firstname) {
       		this.firstname = firstname;
   			return this;
   		}

   		public BuilderDemo.BuilderDemoBuilder lastname(String lastname) {
        	this.lastname = lastname;
        	return this;
    	}

    	public BuilderDemo.BuilderDemoBuilder email(String email) {
       		this.email = email;
        	return this;
    	}

    	public BuilderDemo build() {
        	return new BuilderDemo(this.firstname, this.lastname, this.email);
    	}

   		public String toString() {
        	return "BuilderDemo.BuilderDemoBuilder(firstname=" + this.firstname + ", lastname=" + this.lastname + ", email=" + 				this.email + ")";
    	}
	}
}	

测试:

BuilderDemo be = BuilderDemo.builder()
    .firstname("张")    		
    .lastage("三")    		
    .email("ABC.com")    		
    .build(); 		
return be.toString();

@SneakyThrows
(1)该注解用在方法上,可以将方法中的代码用 try-catch 语句包裹起来,捕获异常并在 catch 中用 Lombok.sneakyThrow(e) 把异常抛出。
(2)也可以使用 @SneakyThrows(Exception.class) 的形式指定抛出哪种异常。
使用:

// 使用注解
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();
	}
			
}

// 不使用注解
public class SneakyThrows implements Runnable {
	public String utf8ToString(byte[] bytes) {
    	try{
        	return new String(bytes, "UTF-8");
    	}catch(UnsupportedEncodingException uee){
        	throw Lombok.sneakyThrow(uee);
   		}
	}

	public void run() {
    	try{
        	throw new Throwable();
    			}catch(Throwable t){
        			throw Lombok.sneakyThrow(t);
    			}
			}
		}

@With :在类的字段上应用 @With 注解之后,将会自动生成一个 withFieldName(newValue) 的方法,该方法会基于 newValue 调用相应构造函数,创建一个当前类对应的实例。
例子:

//使用注解
public class WithDemo {
	@With(AccessLevel.PROTECTED)
	@NonNull
	private final String name;
	@With
	private final int age;

	public WithDemo(String name, int age) {
    	if (name == null) throw new NullPointerException();
   			this.name = name;
    		this.age = age;
		}
	}
}
//不使用注解
public class WithDemo {
	@NonNull
	private final String name;
    private final int age;

	public WithDemo(String name, int age) {
	   	if (name == null) {
       		throw new NullPointerException();
    	} else {
        	this.name = name;
       		this.age = age;
    	}
	}

	protected WithDemo withName(@NonNull String name) {
    	if (name == null) {
        	throw new NullPointerException("name is marked non-null but is null");
   		} else {
        	return this.name == name ? this : new WithDemo(name, this.age);
    	}
	}

	public WithDemo withAge(int age) {
    	return this.age == age ? this : new WithDemo(this.name, age);
	}
}

其他:
val 用在局部变量前面,相当于将变量声明为 final,此外 Lombok 在编译时还会自动进行类型推断
例子:

//使用val
public class ValExample {

  	public String example() {
    	val example = new ArrayList<String>();
    	example.add("Hello, World!");
    	val foo = example.get(0);
    	return foo.toLowerCase();
  	}

  	public void example2() {
    	val map = new HashMap<Integer, String>();
    	map.put(0, "zero");
    	map.put(5, "five");
    	for (val entry : map.entrySet()) {
      		System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    	}
  	}
}
//不使用val
public class ValExample {
    
  	public String example() {
    	final ArrayList<String> example = new ArrayList<String>();
    	example.add("Hello, World!");
    	final String foo = example.get(0);
    	return foo.toLowerCase();
  	}

  	public void example2() {
    	final HashMap<Integer, String> map = new HashMap<Integer, String>();
    	map.put(0, "zero");
    	map.put(5, "five");
    	for (final Map.Entry<Integer, String> entry : map.entrySet()) {
     		System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    	}
  	}
}

项目中常用的注解包括有@Data @Getter @Setter @Slfj4 @AllArgsConstructor @NonNull @ToString @@EqualsAndHashCode

常见问题

1、肯定有人要问为什么maven中加入lombok依赖后,还需要安装插件?

因为lombok的引入使得java文件使用javac编译成字节码文件中包含get set函数,但是源代码中找不到定义,IDE会认为这是错误,因此需要安装一个lombok的插件

2、在Lombok使用的过程中,只需要添加相应的注解,无需再为此写任何代码。但是自动生成的代码到底是如何产生的呢?

首先说一说抽象语法树AST,抽象语法树是一种描述程序代码语法结构的树形表示方式。
先讲讲java源文件编译成.class文件,这一步大致可以分为3个过程:
1、把所有的源文件解析成语法树,输入到编译器的符号表;(Parse and Enter)
(1)源代码经过词法分析,通过Scanner将源码的字符流解析成Token流,(输入:源代码;输出:token)
(2)再进行语法分析,根据token流,利用TreeMaker,以JCTree的子类作为语法节点来构建抽象语法树,(输入:token;输出:AST)
(3)将java类中的符号输入到符号表中,符号表用来存储源文件中的token数据信息,基本上跟标识符有关(输入:AST;输出:无)
符号表的作用(在语义分析的时候用到):格式:例如 int a = 1 --> <a,1,int>
假如你先遍历到了int a 这个节点, 接着又遍历到了一个表达式a = 4这个节点, 你需要检查变量a有没有声明啊, 变量a和4的类型批不匹配呢? 这时你如果没有保存变量a的信息, 那么你怎么检查? 所以就需要符号表来保存这些信息了.
2、注解处理器的注解处理过程(JSR-269规范JSR-269);(Annotation Processing)
在JDK 1.6中实现了JSR-269规范JSR-269:Pluggable Annotations Processing API(插入式注解处理API)。提供了一组插入式注解处理器的标准API在编译期间对注解进行处理;在注解处理期间,我们可以获取到所有的抽象语法树,可以对抽象语法树进行增删改查;语法树被修改过之后,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止。
3、分析语法树并生成字节码。(Analyse and Generate)
(1)语义分析:对结构正确的源程序进行上下文有关性质的审查,过程分为标注检查和数据及控制流分析两个步骤
标注检查(根据符号表)
检查语义合法性、进行逻辑判断,如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等;
数据及控制流分析
在Javac的源码中,数据及控制流分析的入口是flow(),由com.sun.tools.javac.comp.Flow类来完成,作用是对程序上下文逻辑更进一步的验证,检查局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题
(2)解语法糖:Java中最常用的语法糖主要是的泛型擦除、变长参数、自动装箱/拆箱、条件编译等,解语法糖就是还原回简单的基础语法结构
(3)生成字节码

语法树的每一个节点都代表着程序代码中的一个语法结构,如包、类型、修饰符、运算符、接口、返回值都可以是一个语法结构。
语法树解析详情见:http://www.java8.com/thread-51-1-1.html

lombok的原理也就是在代码编译的时候根据相应的注解动态的去修改AST这个树,为他增加新的节点(也就是对于的代码比如get、set)。所以如果想动态的修改代码,或者直接生成java代码的话,AST是一个不错的选择。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值