很多java程序员在写代码时,经常抱怨java语言写起来的臃肿与繁复,在大家都依照前人制定的代码规范进行编写的今天,大家在这些条条框框的限制下,都需要编写很多“麻烦”的代码。比如下面的代码:
/**
* 员工类
**/
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 工号
*/
private String number;
/**
* 地址
*/
private String address;
/**
* 电话号码
*/
private String phoneNumber;
/**
* 所属部门
*/
private Department department;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '/'' +
", age=" + age +
", number='" + number + '/'' +
", address='" + address + '/'' +
", phoneNumber='" + phoneNumber + '/'' +
", department=" + department +
'}';
}
}
这是一个简单的实体类,用以表示员工的基本信息,封装其相关数据。很简单的代码,6个属性包括相应的getter/setter方法以及重写的toString方法,这个类就已经有90行了,要知道,目前我们的系统中充斥着各种各样的实体,每一个实体都要有这一系列的方法声明,写一个实体还好,连续十几个呢?编写这种千篇一律的代码,不仅会降低我们的开发积极性,更会因为心理上的排斥导致编写上的疏忽,带来不必要的bug和时间耗费。
有些人会说,直接用ide自带的工具,就可以一键生成这些东西了,那么,修改呢?删了再一键生成吗?修改十几个类呢?
如果你也在为上面所说的“麻烦”的代码所困扰,那么,神奇的工具出现了--lombok,它可以自主生成上述“麻烦的”、千篇一律的代码,提高我们的开发效率、降低我们的时间耗费、提高代码正确率,更能让我们码代码时能专注于核心实现,而不是为这些边边角角所困扰。那么接下来,让我们先看一下这个神奇的工具带来的变化,还是以上面那个User类为例,引入lombok后,它变成了这样:
import lombok.Data;
/**
* 员工类
**/
@Data
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 工号
*/
private String number;
/**
* 地址
*/
private String address;
/**
* 电话号码
*/
private String phoneNumber;
/**
* 所属部门
*/
private Department department;
}
有些人可能会惊讶,为啥加一个注解,就可以了呢,那些getter/setter方法去哪里了?我们反编译一下看看:
package com.bj58.wuxian.app.homepage.controllers;
public class Employee
{
private String name;
private int age;
private String number;
private String address;
private String phoneNumber;
private Department department;
public String getName()
{
return this.name;
}
public int getAge() {
return this.age;
}
public String getNumber() {
return this.number;
}
public String getAddress() {
return this.address;
}
public String getPhoneNumber() {
return this.phoneNumber;
}
public Department getDepartment() {
return this.department;
}
public void setName(String name)
{
this.name = name; }
public void setAge(int age) { this.age = age; }
public void setNumber(String number) { this.number = number; }
public void setAddress(String address) { this.address = address; }
public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
public void setDepartment(Department department) { this.department = department; }
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Employee)) return false; Employee other = (Employee)o; if (!other.canEqual(this)) return false; Object this$name = getName(); Object other$name = other.getName(); if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; if (getAge() != other.getAge()) return false; Object this$number = getNumber(); Object other$number = other.getNumber(); if (this$number == null ? other$number != null : !this$number.equals(other$number)) return false; Object this$address = getAddress(); Object other$address = other.getAddress(); if (this$address == null ? other$address != null : !this$address.equals(other$address)) return false; Object this$phoneNumber = getPhoneNumber(); Object other$phoneNumber = other.getPhoneNumber(); if (this$phoneNumber == null ? other$phoneNumber != null : !this$phoneNumber.equals(other$phoneNumber)) return false; Object this$department = getDepartment(); Object other$department = other.getDepartment(); return this$department == null ? other$department == null : this$department.equals(other$department); }
protected boolean canEqual(Object other) { return other instanceof Employee; }
public int hashCode() { int PRIME = 59; int result = 1; Object $name = getName(); result = result * 59 + ($name == null ? 43 : $name.hashCode()); result = result * 59 + getAge(); Object $number = getNumber(); result = result * 59 + ($number == null ? 43 : $number.hashCode()); Object $address = getAddress(); result = result * 59 + ($address == null ? 43 : $address.hashCode()); Object $phoneNumber = getPhoneNumber(); result = result * 59 + ($phoneNumber == null ? 43 : $phoneNumber.hashCode()); Object $department = getDepartment(); return result * 59 + ($department == null ? 43 : $department.hashCode()); }
public String toString() { return "Employee(name=" + getName() + ", age=" + getAge() + ", number=" + getNumber() + ", address=" + getAddress() + ", phoneNumber=" + getPhoneNumber() + ", department=" + getDepartment() + ")";
}
}
是不是很神奇?也就是说,一个lombok@Data注解,为我们自动生成了所有属性的getter/setter方法、toString方法、equals、hashcode方法。那么除了这个以外,lombok还能做什么呢?lombok的工作原理是什么呢?使用lombok有什么坑呢?带着这三个疑问,接下来我们全面的看一看这个神奇的小工具-lombok。
lombok官网的介绍是这样的:
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
机翻一下:
lombok项目是一个java类库,它可以自动嵌入到你的ide中,为你的java代码添加趣味。使用了lombok,再也不用写任何的getter或者equals方法了,通过一个注解你的类就会拥有全特性的builder,它可以自动生成你的日志变量,还有更多。
上面的介绍已经说得很明白了,lombok是一个通过注解可以帮助我们简化代码的类库。让我们看看lombok都能帮我们简化哪些代码:
@Getter/@Setter:该注解自动生成所有属性的get/set方法,这一组注解有一个属性是AccessLevel,通过这个可以指定get/set方法的访问级别
@NonNull:被该注解标识的参数、成员等,会赋予null快速失败的能力,即当传入该属性的值为null时,会抛出NullPointerException异常,使用代码如下:
public String test(@NonNull String param) {
return param;
}
反编译后的代码:
public String test(@NonNull String param)
{
if (param == null) throw new NullPointerException("param is marked @NonNull but is null");
return param;
}
@toString:该注解可以为类自动覆盖toString方法,其默认会把所有非静态域都涵盖在方法中。 其中,includeFieldNames属性默认为true,toString方法会打印属性名称,如果不需要,可设置为false; callsuper属性用于控制是否调用父类的toString方法; exclude属性是一个String数组,可以将不需要在toString中打印的属性置于其中; of属性可以显示指定哪些属性需要打印; doNotUserGetters用于确定在toString方法中是否调用属性的get方法; onlyExplicitlyIncluded属性用于确定是否仅打印被@ToString.Include注解的属性,代码如下:
/**
* 员工类
**/
@ToString
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 工号
*/
private String number;
/**
* 地址
*/
private String address;
/**
* 电话号码
*/
private String phoneNumber;
/**
* 所属部门
*/
private Department department;
}
反编译后如下:
public class Employee
{
private String name;
private int age;
private String number;
private String address;
private String phoneNumber;
private Department department;
public String toString()
{
return "Employee(name=" + this.name + ", age=" + this.age + ", number=" + this.number + ", address=" + this.address + ", phoneNumber=" + this.phoneNumber + ", department=" + this.department + ")";
}
}
@EqualsAndHashCode:该注解可以为类自动生成hashcode和equals方法。 该注解的属性与@ToString注解属性大致相同,功能上也基本相同,上代码:
/**
* 员工类
**/
@EqualsAndHashCode
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 工号
*/
private String number;
/**
* 地址
*/
private String address;
/**
* 电话号码
*/
private String phoneNumber;
/**
* 所属部门
*/
private Department department;
}
public class Employee
{
private String name;
private int age;
private String number;
private String address;
private String phoneNumber;
private Department department;
public boolean equals(Object o)
{
if (o == this) return true; if (!(o instanceof Employee)) return false; Employee other = (Employee)o; if (!other.canEqual(this)) return false; Object this$name = this.name; Object other$name = other.name; if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; if (this.age != other.age) return false; Object this$number = this.number; Object other$number = other.number; if (this$number == null ? other$number != null : !this$number.equals(other$number)) return false; Object this$address = this.address; Object other$address = other.address; if (this$address == null ? other$address != null : !this$address.equals(other$address)) return false; Object this$phoneNumber = this.phoneNumber; Object other$phoneNumber = other.phoneNumber; if (this$phoneNumber == null ? other$phoneNumber != null : !this$phoneNumber.equals(other$phoneNumber)) return false; Object this$department = this.department; Object other$department = other.department; return this$department == null ? other$department == null : this$department.equals(other$department); }
protected boolean canEqual(Object other) { return other instanceof Employee; }
public int hashCode() { int PRIME = 59; int result = 1; Object $name = this.name; result = result * 59 + ($name == null ? 43 : $name.hashCode()); result = result * 59 + this.age; Object $number = this.number; result = result * 59 + ($number == null ? 43 : $number.hashCode()); Object $address = this.address; result = result * 59 + ($address == null ? 43 : $address.hashCode()); Object $phoneNumber = this.phoneNumber; result = result * 59 + ($phoneNumber == null ? 43 : $phoneNumber.hashCode()); Object $department = this.department; return result * 59 + ($department == null ? 43 : $department.hashCode());
}
}
@AllArgsConstructor:该注解为被标注的类提供一个包含所有成员变量的构造方法,其中access属性用于标识该构造函数的访问级别;staticName属性可以将我们的构造方法私有,并额外创建一个静态工厂方法,该属性的值用于指定这个静态工厂方法的方法名,代码如下:
/**
* 员工类
**/
@AllArgsConstructor(staticName = "staticFactory")// 静态工厂方法名为staticFactory
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 工号
*/
private String number;
/**
* 地址
*/
private String address;
/**
* 电话号码
*/
private String phoneNumber;
/**
* 所属部门
*/
private Department department;
}
反编译后:
public class Employee
{
private String name;
private int age;
private String number;
private String address;
private String phoneNumber;
private Department department;
private Employee(String name, int age, String number, String address, String phoneNumber, Department department)
{
this.name = name; this.age = age; this.number = number; this.address = address; this.phoneNumber = phoneNumber; this.department = department; }
public static Employee staticFactory(String name, int age, String number, String address, String phoneNumber, Department department) { return new Employee(name, age, number, address, phoneNumber, department);
}
}
@Builder:该注解可以为我们自动生成基于建造者模式的类构造器,我们可以通过builderClassName来指定建造者模式内部类的类名,通过builderMethodName来指定创建建造者实例的方法名,通过buildMethodName来指定创建被注解的类的实例的方法名,通过toBuilder属性可以指定是否提供一个toBuilder方法,该方法可以允许我们可以通过建造者模式修改实体;当然了,我们可以通过accessLevel属性来指定方法的访问级别,代码如下:
/**
* 员工类
**/
@Builder(builderClassName = "testClass", buildMethodName = "testMethod", builderMethodName = "testBuilderMethod", toBuilder = true)
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 工号
*/
private String number;
/**
* 地址
*/
private String address;
/**
* 电话号码
*/
private String phoneNumber;
/**
* 所属部门
*/
private Department department;
}
反编译:
public class Employee
{
private String name;
private int age;
private String number;
private String address;
private String phoneNumber;
private Department department;
Employee(String name, int age, String number, String address, String phoneNumber, Department department)
{
this.name = name; this.age = age; this.number = number; this.address = address; this.phoneNumber = phoneNumber; this.department = department; }
public static testClass testBuilderMethod() { return new testClass(); }
public testClass toBuilder() { return new testClass().name(this.name).age(this.age).number(this.number).address(this.address).phoneNumber(this.phoneNumber).department(this.department); }
public static class testClass { private String name;
private int age;
private String number;
private String address;
private String phoneNumber;
private Department department;
public testClass name(String name) { this.name = name; return this; }
public testClass age(int age) { this.age = age; return this; }
public testClass number(String number) { this.number = number; return this; }
public testClass address(String address) { this.address = address; return this; }
public testClass phoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; return this; }
public testClass department(Department department) { this.department = department; return this; }
public Employee testMethod() { return new Employee(this.name, this.age, this.number, this.address, this.phoneNumber, this.department); }
public String toString() { return "Employee.testClass(name=" + this.name + ", age=" + this.age + ", number=" + this.number + ", address=" + this.address + ", phoneNumber=" + this.phoneNumber + ", department=" + this.department + ")";
}
}
}
@Cleanup:该注解可以为我们将那些需要显式回收的资源自动回收,比如我们的io流、文件等等,它只有一个属性value,该属性可以用来指定关闭资源的方法,该方法必须是无参方法,代码如下:
public void test(String file, String outFile) throws IOException {
@Cleanup FileInputStream in = new FileInputStream(file);
@Cleanup FileOutputStream out = new FileOutputStream(outFile);
byte[] bytes = new byte[Integer.MAX_VALUE];
while (true) {
int s = in.read(bytes);
if (s == -1) {
break;
}
out.write(bytes);
}
}
反编译:
public class Department
{
public void test(String file, String outFile)
throws IOException
{
FileInputStream in = new FileInputStream(file);
try { FileOutputStream out = new FileOutputStream(outFile);
try { byte[] bytes = new byte[2147483647];
while (true) {
int s = in.read(bytes);
if (s == -1) {
break;
}
out.write(bytes);
}
}
finally
{
if (Collections.singletonList(out).get(0) != null) out.close();
}
}
finally
{
if (Collections.singletonList(in).get(0) != null) in.close();
}
}
}
@Data:这个注解将@Getter、@Setter、@ToString、@EqualsAndHashCode集合为一体,同时,staticConstrutor属性可以为我们生成一个静态工厂方法
@Synchronized:该注解会对被标注的方法进行同步,与synchronized关键字不同的是,同步的方法不是使用内置锁,而是使用私有对象锁来完成的,这样不仅可以降低锁的粒度,并且可以提高多线程情况下系统的性能,代码如下:
@Synchronized
public int test(int a, int b) {
int result = a + b;
return result;
}
反编译之后:
public class Department
{
private final Object $lock = new Object[0];
public int test(int a, int b) { synchronized (this.$lock)
{
int result = a + b;
return result;
}
}
}
@Value:该注解与@Data类似,将多个注解的功能整合在一起,但是该注解是用于标注不可变类。 其将@Getter,@Setter,@FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE),@AllArgsConstructor,@ToString @EqualsAndHashCode结合在一起,代码如下:
/**
* 员工类
**/
@Value
public class Employee {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
/**
* 工号
*/
private String number;
/**
* 地址
*/
private String address;
/**
* 电话号码
*/
private String phoneNumber;
/**
* 所属部门
*/
private Department department;
}
反编译:
public final class Employee
{
private final String name;
private final int age;
private final String number;
private final String address;
private final String phoneNumber;
private final Department department;
public Employee(String name, int age, String number, String address, String phoneNumber, Department department)
{
this.name = name; this.age = age; this.number = number; this.address = address; this.phoneNumber = phoneNumber; this.department = department;
}
public String getName()
{
return this.name;
}
public int getAge() {
return this.age;
}
public String getNumber() {
return this.number;
}
public String getAddress() {
return this.address;
}
public String getPhoneNumber() {
return this.phoneNumber;
}
public Department getDepartment() {
return this.department;
}
public boolean equals(Object o)
{
if (o == this) return true; if (!(o instanceof Employee)) return false; Employee other = (Employee)o; Object this$name = getName(); Object other$name = other.getName(); if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; if (getAge() != other.getAge()) return false; Object this$number = getNumber(); Object other$number = other.getNumber(); if (this$number == null ? other$number != null : !this$number.equals(other$number)) return false; Object this$address = getAddress(); Object other$address = other.getAddress(); if (this$address == null ? other$address != null : !this$address.equals(other$address)) return false; Object this$phoneNumber = getPhoneNumber(); Object other$phoneNumber = other.getPhoneNumber(); if (this$phoneNumber == null ? other$phoneNumber != null : !this$phoneNumber.equals(other$phoneNumber)) return false; Object this$department = getDepartment(); Object other$department = other.getDepartment(); return this$department == null ? other$department == null : this$department.equals(other$department); }
public int hashCode() { int PRIME = 59; int result = 1; Object $name = getName(); result = result * 59 + ($name == null ? 43 : $name.hashCode()); result = result * 59 + getAge(); Object $number = getNumber(); result = result * 59 + ($number == null ? 43 : $number.hashCode()); Object $address = getAddress(); result = result * 59 + ($address == null ? 43 : $address.hashCode()); Object $phoneNumber = getPhoneNumber(); result = result * 59 + ($phoneNumber == null ? 43 : $phoneNumber.hashCode()); Object $department = getDepartment(); return result * 59 + ($department == null ? 43 : $department.hashCode()); }
public String toString() { return "Employee(name=" + getName() + ", age=" + getAge() + ", number=" + getNumber() + ", address=" + getAddress() + ", phoneNumber=" + getPhoneNumber() + ", department=" + getDepartment() + ")";
}
}
@Slf4j:这个注解可以很方便的为我们生成基于slf4j的日志变量。
除了上面介绍的注解,还有一些注解可以使用,需要的可去lombok官方文档查看:https://objectcomputing.com/resources/publications/sett/january-2010-reducing-boilerplate-code-with-project-lombok
介绍了lombok的注解,我们接下来简单介绍下lombok如何使用,一共分两步,很简单:
maven中引入lombok的jar包:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
需要指出的是,lombok是在编译阶段进行使用的,因此将其scope定为provided
为了让我们在写代码时不受到ide对lombok无法静态分析的困扰,需要在我们的ide中安装lombok插件
介绍完lombok和使用方法,我们深入lombok看看,这个工具到底是如何实现的。我们首先应该注意到,lombok工具统统都是通过注解来进行的,lombok也正是使用了注解解析的特性来实现自己的功能的。那么大家应该知道接下来,我们需要先看一下注解解析。
众所周知,自JDK5加入注解以来,注解被广泛应用到java编程的各个领域,注解其实就是一种标识,是一种修饰符,通过注解,我们可以赋予我们的类、方法、变量一些其他的能力。注解的工作原理就是通过注解处理器对被注解标注的代码进行解析,并赋予其相应的功能。
注解解析可以分为两类,一类是编译时解析,一类是运行时解析。运行时解析是通过java的反射机制来实现的,相信大家都进行过实践;lombok正是基于第一类,编译时解析机制来实现的。编译时解析,顾名思义,在java代码被编译为字节码的过程中,通过注解处理器,对注解进行解析。提到这个,我们就不得不提这个机制的核心,注解处理器。
注解处理器(apt)是javac的一个命令行工具,java5引入注解后,提供了apt(annotation process tool)这样一个工具来获取java代码中的注解信息,并根据这些信息自动生成java代码,该工具在java5集成在com.sun.mirror包下,并不属于jdk标准包,并且该工具未集成到javac中,如果需要使用必须额外运行该工具才行。
java6发布了JSR269( Pluggable Annotation Processing API),让我们可以自定义注解处理器,并将注解处理过程嵌入到javac的编译过程中,这个包的发布真的为我们打开了新世界的大门,伟大航路就在眼前了,一下子,很多人的代码都开始“骚”起来了。
熟悉编译原理的同学可能知道,javac作为前端编译器,从源代码编译到class字节码的过程可以分为三个阶段:
javac在生成抽象语法树后会启动根据JSR269生成的注解处理器,对注解进行解析处理,并根据处理结果修改抽象语法树,javac会对修改后的抽象语法树再进行语义分析并最终生成字节码。总结一下,引入JSR269后,javac的编译过程可以总结为以下三个阶段:
lombok正是基于JSR269实现的。在注解解析处理这一步中,lombok自定义的注解解析处理器根据注解,知道要生成的代码是什么,然后通过内部的组件在抽象语法树中找到修改的关键点,然后对抽象语法树进行修改,从而完成编译时注解代码自动生成的功能。
现在,我们也自行来简单的实现一个@Get注解,这个注解和lombok的@Getter注解功能一样,为我们的类成员变量自动生成get方法。要实现自定义注解处理器,必须要继承 AbstractProccessor 类,这个抽象类是JSR269为我们提供的实现注解处理器的统一接口,我们来看一下这个抽象类:
public abstract class AbstractProcessor implements Processor {
/**
* 该变量是框架为我们提供的注解处理的上下文环境变量,包含了很多我们需要的工具类,比如Elements,Filter等等
*/
protected ProcessingEnvironment processingEnv;
private boolean initialized = false;
/**
* 子类需要调用的构造方法
*/
protected AbstractProcessor() {}
/**
* 指定可以被处理器识别的选项,如果处理器类使用了@SupportedOptions注解,则会根据这个注解的标注生成一个不可变的StringSet,其中包含了该注解设置的属性值
*/
public Set<String> getSupportedOptions() {
SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
if (so == null)
return Collections.emptySet();
else
return arrayToSet(so.value());
}
/**
* 该方法用于指定处理器要识别的注解类型,通过这个方法,我们就能告诉处理器要处理哪些我们自定义的注解
*/
public Set<String> getSupportedAnnotationTypes() {
SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
if (sat == null) {
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedAnnotationTypes annotation " +
"found on " + this.getClass().getName() +
", returning an empty set.");
return Collections.emptySet();
}
else
return arrayToSet(sat.value());
}
/**
*该方法用于返回支持的java版本
*/
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
SourceVersion sv = null;
if (ssv == null) {
sv = SourceVersion.RELEASE_6;
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedSourceVersion annotation " +
"found on " + this.getClass().getName() +
", returning " + sv + ".");
} else
sv = ssv.value();
return sv;
}
/**
* 该方法是用于初始化我们的注解处理器的,主要是通过该方法将我们的注解环境参数ProcessingEnvironment对象传入我们的处理器当中
*/
public synchronized void init(ProcessingEnvironment processingEnv) {
if (initialized)
throw new IllegalStateException("Cannot call init more than once.");
Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
this.processingEnv = processingEnv;
initialized = true;
}
/**
* 该抽象方法是整个注解处理器的入口,是处理器的主方法,也是我们必须实现的方法
*/
public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv);
/**
* 该方法返回一个空的completion集合
*/
public Iterable<? extends Completion> getCompletions(Element element,
AnnotationMirror annotation,
ExecutableElement member,
String userText) {
return Collections.emptyList();
}
/**
* 该方法用于返回处理器的初始化状态
*/
protected synchronized boolean isInitialized() {
return initialized;
}
/**
* 该方法是将一个字符串数组转化为一个不可变的hashset,主要为其他方法服务
*/
private static Set<String> arrayToSet(String[] array) {
assert array != null;
Set<String> set = new HashSet<String>(array.length);
for (String s : array)
set.add(s);
return Collections.unmodifiableSet(set);
}
}
那么了解完这个抽象类,我们来手动实现一下:
首先创建一个注解,为了简单,这个注解我们就不定义属性了
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Get {
}
注意的是RetentionPolicy我们不要选择运行时了,应该选择CLASS,即编译阶段。ok,有了注解,我们来实现一下注解处理器:
package com.jim2954981.processor;
import com.jim2954981.annotation.Get;
import com.jim2954981.generatecode.CodeGenerator;
import com.squareup.javapoet.JavaFile;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Liuyi28
* @since 2019-08-01 16:17
**/
@SupportedAnnotationTypes({"com.jim2954981.annotation.Get"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class TestProcesser extends AbstractProcessor {
// 被处理的注解所对应的类
private TypeElement ele;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取注解了@Get的元素
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Get.class);
for (Element element : elements) {
// Element接口涵盖了各种元素,包、限定符、类等等,所以我们需要通过element获取到被我们注解的那个类
if (element.getKind() == ElementKind.CLASS && element instanceof TypeElement) {
ele = (TypeElement) element;
break;
}
}
// 经过上一步操作,我们算是把Get注解拿到了,接下来就要对根据这些生成源代码了,也就是生成类的成员变量和get方法
// 返回被注解的类内的所有节点
List<? extends Element> classElements = ele.getEnclosedElements();
// 遍历获取所有成员变量
List<VariableElement> fields = new ArrayList<>(classElements.size());
for (Element e : classElements) {
if (e.getKind() == ElementKind.FIELD) {
fields.add((VariableElement) e);
}
}
// 基于javapoet生成所需要的类源码、变量源码和get方法源码
CodeGenerator generator = new CodeGenerator(ele);
JavaFile file = JavaFile.builder(processingEnv.getElementUtils()
.getPackageOf(ele).getQualifiedName().toString()
, generator.generateTypeCode(fields)).build();
// 将生成的源码写入源文件中
try {
file.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
// 把我们的get注解注册到处理器中
Set<String> types = new HashSet<>();
types.add(Get.class.getCanonicalName());
return types;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
}
package com.jim2954981.generatecode;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import java.util.ArrayList;
import java.util.List;
/**
* @author Liuyi28
* @since 2019-08-02 11:20
**/
public class CodeGenerator {
private Element ele;
public CodeGenerator(Element element) {
this.ele = element;
}
/**
* 生成get方法源码
*
* @param fields 变量集合
* @return
*/
private List<MethodSpec> generateGetMethod(List<VariableElement> fields) {
List<MethodSpec> result = new ArrayList<>(fields.size());
for (VariableElement field : fields) {
// 分别获取变量的名称和类型
String name = field.getSimpleName().toString();
TypeMirror type = field.asType();
// 变量名首字母要大写
String fl = name.substring(0, 1).toUpperCase() + name.substring(1);
// 生成get方法
MethodSpec methodSpec = MethodSpec.methodBuilder("get" + fl)
.addModifiers(Modifier.PUBLIC)
.addCode("return this." + name + ";")
.returns(TypeName.get(type))
.build();
result.add(methodSpec);
}
return result;
}
/**
* 批量生成类变量源码
*
* @param fields 变量集合
* @return
*/
private List<FieldSpec> generateFieldCode(List<VariableElement> fields) {
List<FieldSpec> result = new ArrayList<>(fields.size());
for (VariableElement field : fields) {
FieldSpec fieldSpec = FieldSpec.builder(TypeName.get(field.asType()), field.getSimpleName().toString(), Modifier.PRIVATE).build();
result.add(fieldSpec);
}
return result;
}
/**
* 创建类源码
*
* @param fields 变量集合
* @return
*/
public TypeSpec generateTypeCode(List<VariableElement> fields) {
List<MethodSpec> methodSpecs = generateGetMethod(fields);
List<FieldSpec> fieldSpecs = generateFieldCode(fields);
return TypeSpec.classBuilder(ele.getSimpleName().toString())
.addModifiers(Modifier.PUBLIC)
.addMethods(methodSpecs)
.addFields(fieldSpecs)
.build();
}
}
coding完之后,我们需要创建这个处理器的注册信息,在resources中的META-INF目录下创建services目录,并在其中创建一个名为javax.annotation.processing.Processor的注册文件,在该文件中将我们处理器的全限定名输入进去:
com.jim2954981.processor.TestProcesser
如果嫌麻烦,我们也可以通google的autoservice工具包来实现同样的功能,流程如下:
1.maven引入依赖
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc2</version>
</dependency>
2.在我们处理器的类上添加注解即可
@AutoService(Processor.class)
public class TestProcesser extends AbstractProcessor {
// 被处理的注解所对应的类
private TypeElement ele;
注解处理器创建好后,我们把它编译打一个jar包。接着,我们创建一个测试类并使用我们的Get注解:
import com.jim2954981.annotation.Get;
@Get
public class TestEntity {
private int key;
private int value;
}
ok,在ide的setting中打开Enable annotation processing选项,然后build我们的测试类。结果:
public class TestEntity {
private int key;
private int value;
public int getKey() {
return this.key;}
public int getValue() {
return this.value;}
}
本篇文章对lombok这个工具包从其功能、使用和原理方面进行了介绍,可以看到,lombok可以为我们带来简洁的代码和轻松的编码工作,让我们摆脱“麻烦”代码的困扰,但同时,lombok也降低了我们的代码可读性,并且lombok的开发需要ide的插件支持,如果在团队开发过程当中,可能会为我们其他同学带来困扰。所以,lombok这把双刃剑,同学们使用的时候还是要酌情思考的。
https://projectlombok.org/
https://www.jianshu.com/p/0cb1f32bc699
https://blog.csdn.net/qinxiandiqi/article/details/49182735