2015-11-27 29 views
6

Ho riscontrato un'eccezione molto strana e non so come trovare il motivo.Un'eccezione della mappa dozer relativa agli sviluppatori di avvio di primavera

Business background: Aggiungi merci e nel frattempo il suo listino prezzi, una merce ha 5 prezzi per utente di livello diff.

Nel controller, convertire prima GoodForm in merci utilizzando dozer, quindi chiamare goodsService per salvare le merci. In goodsService dopo il salvataggio merci, merci attraversamento listino prezzi e popolare goodsId al prezzo delle merci,

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()); 

Ma buttare eccezione:

messaggio
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$11/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] 

Questo errore fammi sentire molto confuso. In aggiunta scrivo un test unitario che volevo ripetere, ma non ci sono riuscito.

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())); 

Eseguire questo test dell'unità, è stato eseguito correttamente. Allora perché nella situazione reale del web (Spring boot + Jpa) è fallito, ma in situazione di test unitario va bene?


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 

Se ho generato un barattolo confezionato, quindi eseguire questa giara

java -jar target/myapp.jar 

In questo caso senza eccezioni sopra.


E ho commentato spring-boot-devtools in pom.xml, quindi ho avviato l'applicazione, senza eccezioni.

+9

L'unica volta che ho avuto un'eccezione del genere è se si carica la stessa classe con 2 diversi caricatori di classe. Puoi provare a stampare il caricatore di classe di ogni oggetto? –

+5

Quindi la stessa classe è stata caricata da due diversi caricatori di classe. La prima misura è di avere la classe in un solo barattolo in una posizione. –

+0

@Wim Deblauwe Ho provato la tua strada, per favore vedi il mio contenuto supplementare in fondo a questo post – zhuguowei

risposta

8

Per impostazione predefinita, qualsiasi progetto aperto nell'IDE viene caricato utilizzando il programma di caricamento di classe "restart" e qualsiasi file .jar regolare verrà caricato utilizzando il programma di caricamento classe "base". Se lavori su un progetto a più moduli e non ogni modulo viene importato nel tuo IDE, potrebbe essere necessario personalizzare le cose. Per fare ciò è possibile creare un file META-INF/spring-devtools.properties.

Il file spring-devtools.properties può contenere restart.exclude. e restart.include. proprietà prefissate. Gli elementi include sono elementi che devono essere estratti nel classloader "restart" e gli elementi exclude sono elementi che devono essere inseriti nel classloader "base". Il valore della proprietà è un modello regex che verrà applicato al classpath.

mia soluzione: mettere META-INF/spring-devtools.properties cartella delle risorse all'interno, e aggiungere questo contenuto

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

Si veda: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-devtools-customizing-classload

+1

Se usi dozer-spring, aggiungi anche questo: restart.include.dozer-spring =/dozer-spring-5.5.1.jar – cLyric

0

Qui stai utilizzando due diversi ClassLoader. Un identico Class caricato con due diversi ClassLoader è considerato come due diversoClass da JVM.

La soluzione per risolvere questo problema è semplice: utilizzare un Interface.

Le interfacce sono in grado di risolvere questo problema e puoi scambiare l'oggetto che implementano tra ClassLoaders senza limitazioni, purché tu non faccia direttamente riferimento all'implementazione.

+1

Vengo con te nella prima parte, ma la tua seconda parte è totalmente sbagliato. Un'interfaccia è, dalla vista di un programma di caricamento classi, anche un file di classe. Quindi 'classLoader1.loadClass (" ... MyInterface ")! = ClassLoader2.loadClass (" ... MyInterface ")'. –

+0

Oh, hai ragione, non intendevo in quel modo, ma è totalmente uscito in quel modo. Ho appena modificato il mio testo per renderlo più chiaro. –

+1

Penso che questo sia ancora vago. Se leggerai il mio segno di duplicazione sull'OP vedrai che si trattava di un'interfaccia. Finché si carica l'interfaccia con i due diversi classloader, non si avrà alcun vantaggio dall'uso di un'interfaccia. So che ci sono situazioni in cui un'interfaccia o una superclasse del loader della classe genitore _common aiuteranno, ma non penso che la domanda corrente appartenga a una di quelle situazioni. Puoi ancora dimostrare che ho sbagliato con un esempio ... –