前言
在日常的开发中,相信你一定使用过Lombok,它是一款开源的可用于Java平台的代码生成库。我们在定义JavaBean的时候,会使用IDE自动生成构造方法、getter、setter、equals、hashCode、toString等方法,一旦类的属性有修改就要重新生成,通过使用Lambok的简单注解来精简代码就能达到消除冗长代码的目的。
Lombok原理
Lombok基于JSR269 Pluggable Annotation Processing API(插入式注解处理器),在jdk6以后提供了一种方式,可以让我们修改编译过程,在编译期融入我们自己编译逻辑,也就是插入式注解处理器,它提供了一组编译器的插入式注解处理器的标准API在编译期间对注解进行处理。解决了APT(注解处理工具)没有集成到javac中,只能在运行时通过反射来获取注解值,运行时代码效率降低等问题,而javac的编译过程如下:
- 解析与填充符号表过程:读取命令行上指定的所有源文件,将其解析为语法树,然后将所有外部可见的定义输入到编译器的符号表中。
- 插入式注解处理器的注解处理过程:调用所有适当的注解处理器。如果任何注解处理器生成任何新的源文件或类文件,则将重新启动编译,直到没有新文件创建为止。
- 分析与字节码生成过程:分析器创建的语法树将被分析并转换为类文件。在分析过程中,可能会找到对其他类的引用。编译器将检查这些类的源和类路径。如果在源路径上找到它们,则这些文件也将被编译,尽管它们将不受注解处理。
javac将源代码分析生成抽象语法树AST,Lombok注解处理器对抽象语法树进行处理,Lombok解析handler输出修改后的AST,javac将修改后的AST解析和生成,生成字节码文件。
捕获异常
在日常开发中我们经常处理非运行时异常的时候,它们从类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能正常编译通过。例如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。我们通过显示的异常捕获,或者忽略,或者对外抛出。
public static void main(String[] args) {
try {
Class clz = Class.forName("com.demo.duansg.SneakyThrows");
System.out.println("success");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Thread.sleep(3000);
System.out.println("success");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@SneakyThrows
sneaky:鬼鬼祟祟的;暗中的,卑鄙的。看字面意思就是暗地里鬼鬼祟祟的抛出的意思。其功能其实也是如此,它的作用就是减少程序的异常捕获。往往在开发中,大部分情况下的异常,都是一路抛到头,所以有些时候的大家处理Exception的常见手段就是外面包一层RuntimeException,如下所示
try{
//......
} catch(Exception e) {
log.error(message, e)
throw new RuntimeException(e);
}
而Lombok的@SneakyThrows就是为了消除这样的模板代码。使用注解后不需要在显示的对Exception进行处理
@SneakyThrows
protected T newInstance() {
Class<?> clazz = Class.forName("com.demo.duansg.SneakyThrows");
return (T) clazz.newInstance();
}
通过编译后,真正生成的代码,类似于Lombok的@Data注解,在编译时就已经把处理的代码嵌入到了class内。
protected T newInstance() {
try {
Class<?> clazz = Class.forName("com.demo.duansg.SneakyThrows");
return clazz.newInstance();
} catch (Throwable var2) {
throw var2;
}
}
@SneakyThrows定义
它可用于方法和构造函数上,其value是Class<? extends Throwable>[],是Throwable异常的子类集合,如下所示:
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface SneakyThrows {
/** @return The exception type(s) you want to sneakily throw onward. */
Class<? extends Throwable>[] value() default {Throwable.class};
}
@SneakyThrows自定义处理异常
假如在一个方法中有两个异常,但是我们需要手动处理其中一个的时候,也可以根据你的自定义需要,使用Lombok的@SneakyThrows自定义处理的异常,如下所示:
@SneakyThrows({ClassNotFoundException.class,})
protected void newInstance() {
Class<?> clazz = Class.forName("com.demo.duansg.SneakyThrows");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@SneakyThrows原理
/**
* Throws any throwable 'sneakily' - you don't need to catch it, nor declare that you throw it onwards.
* The exception is still thrown - javac will just stop whining about it.
* <p>
* Example usage:
* <pre>public void run() {
* throw sneakyThrow(new IOException("You don't need to catch me!"));
* }</pre>
* <p>
* NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does not know or care
* about the concept of a 'checked exception'. All this method does is hide the act of throwing a checked exception
* from the java compiler.
* <p>
* Note that this method has a return type of {@code RuntimeException}; it is advised you always call this
* method as argument to the {@code throw} statement to avoid compiler errors regarding no return
* statement and similar problems. This method won't of course return an actual {@code RuntimeException} -
* it never returns, it always throws the provided exception.
*
* @param t The throwable to throw without requiring you to catch its type.
* @return A dummy RuntimeException; this method never returns normally, it <em>always</em> throws an exception!
*/
public static RuntimeException sneakyThrow(Throwable t) {
if (t == null) throw new NullPointerException("t");
return Lombok.<RuntimeException>sneakyThrow0(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T {
throw (T)t;
}
从Lombok的核心方法不难看出,它是利用泛型将传入的Throwable强转为RuntimeException。虽然事实上我们不是RuntimeException。但是JVM并不关心这个,这样写只是为了骗过javac编译器,在代码的运行阶段,会对泛型进行擦除。
小结
从此上看来,使用@SneakyThrows时,如果你的代码遇到了异常,那么在编码阶段你是无法感知的。而如果你的代码里可能并没有异常,但是你却声明了该注解,反而会给了解这个注解的开发者带来不必要的误会。所有此注解还是谨慎使用为好,其实它从发布到如今一直以来都备受开发者的争议,有人觉得其使用方式很方便,但是也有人觉得是这是一种对语言逻辑的破坏,反而笔者觉得,约定是大于规范的,开发团队达成一致,Lombok是可以在一定程度上提升团队代码可读性和团队开发效率的。