您好,登錄后才能下訂單哦!
本篇內容介紹了“怎么解決Beanutils造成dubbo反序列化失敗問題”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
??經過測試,發現確實是我的問題。還好沒甩鍋,要不然就要被打臉了。錯誤信息如下:
{ "code": "010000", "message":"java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee", "data": null }
??看到這個錯誤有點懵,HashMap
無法轉換為AddEmployeeDTO$Employee
。內心在想,沒道理啊。請求參數我都是拷貝過來的,壓根就沒用Map
進行參數傳遞。畢竟我都是個老手了,咋可能犯這樣愚蠢的錯誤。俗話說遇到問題不要慌,讓我們掏出手機先發個朋友圈,不對好像有點跑題了,我們先看一下調用鏈的數據傳遞。
??首先web將AddEmployeeForm
數據傳遞到服務端,然后使用fromToDTO()
方法,進行將數據轉換為Dubbo請求需要的AddEmployeeDTO
。Dubbo服務放接收AddEmployeeDTO
后,使用 EmployeeConvert
將數據轉換為AddEmployeeXmlReq
再執行相關邏輯。
@Data public class AddEmployeeForm implements Serializable { /** * 職員信息列表 */ private List<Employee> employees; @Data public static class Employee implements Serializable { /** * 姓名 */ private String name; /** * 工作 */ private String job; } }
public <T, F> T formToDTO(F form, T dto) { // 進行數據拷貝 BeanUtils.copyProperties(form, dto); // 返回數據 return dto; }
@Data public class AddEmployeeDTO implements Serializable { /** * 職員信息列表 */ private List<Employee> employees; @Data public static class Employee implements Serializable { /** * 姓名 */ private String name; /** * 工作 */ private String job; } }
EmployeeConvert轉換類,使用了mapstruct進行實現,沒使用過的小伙伴可以簡單的了解下。
@Mapper public interface EmployeeConvert { EmployeeConvert INSTANCE = Mappers.getMapper(EmployeeConvert.class); AddEmployeeXmlReq dtoToXmlReq(AddEmployeeDTO dto); }
@Data public class AddEmployeeXmlReq implements Serializable { /** * 職員信息列表 */ private List<Employee> employees; @Data public static class Employee implements Serializable { /** * 姓名 */ private String name; /** * 工作 */ private String job; } }
@RestController @AllArgsConstructor public class EmployeeController { private final EmployeeRpcProvider provider; @PostMapping("/employee/add") public ResultVO employeeAdd(@RequestBody AddEmployeeForm form) { provider.add(formToDTO(form,new AddEmployeeDTO())); return ResultUtil.success(); } }
@Slf4j @Service public class EmployeeRpcServiceImpl implements EmployeeService { @Override public ResultDTO add(AddEmployeeDTO dto) { log.info("dubbo-provider-AddEmployeeDTO:{}", JSON.toJSONString(dto)); AddEmployeeXmlReq addEmployeeXmlReq = EmployeeConvert.INSTANCE.dtoToXmlReq(dto); return ResultUtil.success(); } }
??我們需要先確定異常是在consumer
拋出的還是provider
拋出的。判斷過程很簡單,我們可以進行本地debug
,看看是執行到哪里失敗了就知道了。如果不方便本地調試,我們可以在關鍵點上打上相應的日志。比如說consumer
調用前后,provider
處理前后。如果請求正常 日志打印的順序應該是:
這樣通過觀察日志就可以判定異常是在哪里拋出的了。
實際并沒有這樣麻煩,因為在consumer做了rpc異常攔截,所以我當時看了下consumer的日志就知道是provider拋出來的。
??既然找到了出問題是出在provider
,那看是什么原因導致的,從前面的調用鏈可以知道,provider
接收到AddEmployeeDTO
會使用EmployeeConvert
將其轉換為AddEmployeeXmlReq
,所以我們可以打印出AddEmployeeDTO
看看consumer
的傳參是否正常。
??通過日志我們可以發現consumer
將參數正常的傳遞過來了。那么問題應該就出在EmployeeConvert
將AddEmployeeDTO
轉換為AddEmployeeXmlReq
這里了。由于EmployeeConvert
是使用mapstruct進行實現,我們可以看看自動生成的轉換類實現邏輯是咋樣的。
??通過觀察源代碼可以發現,在進行轉換的時候需要傳入一個List<Employee>
而這個Employee
正是AddEmployeeDTO.Employee
。這個時候可能會困擾了,我明明就是傳入AddEmployeeDTO
,而且類里面壓根就沒有Map
,為啥會拋出java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee
這個異常呢?
讓我們Debug
一下看看發生了啥。
??這個時候你會發現接收到的AddEmployeeDTO.employees
內存儲的并不是一個AddEmployeeDTO$Employee
對象,而是一個HashMap
。那看來真相大白了,原來是dubbo反序列化的時候將AddEmployeeDTO$Employee
轉換為HashMap
了。從而導致了java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee
異常的拋出。
??為啥Dubbo
反序列化時會將AddEmployeeDTO$Employee
變成Map
呢?我們回過頭看看之前打印參數的日志,有一個警告日志提示了java.lang.ClassNotFoundException:com.aixiao.inv.api.model.form.AddEmployeeForm$Employee
,找不到AddEmployeeForm$Employee
這個就有點奇怪了,為啥不是AddEmployeeDTO$Employee
?
??在進行dubbo
調用前AddEmployeeForm
會使用fromToDTO()
方法將其轉化為AddEmployeeDTO
。那么問題會不會出現在這里呢?我們繼續Debug
看看。
??嘔吼,這下石錘了。原來是在formToDTO
的時候出問題了。傳遞過去AddEmployeeDTO
內部的Employee
竟然變成了AddEmployeeForm$Employee
。這也是為什么provider
那邊會拋出java.lang.ClassNotFoundException:com.aixiao.inv.api.model.form.AddEmployeeForm$Employee
的原因了。審查一下formToDTO
的代碼看看為啥會發生這樣的情況:
public <T, F> T formToDTO(F form, T dto) { // 進行數據拷貝 BeanUtils.copyProperties(form, dto); // 返回數據 return dto; }
??fromToDTO
內的代碼非常精簡,就一個BeanUtils.copyProperties()
的方法,那毫無疑問它就是罪魁禍首了。通過在baidu的海洋里遨游,我找到了原因。原來是BeanUtils
是淺拷貝造成的。淺拷貝只是調用子對象的set方法,并沒有將所有屬性拷貝。(也就是說,引用的一個內存地址),所以在轉換的時候,將AddEmployeeDTO
內的employees
屬性指向了AddEmployeeForm
的employees
的內存地址。所以將在進行調用時,Dubbo
因為反序列化時找不到對應的類,就會將其轉換為Map
。
??上面的問題,主要是由于BeanUtils淺拷貝造成。并且引發連鎖反應,造成Dubbo
反序列化異常以及EmployeeConvert
的轉換異常,最后拋出了java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee
錯誤信息。
??既然知道了問題出現的原因,那么解決起來就很簡單了。對于單一的屬性,那么不涉及到深拷貝的問題,適合用BeanUtils繼續進行拷貝。但是涉及到集合我們可以這樣處理:
簡單粗暴使用foreach進行拷貝。
使用labmda實現進行轉換。
AddEmployeeDTO dto = new AddEmployeeDTO(); dto.setEmployees(form.getEmployees().stream().map(tmp -> { AddEmployeeDTO.Employee employee = new AddEmployeeDTO.Employee(); BeanUtils.copyProperties(tmp,employee); return employee; }).collect(Collectors.toList()));
封裝一個轉換類進行轉換。
AddEmployeeDTO dto = new AddEmployeeDTO(); dto.setEmployees(convertList(form.getEmployees(),AddEmployeeDTO.Employee.class)); public <S, T> List<T> convertList(List<S> source, Class<T> targetClass) { return JSON.parseArray(JSON.toJSONString(source), targetClass); }
“怎么解決Beanutils造成dubbo反序列化失敗問題”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。