请选择 进入手机版 | 继续访问电脑版

记一次JDK8中关于LocalDate的一点源码改动

[复制链接]
admin 发表于 2020-1-18 22:25:17 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
LocalDate、 LocalTime、 LocalDateTime是Java 8开始提供的时间日期API,主要用来优化Java 8以前对于时间日期的处理操作,确实很方便。笔者在使用的过程中,由于引用LocalDate产生了一个有趣的问题,觉得有必要记录一下。
问题描述
我们系统需要利用原有的核心一个接口报文,向外围提供接口服务。里面有表示日期的字段,原始字段类型定义为String。服务中拿LocalDate.parse来获得LocalDate对象,类似于下面:
LocalDate d = LocalDate.parse("2017/2/21", DateTimeFormatter.ofPattern("yyyy/M/d"));
非常快的开发完了,测试的结果也非常满意。
但是某一天,外围调用我们接口的人反应了一个情况:有一次他们手工做报文,日期写错了,为"2017/2/29",按照道理我们的服务应该校验日期,然后给调用者返回一个错误,但是实际上什么也没有,正常业务执行了。
我找到这一条的日志,发现后台记录的日期是“2017-02-28”。按照先入为主的概念,2017不是闰年,2月份只有28天,所以应该是校验出错误的。查看代码,发现除了上面的转换,都没有其他对于这个字段的操作,所以一下子就僵住了。
问题查找
由于确实没有其他地方来操作这个字段,百度了一圈,没有任何这方面的提示。实在没有办法,只好追踪源码了。时间日期API在rt.jar中,幸好我们有强大的idea, 直接点开就行:
在LocalDate里面发现,parse调用的是DateTimeFormatter.parse,真正调用的是parseResolved0,有异常就抛出DateTimeParseException:
public <T> T parse(CharSequence text, TemporalQuery<T> query) {
        Objects.requireNonNull(text, "text");
        Objects.requireNonNull(query, "query");
        try {
            return parseResolved0(text, null).query(query);
        } catch (DateTimeParseException ex) {
            throw ex;
        } catch (RuntimeException ex) {
            throw createError(text, ex);
        }
    }
经过一连串的debug, 最终定位在java.time.chrono.IsoChronology.resolveYMD方法中,代码为:
@Override  // override for performance
    LocalDate resolveYMD(Map <TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
        int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
        if (resolverStyle == ResolverStyle.LENIENT) {
            long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
            long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1);
            return LocalDate.of(y, 1, 1).plusMonths(months).plusDays(days);
        }
        int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR));
        int dom = DAY_OF_MONTH.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH));
        if (resolverStyle == ResolverStyle.SMART) {  // previous valid
            if (moy == 4 || moy == 6 || moy == 9 || moy == 11) {
                dom = Math.min(dom, 30);
            } else if (moy == 2) {
                dom = Math.min(dom, Month.FEBRUARY.length(Year.isLeap(y)));

            }
        }
        return LocalDate.of(y, moy, dom);
    }
可以看到,源码中,对于4、6、9、11月份,她会获取传入的日期天数和30之间的最小值,而对于2月来说,则判断传入的日期天数和28(闰年是29)之间的最小值,这样就能合理的解释了上面为什么"2017/2/29"校验没有错误,直接变成了"2017-2-28"。我又试了一下,把日期改为"2017/2/31", 也不会有问题,改为“2017/2/32”,就会报异常:

根据上面的情况,推断出:
  • 首先会判断天数是不是大于31,如果大于31,抛出异常,不管是哪个月份
  • 不大于31, 则根据不同的月份,返回月份的实际天数

其实这种处理说不上好坏,反正我觉得没有什么大问题,只不过和我们业务的要求不太相符。我们保险业务对应日期是要严格校验的,前后一天的日期的变化直接关系到是否能够承保,所以不能容忍这样的“智能”的操作,而是应该抛出异常
解决
问题已经出来了,怎么解决呢?当然有很多解决办法,比如直接甩锅给调用方,理直气壮不带犹豫的,不过这都不是我的风格。还是从源码上改一下吧:理想情况就是如果天数不符合要求,就抛出异常:
LocalDate resolveYMD(Map <TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
        int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
        if (resolverStyle == ResolverStyle.LENIENT) {
            long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
            long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1);
            return LocalDate.of(y, 1, 1).plusMonths(months).plusDays(days);
        }
        int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR));
        int dom = DAY_OF_MONTH.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH));
        // previous valid
        if (resolverStyle == ResolverStyle.SMART) {
            if (moy == 4 || moy == 6 || moy == 9 || moy == 11) {
                //原来的:dom = Math.min(dom, 30);
                //改为:
                if (dom > 30) {
                    throw new DateTimeException("The max days of " + moy + " month is 30.");
                }

            } else if (moy == 2) {
                //原来的:dom = Math.min(dom, Month.FEBRUARY.length(Year.isLeap(y)));
                //改为:
                if (Year.isLeap(y)) {
                    if (dom > 29) {
                        throw new DateTimeException("The max of days of " + moy + " month is 29.");
                    }
                } else {
                    if (dom > 28) {
                        throw new DateTimeException("The max days of " + moy + " month is 28.");
                    }
                }


            }
        }
        return LocalDate.of(y, moy, dom);
    }
原本在idea里面新建了java.time.chrono.IsoChronology类,把原来的代码拷贝过来,改一下上面的方法就好了,谁知道竟然没有任何变化,这是为什么呢?
原来,java里面有些jar里面的类最优先加载的,你没有办法加载你自己项目中写的同样pagekage和classname的类,怎么办,只好釜底抽薪,先把下面中自己写的类编译,然后在rt.jar中,把IsoChronology.class更改掉,再试成功了:
注意抛出的就是我定义的异常信息。
下面的图是没有替换IsoChronolocy.class的时候返回的,没有异常,只有最后被“智能”返回的信息:

有毕设需求和其他疑问请联系QQ1249870753
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关注0

粉丝13

帖子737

发布主题
一周下载排行最近7x24小时热帖
最新发布
关闭

站长推荐 上一条 /1 下一条

毕设定制,价格very nice。加qq 1249870753
这里有你想要的资源,懂您所要!

扫描二维码关注我们

© 2019 YOU友共享资源网版权所有 ( 蜀ICP备19016380号-1 )