简介:系统启动异常日志被JDK吞掉无法定位? 使用同样的加密方式,有些数据无法解密? 向List添加数据时提示不支持? 日期显然相隔一年,但输出了一天。 难道这就是天上人间吗? 1582年神秘消失10天的JDK能否被识别? 很厉害,但是List转Map失败了……JDK8官方的这些坑你踩过几个?
目录:
01:你是我无法解开的谜
出于保护用户隐私信息的目的,系统需要对姓名、身份证、手机号码等敏感信息进行加密存储。 很自然地选择外面多了一层的 AES 算法。 上一个是sun.misc./,网上的资料基本都是这样写的,运行完美。 但是这种写法在idea或者maven编译的时候会出现一些黄色警告提示。 Java 8之后,编码已经成为Java类库的标准,内置了编码编码器和解码器。于是,我笨手笨脚地修改了代码,使用了jdk8自带的方法。
import java.util.Base64;
public class Base64Utils {
public static final Base64.Decoder DECODER = Base64.getDecoder();
public static final Base64.Encoder ENCODER = Base64.getDecoder();
public static String encodeToString(byte[] textByte) {
return ENCODER.encodeToString(textByte);
}
public static byte[] decode(String str) {
return DECODER.decode(str);
}
}
我们还是有程序员的职业道德,构造新旧数据,自测,通过,提交测试版本。 充满信心,我将继续延续我的零bug神话! 然后……然后版本就回滚了。
Caused by: java.lang.IllegalArgumentException: Illegal base64 character 3f
at java.util.Base64$Decoder.decode0(Base64.java:714)
at java.util.Base64$Decoder.decode(Base64.java:526)
at java.util.Base64$Decoder.decode(Base64.java:549)
关键是这个错误很奇怪。 有些数据可以解密,但有些则不能。
依赖于简单的编码和解码算法,使用 65 个字符的 US-ASCII 子集,其中前 64 个字符中的每一个都映射到等效的 6 位二进制序列,第 65 个字符 (=) 用于对编码文本进行填充为整数大小。 后来又产生了3种变体:
SN 方法名称和描述。 ()
返回。 使用基本编码方案进行解码。 。 ()
返回用于编码的基本编码方案。 。 ()
返回。 使用 MIME 类型对解码方案进行解码。 。 ()
返回。 编码使用 MIME 类型编码方案。 。 (整数,字节[])
使用指定的行长度和行分隔符返回 MIME 类型的编码方案。 。 ()
返回。 使用 URL 和文件名安全的编码方案进行解码。 。 ()
返回。 使用 URL 和文件名安全的编码方案进行解码。
详细使用说明请参考:
关于上面的错误,网上有人提出建议使用.()和.()。 我只能建议:如果旧系统已有数据,就不要使用jdk自带的。 官方的JDK和sun不兼容! 不换! 不换! 不换!
02 被吞噬的异常:我不敢说出你的名字
这个问题比较难理解,所以我把这个系统异常的过程提炼成了一个美丽的故事。 放松心情,背诵一首诗!
最怕相思
一切都是你
仅有的
不敢说出你的名字
–码叔
使用注解的时候遇到了这个问题。 发现JDK在解析注解的时候,如果该注解所依赖的类定义在JVM加载时不存在,即实际获取到的异常会是,不会,与JDK中的类是.java有关,具体代码如下:
private static Object parseClassArray(int paramInt, ByteBuffer paramByteBuffer, ConstantPool paramConstantPool, Class paramClass) {
Class[] arrayOfClass = new Class[paramInt];
int i = 0;
int j = 0;
for (int k = 0; k < paramInt; k++){
j = paramByteBuffer.get();
if (j == 99) {
// 注意这个方法
arrayOfClass[k] = parseClassValue(paramByteBuffer, paramConstantPool, paramClass);
} else {
skipMemberValue(j, paramByteBuffer);
i = 1;
}
}
return i != 0 ? exceptionProxy(j) : arrayOfClass;
}
private static Object parseClassValue(ByteBuffer paramByteBuffer, ConstantPool paramConstantPool, Class paramClass) {
int i = paramByteBuffer.getShort() & 0xFFFF;
try
{
String str = paramConstantPool.getUTF8At(i);
return parseSig(str, paramClass);
} catch (IllegalArgumentException localIllegalArgumentException) {
return paramConstantPool.getClassAt(i);
} catch (NoClassDefFoundError localNoClassDefFoundError) {
// 注意这里,异常发生了转化
return new TypeNotPresentExceptionProxy("[unknown]", localNoClassDefFoundError);
} catch (TypeNotPresentException localTypeNotPresentException) {
return new TypeNotPresentExceptionProxy(localTypeNotPresentException.typeName(), localTypeNotPresentException.getCause());
}
}
在这个方法中,期望返回的是Class对象,但是看实际逻辑,遇到的时候,返回的是由于类型强制转换失败,最终抛出的是java.lang.:sun…,此时只需调试这行代码,找出缺少哪个类定义就可以解决这个问题。
作者再现了发现这个陷阱的场景。 有三种:依赖但未声明依赖、依赖但声明类型。 依赖关系图如下:
上面每一个都有一个Class,我们命名为它。 启动时注解中使用的类是由该类继承的。 这些类的依赖关系图如下:
这样一来,其实很容易知道运行时会出现,但是真正运行的时候,你能看到的异常不会是,而是java.lang.:sun…,这时候,如果你想要了解具体的异常 对于任何异常,都需要使用debug来定位具体的问题。 下面是两张截图,分别对应系统控制台实际抛出的异常和通过debug发现的异常信息。
控制台异常信息:
注意,异常实际上是在红圈内,它已经自动缩小了。 需要展开才能看到通过调试发现的异常信息:
如果你想体验这个例子,可以关注公众号大叔与作者交流。 如果下次遇到莫名其妙的java.lang.:sun…,请记得用这个方法来定位具体问题。
03 日期计算:我想保留时间,让一天和一年一样长
Java 8之前的日期和时间操作相当繁琐。 不管是静止的,都会让你觉得这个设计太反人类了。 它甚至存在多线程安全问题。 阿里巴巴开发手册中禁止修改。 庆幸的是,经过长时间的呼吁,已经落实了。 Java8带来了新的日期和时间API,还带来了两个用于时间和日期计算的API。
和 ,都表示时间间隔,通常用来表示小时、分钟、秒甚至纳秒之间的时间间隔,通常用来表示年、月、日之间的时间间隔。
网上大部分文章也是这样描述的,所以计算两个日期之间的间隔可以写成下面的代码:
// parseToDate方法作用是将String转为LocalDate,略。
LocalDate date1 = parseToDate("2020-05-12");
LocalDate date2 = parseToDate("2021-05-13");
// 计算日期间隔
int period = Period.between(date1,date2).getDays();
一个是2023年,一个是2023年,你觉得间隔是多少? 一年? 恭喜你,和我一起跳坑了(画外音:里面的人都挤了,动了,还有新人来了)。
正确答案应该是:1天。
这个词的意思和这个方法确实看起来很有误导性,一不小心就会掉进陷阱。 事实上,只能计算同月的天数和同年的月数,而无法计算跨月的天数和跨年的月数。
正确写法1:
long period = date2.toEpochDay()-date1.toEpochDay();
():将日期转换为纪元天数,即相对于1970-01-01(ISO)开始的天数,与时间戳相同。 时间戳是秒数。 显然,这种方法有一定的局限性。
正确写法2:
long period = date1.until(date2,ChronoUnit.DAYS);
使用这种写法时,一定要注意date1和date2的前后顺序:date1到date2。
正确做法3(推荐):
long period = ChronoUnit.DAYS.between(date1, date2);
:一组标准日期和时间单位。 这组单位提供基于单位的访问来操作日期、时间或日期时间。 这些单位可与多个日历系统配合使用。 这是一个最终的、不可变的、线程安全的枚举。
好了,今天的主题就讲到这里吧,不管如何,能帮到你我就很开心了,如果您觉得这篇文章写得不错,欢迎点赞和分享给身边的朋友。