Java 与Spring boot devtools相关的推土机地图异常

Java 与Spring boot devtools相关的推土机地图异常,java,spring-boot,classloader,dozer,Java,Spring Boot,Classloader,Dozer,我遇到了一个非常奇怪的异常,我不知道如何找到原因 业务背景: 添加商品和同时它的价格表,一个商品有5个不同级别的用户价格 在控制器中,首先使用推土机将goodForm转换为商品,然后调用goodsService保存商品。 在goodsService中,保存商品后,遍历商品价目表并将goodsId填充到商品价格中 GoodsForm: @Mapping("priceList") List<GoodsPriceForm> goodsPriceFormList; Goods: List&l

我遇到了一个非常奇怪的异常,我不知道如何找到原因

业务背景: 添加商品和同时它的价格表,一个商品有5个不同级别的用户价格

在控制器中,首先使用推土机将goodForm转换为商品,然后调用goodsService保存商品。 在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());
这个错误消息让我感到非常困惑。此外,我还编写了一个单元测试,希望重复此操作,但失败了

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环境(SpringBoot+Jpa)中它失败了,但在单元测试环境中它没问题呢



如果我生成了一个打包的jar,那么执行这个jar

java -jar target/myapp.jar
在这种情况下,没有上述例外



我在pom.xml中注释了spring boot devtools,然后启动了应用程序,没有上述异常。

您在这里使用了两个不同的
类加载器。一个相同的
加载了两个不同的
类加载器
被JVM视为两个不同的

解决这个问题的方法很简单:使用
接口

接口能够抽象这个问题,只要不直接引用实现,就可以在类加载器之间无限制地交换它们实现的对象

默认情况下,IDE中任何打开的项目都将使用“restart”类加载器加载,任何常规的.jar文件都将使用“base”类加载器加载。如果您处理的是多模块项目,而不是每个模块都导入到IDE中,则可能需要进行自定义。为此,您可以创建META-INF/spring-devtools.properties文件

spring-devtools.properties文件可以包含restart.exclude。并重新启动.include。前缀属性。include元素是应该向上拉入“restart”类加载器的项,exclude元素是应该向下推入“base”类加载器的项。该属性的值是将应用于类路径的正则表达式模式

我的解决方案是:将
META-INF/spring devtools.properties
放在resources文件夹中,并添加此内容

restart.include.dozer=/dozer-5.5.1.jar

请参阅:

我唯一一次遇到这样的异常是,如果您使用两个不同的类装入器装入同一个类。你能试着打印出每个对象的类加载器吗?然后相同的类被两个不同的类加载器加载。第一个措施是将类放在同一个位置的一个罐子中。@Wim Deblauwe我已经尝试了你的方法,请参阅本文底部的补充内容。因此goods.getPriceList已经包含了错误的类型化对象。bean操作工具(dozer?)可能会出现这种情况(类型擦除)。他们可能使用另一个同名的jar/类。Dozer可能用作web服务器,并具有其他类加载器。顺便说一句,BigDecimal.valueOf(“9.00”)
可能会更好:精度是2位
getClass().getProtectionDomain().getCodeSource().getLocation().toString()
可以告诉jar您何时不会收到该错误。没有静态价目表吗?没有保存超过应用程序寿命的价目表?最好在应用程序开始时加载。我同意你的第一部分,但你的第二部分完全错了。从类加载器的角度来看,接口也是一个类文件。所以
classLoader1.loadClass(“…MyInterface”)!=loadClass(“…MyInterface”)
。哦,你是对的,我不是那个意思,但它完全是那个意思。我只是修改了我的措辞以使它更清楚。我认为这仍然是含糊不清的。如果你阅读我在OP上的复制标记,你会看到,这是关于一个接口。只要您仍然使用两个不同的类加载器加载接口,使用接口就不会有任何好处。我知道在某些情况下,来自公共父类加载器的接口或超类会有所帮助,但我不认为当前的问题属于这种情况。你仍然可以用一个例子来证明我是错的……如果你使用dozer spring,也可以添加这个:restart.include.dozer spring=/dozer-spring-5.5.1.jar这个文件也支持regexp路径,所以我宁愿使用
restart.include.dozer=/dozer-[\\w\\d.]+\.jar
。在更新依赖项时,很容易忘记更新此属性。
java -jar target/myapp.jar
restart.include.dozer=/dozer-5.5.1.jar