与 Spring boot devtools 相关的推土机映射异常
A dozer map exception related to Spring boot devtools
我遇到了一个很奇怪的异常,不知道怎么找原因。
业务背景:
添加商品同时是价目表,diff级别用户一个商品有5个价格。
controller中先使用dozer将goodForm转为goods,然后调用goodsService保存goods。
在goodsService保存商品后,遍历商品价目表,将goodsId填充为商品价格,
GoodsForm:
@Mapping("priceList")
List<GoodsPriceForm> goodsPriceFormList;
Goods:
List<GoodsPrice> priceList;
Controller:
Goods goods = BeanMapper.map(goodsForm, Goods.class);
goodsService.saveGoods(adminId, goods);
GoodsService:
goodsDao.save(goods);
goods.getPriceList().forEach(p -> p.setGoodsId(goods.getId()));
goodsPriceDao.save(goods.getPriceList());
但它抛出异常:
2015-11-27 17:10:57,042 [http-nio-8081-exec-8] ERROR o.a.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: com.foo.goods.model.GoodsPrice cannot be cast to com.foo.goods.model.GoodsPrice] with root cause
java.lang.ClassCastException: com.foo.goods.model.GoodsPrice cannot be cast to com.foo.goods.model.GoodsPrice
at com.foo.goods.service.GoodsService$$Lambda/310447431.accept(Unknown Source) ~[na:na]
at java.util.ArrayList.forEach(ArrayList.java:1249) ~[na:1.8.0_51]
at com.foo.goods.service.GoodsService.saveGoods(GoodsService.java:34) ~[classes/:na]
这个错误信息让我感到很困惑。另外我写了一个单元测试想重复这个,但是失败了。
GoodsForm form = new GoodsForm();
form.setGoodsPriceFormList(Lists.newArrayList(new GoodsPriceForm((byte) 1, BigDecimal.valueOf(10)),
new GoodsPriceForm((byte) 2, BigDecimal.valueOf(9)),
new GoodsPriceForm((byte) 3, BigDecimal.valueOf(8))));
Goods goods = BeanMapper.map(form, Goods.class);
goods.getPriceList().forEach(p -> p.setGoodsId(goods.getId()));
运行这个单元测试,执行正常。
那为什么在真实的web情况下(Spring boot + Jpa)它失败了,但是在单元测试情况下就可以了?
Controller:
System.out.println("PriceList: " + goods.getPriceList().getClass().getClassLoader());//PriceList: null
System.out.println(goods.getPriceList().get(0).getClass().getClassLoader()); //java.lang.ClassCastException: com.foo.goods.model.GoodsPrice cannot be cast to com.foo.goods.model.GoodsPrice
如果我生成了打包好的jar,那么执行这个jar
java -jar target/myapp.jar
在这种情况下没有上述例外。
并且我在pom.xml中注释了spring-boot-devtools,然后开始应用,无一例外。
您在这里使用了两个不同的ClassLoader
。加载两个不同 ClassLoader
的相同 Class
被 JVM 视为两个 不同 Class
。
解决这个问题的方法很简单:使用 Interface
.
接口可以抽象这个问题,你可以无限制地在类加载器之间互换它们实现的对象,只要你不直接引用实现。
By default, any open project in your IDE will be loaded using the “restart” classloader, and any regular .jar file will be loaded using the “base” classloader. If you work on a multi-module project, and not each module is imported into your IDE, you may need to customize things. To do this you can create a META-INF/spring-devtools.properties file.
The spring-devtools.properties file can contain restart.exclude. and restart.include. prefixed properties. The include elements are items that should be pulled-up into the “restart” classloader, and the exclude elements are items that should be pushed down into the “base” classloader. The value of the property is a regex pattern that will be applied to the classpath.
我的解决方案:将 META-INF/spring-devtools.properties
放入资源文件夹,并添加此内容
restart.include.dozer=/dozer-5.5.1.jar
我遇到了一个很奇怪的异常,不知道怎么找原因。
业务背景: 添加商品同时是价目表,diff级别用户一个商品有5个价格。
controller中先使用dozer将goodForm转为goods,然后调用goodsService保存goods。 在goodsService保存商品后,遍历商品价目表,将goodsId填充为商品价格,
GoodsForm:
@Mapping("priceList")
List<GoodsPriceForm> goodsPriceFormList;
Goods:
List<GoodsPrice> priceList;
Controller:
Goods goods = BeanMapper.map(goodsForm, Goods.class);
goodsService.saveGoods(adminId, goods);
GoodsService:
goodsDao.save(goods);
goods.getPriceList().forEach(p -> p.setGoodsId(goods.getId()));
goodsPriceDao.save(goods.getPriceList());
但它抛出异常:
2015-11-27 17:10:57,042 [http-nio-8081-exec-8] ERROR o.a.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: com.foo.goods.model.GoodsPrice cannot be cast to com.foo.goods.model.GoodsPrice] with root cause
java.lang.ClassCastException: com.foo.goods.model.GoodsPrice cannot be cast to com.foo.goods.model.GoodsPrice
at com.foo.goods.service.GoodsService$$Lambda/310447431.accept(Unknown Source) ~[na:na]
at java.util.ArrayList.forEach(ArrayList.java:1249) ~[na:1.8.0_51]
at com.foo.goods.service.GoodsService.saveGoods(GoodsService.java:34) ~[classes/:na]
这个错误信息让我感到很困惑。另外我写了一个单元测试想重复这个,但是失败了。
GoodsForm form = new GoodsForm();
form.setGoodsPriceFormList(Lists.newArrayList(new GoodsPriceForm((byte) 1, BigDecimal.valueOf(10)),
new GoodsPriceForm((byte) 2, BigDecimal.valueOf(9)),
new GoodsPriceForm((byte) 3, BigDecimal.valueOf(8))));
Goods goods = BeanMapper.map(form, Goods.class);
goods.getPriceList().forEach(p -> p.setGoodsId(goods.getId()));
运行这个单元测试,执行正常。 那为什么在真实的web情况下(Spring boot + Jpa)它失败了,但是在单元测试情况下就可以了?
Controller:
System.out.println("PriceList: " + goods.getPriceList().getClass().getClassLoader());//PriceList: null
System.out.println(goods.getPriceList().get(0).getClass().getClassLoader()); //java.lang.ClassCastException: com.foo.goods.model.GoodsPrice cannot be cast to com.foo.goods.model.GoodsPrice
如果我生成了打包好的jar,那么执行这个jar
java -jar target/myapp.jar
在这种情况下没有上述例外。
并且我在pom.xml中注释了spring-boot-devtools,然后开始应用,无一例外。
您在这里使用了两个不同的ClassLoader
。加载两个不同 ClassLoader
的相同 Class
被 JVM 视为两个 不同 Class
。
解决这个问题的方法很简单:使用 Interface
.
接口可以抽象这个问题,你可以无限制地在类加载器之间互换它们实现的对象,只要你不直接引用实现。
By default, any open project in your IDE will be loaded using the “restart” classloader, and any regular .jar file will be loaded using the “base” classloader. If you work on a multi-module project, and not each module is imported into your IDE, you may need to customize things. To do this you can create a META-INF/spring-devtools.properties file.
The spring-devtools.properties file can contain restart.exclude. and restart.include. prefixed properties. The include elements are items that should be pulled-up into the “restart” classloader, and the exclude elements are items that should be pushed down into the “base” classloader. The value of the property is a regex pattern that will be applied to the classpath.
我的解决方案:将 META-INF/spring-devtools.properties
放入资源文件夹,并添加此内容
restart.include.dozer=/dozer-5.5.1.jar