07. 《Lombok 实战 —— @SneakyThrows & @Synchronized》

x33g5p2x  于2021-12-25 转载在 其他  
字(4.2k)|赞(0)|评价(0)|浏览(329)

@SneakyThrows

1.1 @SneakyThrows 实战使用

@SneakyThrows 可用于偷偷抛出checked exception,而无需在方法上的throws子句中声明需要抛出的异常。
lombok生成的代码,不会忽略,不会包装,不会替换或以其他方式修改抛出的checked exception,而是将 checked exception 看做unchecked exception,不处理,直接扔掉。
那么lombok是如何工作的呢?

在JVM(类文件)级别上,无论方法throw什么异常,lombok都可以抛出所有异常(无论是否是检查时异常),如下面一个简单的示例:

public class SneakyThrowsTest {
    public static void main(String[] args) {
        throwException();
    }
    @SneakyThrows
    public static void throwException() {
        String str  = null;
        String[] split = str.split(",");
        System.out.println(split);
    }
}

// 编译后:
public class SneakyThrowsTest {
    public SneakyThrowsTest() {}

    public static void main(String[] args) {
        throwException();
    }
    public static void throwException() {
        try {
            String str = null;
            String[] split = ((String)str).split(",");
            System.out.println(split);
        } catch (Throwable var2) {
            throw var2;
        }
    }
}

看到示例,可能就很好解释,为什么lombok都可以抛出所有异常,catch (Throwable var2)放在方法体的开始和结束,可以到所有的异常。

1.2 @SneakyThrows 注解源码

其实lombok并未为我们提供更多配置,如下所示:

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface SneakyThrows {
	// 指定准确的异常类型
	Class<? extends Throwable>[] value() default java.lang.Throwable.class;
}
  • 测试注解属性value
public class SneakyThrowsTest {
    public static void main(String[] args) {
        throwException();
    }
    @SneakyThrows(value = NullPointerException.class)
    public static void throwException(){
        String str  = null;
        String[] split = str.split(",");
        System.out.println(split);
    }
}
// 编译后:
public class SneakyThrowsTest {
    public SneakyThrowsTest() {}

    public static void main(String[] args) {
        throwException();
    }
    public static void throwException() {
        try {
            String str = null;
            String[] split = ((String)str).split(",");
            System.out.println(split);
        } catch (NullPointerException var2) {
            throw var2;
        }
    }
}

当我们制定了异常之后,lombok就会只捕捉我们指定的一种或者几种类型的异常,当然如果我们指定的类型没有被捕捉到,就会被抛到上一层。

当然我们使用的时候如果使用注解的情况下,还是不要指定异常类型了。

1.3 两个特殊情况
public class SneakyThrowsTest {
    public static void main(String[] args) {
        throwException();
    }
    @SneakyThrows(value = FileNotFoundException.class)
    public static void throwException(){
        File file = new File("filePath");
        @Cleanup InputStream inputStream = new FileInputStream(file);
    }
}

当我们准确指定unchecked exception时,你会发现在IDEA中编译并不会通过,但是在你不指定准确异常类型时,比如使用以下方式来注解的话,却可以正常编译通过:

public class SneakyThrowsTest {
    public static void main(String[] args) {
        throwException();
    }
    @SneakyThrows
    public static void throwException(){
        File file = new File("filePath");
        @Cleanup InputStream inputStream = new FileInputStream(file);
    }
}
1.4 总结一下 @SneakyThrows

首先,我在实际开发中使用@SneakyThrows并不是太多,在处理一些业务时使用起来并不灵活。但是在一些情况下你可以使用,比如在一些不太可能发生异常的地方,但是你又必须cache checked exception的地方使用这个@SneakyThrows annotation会显得代码比较规整,易读。

2. @Synchronized

synchronized done right: Don’t expose your locks.

2.1 @Synchronized 实战使用

@Synchronizedsynchronized方法修饰符的更安全的变体。与synchronized一样,注释只能用于静态实例方法。
@Synchronized的操作类似于synchronized关键字,但它锁定在不同的对象上。
关键字synchronized锁定this,但@Synchronized锁定在名为$lock的字段上,并且该字段是私有的。
(如果该字段不存在,则会为你创建该字段。)如果注释静态方法,则注释会锁定名为$LOCK的静态字段。

public class SynchronizedTest {
    private static final Logger log = LoggerFactory.getLogger(SynchronizedTest.class);
    private static final Object $LOCK = new Object[0];
    private final Object $lock = new Object[0];

    public SynchronizedTest() { }

    public static void staticMethodTest() {
        Object var0 = $LOCK;
        synchronized($LOCK) {
            log.info("this is static method test.");
        }
    }

    public void commonMethodTest() {
        Object var1 = this.$lock;
        synchronized(this.$lock) {
            log.info("this is common method test.");
        }
    }
}

如果自动生成$lock$LOCK,则会使用空的Object[]数组初始化锁字段,而不仅仅是新的Object()
Lombok这样做是主要是因为new object()不可序列化的,但空的Object[]数组是可以实现序列化的。
因此,使用@Synchronized不会阻止对象序列化。

在类中至少有一个方法使用注解@Synchronized,意味着会有一个锁字段,但是如果稍后删除这些添加@Synchronized注解的方法,则此锁字段也会被同步删除。
这意味着你预定的serialVersionUID就会会发生变化。
如果您打算通过java的序列化机制长期存储这些对象,所以建议始终在类中添加固定的serialVersionUID
添加了固定的serialVersionUID之后,再从方法中删除所有@Synchronized注释的方法将不会破坏序列化与反序列化。

2.2 @Synchronized 自定义锁

当然,你也可以自己创建这些锁。如果你已经自己创建了$lock$LOCK字段,那么lombok不会再为你生成这些锁字段。
另外,你还可以选择锁定另一个字段(非$lock$LOCK字段),方法是将其指定为@Synchronized注释的参数。
在此用法变体中,不会自动创建字段,所以必须自己显式创建这些自定义的锁,否则将会在编译期发出错误。

public class SynchronizedTest {
    private static final Logger log = LoggerFactory.getLogger(SynchronizedTest.class);
    private final Object customLock = new Object();

    public SynchronizedTest() {}

    public void specifiedLockTest() {
        Object var1 = this.customLock;
        synchronized(this.customLock) {
            log.info("this is specified lock test.");
        }
    }
}

为什么在注解@Synchronized中通过value属性指定自定义的锁名称时,而不会自动生成此自定义的锁字段字段:因为否则在字段名称中输入错误将导致很难找到错误!

参考文档

下一篇:08. Lombok @Log

相关文章