还有近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中办法
文档详见:
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都在父容器中。
对应配置示例如下:
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,均有父容器加载
对应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());
}
}
运行结果如下:
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