2个小bug,有点小门道

Wesley13
• 阅读 808

还有近1个月就要离职了,最近整理下解决过的问题,发现2个小bug,有点小门道。

Bug1:

2017年的某日,小辉(我的同事)遇到了一个bug,解决了一下午还是没有找到,气的摔键盘,骂人,我看在眼里,急在心中。

在他发作了5分钟后。我提心吊胆的问:

       “小辉,别着急遇到了什么问题啊?大家一块看看吧!“

小辉:

       “ 姥姥的,Spring事务不生效!!”

我眉头微微一皱的说:

        “具体什么情况?”

小辉深叹一口气说:

    “Spring,Spring MVC项目,在XML中配置了Spring声明事务,service层的XX方法抛出异常的情况下,内部仍然可以insert成功。”

我一听问题表像立刻说:

     “声明事务配置确认过吗?,抛出的异常是RuntimeException吗?”

小辉冷哼一声说:

     “老子看了,不下5边,绝对没错!”

我咂摸了一下,意味深长的说:

     “Spring父子容器配置正确吗?”

小辉:

      “Spring 父子容器是什么鬼?”

Spring父子容器

Spring,Spring MVC 共存时,会有两个容器一个Spring MVC的Servlet WebApplicationContext为子容器。  一个Spring的Root WebApplicationContext 为父容器。

当子容器找不到对应的bean时会委托父容器中的bean。

Root WebApplicationContext (父容器)中的bean对Servlet WebApplicationContext(子容器)可见,但Servlet WebApplicationContext(子容器)中的bean对Root WebApplicationContext (父容器)不可见。

                                                                    -----白话Spring父子容器关系,温安适,20180422

介绍完成后,我看了下小辉的服务代码,其中web.xml,有如下片段如下:

 <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/mvc-dispatcher.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/api/*</url-pattern>
  </servlet-mapping>
<!---其他配置...... -->
<!---其他配置...... -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/*.xml</param-value>
  </context-param>

用于配置子容器的mvc-dispatcher.xml中配置片段如下:

<context:component-scan base-package="com.palm.ticai" />

用于配置父容器的spring/application-context.xml片段如下:

<aop:aspectj-autoproxy />
<context:component-scan base-package="com.palm.ticai" />
<!--其他必要数据源,和事务配置-->

由于在父容器中开启了@AspectJ注解,与事务配置。子容器和父容器均加载了所有bean。造成子容器中有services覆盖了父容器中的services,导致父容器中动态代理的services不会生效,事务也就没有生效。

其具体情况如下图:

2个小bug,有点小门道

解决小辉的问题官方有2中办法

文档详见:

https://docs.spring.io/spring/docs/4.3.16.RELEASE/spring-framework-reference/htmlsingle中的

Figure 22.2. Typical context hierarchy in Spring Web MVC。

方法1:

子容器包含Controllers,HandlerMapping,viewResolver,其他bean都在父容器中。

2个小bug,有点小门道

对应配置示例如下:

web.xml中没有变化

声明子容器:mvc-dispatcher.xml 对应子容器配置文件:仅加载@Controller,@ControllerAdvice注解的bean

<!--其他配置-->
<context:component-scan 
        base-package="com.palm.ticai"
        use-default-filters="false">
<context:include-filter type="annotation" 
        expression="org.springframework.stereotype.Controller" />
<context:include-filter type="annotation" 
         expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>

父容器对应配置文件,加载其他类:

<aop:aspectj-autoproxy />
<context:component-scan base-package="com.palm.ticai" > 
<context:exclude-filter type="annotation" 
         expression="org.springframework.stereotype.Controller" />
<context:exclude-filter type="annotation" 
         expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>

方法2:子容器不加载任何bean,均有父容器加载

2个小bug,有点小门道

对应web.xml配置

 <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <!--此处不在加载子容器-->
      <param-value></param-value>
      <!--此处不在加载子容器-->
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/api/*</url-pattern>
  </servlet-mapping>
<!---其他配置...... -->

对应父容器配置配置:

<mvc:annotation-driven/>
<aop:aspectj-autoproxy />
<context:component-scan base-package="com.palm.ticai" />
<!--其他必要数据源,和事务配置-->

Bug2:

2018年3月的某日,喜哥,老江湖了,8年的编程经验,遇到了一个小问题,卡了2小时未解决,中午吃饭的时候跟我们吐槽说:

      “我遇到了一个问题,线上竞彩算奖后差2分钱!不知各位大牛有什么想法?”

小辉:

      “赔率问题吧,算法都是久经考验的绝对没有问题!”

我仔细思索了下没有说话。

喜哥摇头说道:

      “赔率确认过绝对没有问题!”

小辉:

       “既然赔率没有问题,算法一定对,2分钱的事,就别找了呗!”

喜哥严肃的说:

        “这2分钱必须找出来,保证账对的上,这是必须的,要不财务不会放过我的。”

我摸了摸下巴说:

         “喜哥,一定是使用了BigDecimal吧?”

喜哥不失礼貌的微笑说道:

         “这是必须啊!我是不会使用double的”

我略带犹豫的说

          “您使用的是new BigDecimal,还是BigDecimal.valueOf”

喜哥眉头一皱说道:

           “还请师傅明示!”

我微笑着说:

          “师傅不敢当,仅仅有一个猜测,如果使用了new BigDecimal ,0.1 在系统中就不是0.1啦,但是使用BigDecimal.valueOf ,0.1还是0.1”。

喜哥喝了口水,鼠标轻轻点击了几下(翻阅了源码)后说:

          “谢谢,温兄,帮了我大忙。”

问题简单模拟

喜哥的问题业务性较大,在这里仅仅以

2.850*2.380*1.0*2.0=13.566

为例,简单模拟下问题

import java.math.BigDecimal;
public class A {
    public static void main(String[] args) {
        BigDecimal d= new BigDecimal(2.850)
               .multiply(new BigDecimal(2.380))
               .multiply(new BigDecimal(1.0))
               .multiply(new BigDecimal(2.0));
        BigDecimal dt= BigDecimal.valueOf(2.850)
               .multiply(BigDecimal.valueOf(2.380))
               .multiply(BigDecimal.valueOf(1.0))
               .multiply(BigDecimal.valueOf(2.0));
        double dd=2.85*2.38*1.0*2.0;
        System.out.println(dd);
        System.out.println(d.doubleValue());
        System.out.println(dt.doubleValue());
    }
}

运行结果如下:

2个小bug,有点小门道

double失真出现13.565999999999999这个很正常。

但是new BigDecimal也有问题?,查看源码BigDecimal有如下注释:

The results of this constructor can be somewhat unpredictable.
     * One might assume that writing {@code new BigDecimal(0.1)} in
     * Java creates a {@code BigDecimal} which is exactly equal to
     * 0.1 (an unscaled value of 1, with a scale of 1), but it is
     * actually equal to
     * 0.1000000000000000055511151231257827021181583404541015625.

简单说new BigDecimal(0.1)会把0.1当成:

0.1000000000000000055511151231257827021181583404541015625.

perfectly predictable: writing {@code new BigDecimal("0.1")}

* creates a {@code BigDecimal} which is exactly equal to

* 0.1, as one would expect. Therefore, it is generally

* recommended that the {@linkplain #BigDecimal(String)

使用BigDecimal(String)这种方式,0.1还是0.1

查阅了valueOf的源码如下:其内部使用了String作为构造函数的入参,所以使用valueOf后0.1还是0.1.

    public static BigDecimal valueOf(double val) {
        // Reminder: a zero double returns '0.0', so we cannot fastpath
        // to use the constant ZERO.  This might be important enough to
        // justify a factory approach, a cache, or a few private
        // constants, later.
        return new BigDecimal(Double.toString(val));
    }

BigDecimal.valueOf(double)将double按字符串处理,保证0.1 在BigDecimal中也是0.1

                                                                             ---白话BigDecimal使用,温安适,20180422

写在最后

 上班以后养成的习惯,随手记录下问题已经解决方案,感觉真是获益良多。

 2个小bug,2个小门道,细细咂摸,挺有味道。

感谢邀约

我的博客即将同步至腾讯云+社区,若有兴趣大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite\_code=3pzs34du7hmok

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
梦
3年前
微信小程序new Date()转换时间异常问题
微信小程序苹果手机页面上显示时间异常,安卓机正常问题image(https://imghelloworld.osscnbeijing.aliyuncs.com/imgs/b691e1230e2f15efbd81fe11ef734d4f.png)错误代码vardate'2021030617:00:00'vardateT
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这