preface

This afternoon, after an hour of intense “key” “disease” code “, ready to take a good review of their own code, after a period of review (touch almost, it is time to leave work), came to the conclusion that THE code I wrote is very elegant, concise. So a big hand to submit the code, and the API management system on the XXX interface to complete. Ready to pack up and leave on time. However, it didn’t take long for the front brother to @ me, said XXX interface has a problem, please deal with it. Heart the first reaction (your ya’s parameters pass wrong) humble I can only silently back, good, trouble to give me the parameters, MY side check [smile face].

Scenario reduction

After testing, it was found to be my problem. Good thing you didn’t, or you’d have gotten slapped in the face. The error message is as follows:

{
  "code": "010000"."message":"java.util.HashMap cannot be cast to com.aixiao.inv.common.dto.tax.AddEmployeeDTO$Employee"."data": null
}
Copy the code

A bit dumbstruck by this error, HashMap cannot be converted to AddEmployeeDTO$Employee. I’m thinking, this doesn’t make any sense. I copied all the request parameters and didn’t use Map to pass them. After all, I’m such an old hand that I couldn’t possibly make such a stupid mistake. As the saying goes, don’t panic when you encounter problems. Let’s take out our mobile phones and send a circle of friends first. It seems to be a bit off topic.

The Web first passes the AddEmployeeForm data to the server and then converts the data to the AddEmployeeDTO required by the Dubbo request using the fromToDTO() method. After the Dubbo service receives AddEmployeeDTO, it uses EmployeeConvert to convert the data to AddEmployeeXmlReq and then executes the related logic.

AddEmployeeForm class

@Data
public class AddEmployeeForm implements Serializable {

    /** * Employee information list */
    private List<Employee> employees;

    @Data
    public static class Employee implements Serializable {

        /** * name */
        private String name;

        /** * work */
        privateString job; }}Copy the code

FormToDTO () method

public <T, F> T formToDTO(F form, T dto) {

    // Copy the data
    BeanUtils.copyProperties(form, dto);

    // Return data
    return dto;
}
Copy the code

AddEmployeeDTO class

@Data
public class AddEmployeeDTO implements Serializable {

    /** * Employee information list */
    private List<Employee> employees;

    @Data
    public static class Employee implements Serializable {

        /** * name */
        private String name;

        /** * work */
        privateString job; }}Copy the code

EmployeeConvert transformation class

EmployeeConvert conversion class, implemented using mapstruct.

@Mapper
public interface EmployeeConvert {

    EmployeeConvert INSTANCE = Mappers.getMapper(EmployeeConvert.class);
		
    AddEmployeeXmlReq dtoToXmlReq(AddEmployeeDTO dto);

}
Copy the code

AddEmployeeXmlReq class

@Data
public class AddEmployeeXmlReq implements Serializable {

    /** * Employee information list */
    private List<Employee> employees;

    @Data
    public static class Employee implements Serializable {

        /** * name */
        private String name;

        /** * work */
        privateString job; }}Copy the code

EmployeeController

@RestController
@AllArgsConstructor
public class EmployeeController {

    private final EmployeeRpcProvider provider;

    @PostMapping("/employee/add")
    public ResultVO employeeAdd(@RequestBody AddEmployeeForm form) {
        provider.add(formToDTO(form,new AddEmployeeDTO()));
        returnResultUtil.success(); }}Copy the code

EmployeeRpcServiceImpl

@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);
        returnResultUtil.success(); }}Copy the code

The analysis reason

Determine the exception throw point

We need to determine whether the exception is thrown by the consumer or the provider. The process is easy to determine, we can do local debug, see where the execution failed to know. If local debugging is not convenient, we can log the key points accordingly. For example, before and after the consumer call, or the provider process. If the request is normal, the log should be printed in the following order:

By looking at the log, you can determine where the exception was thrown.

In fact, there was no such trouble, because the consumer did RPC exception interception, so I looked at the log of the consumer and knew that it was thrown by the provider.

Find the wrong code

When the provider receives AddEmployeeDTO, it uses EmployeeConvert to convert it to AddEmployeeXmlReq. When the Provider receives AddEmployeeXmlReq, it uses EmployeeConvert to convert the AddEmployeeXmlReq. So we can print out AddEmployeeDTO to see if the consumer’s input is normal.

From the log, we can see that the consumer passed the parameter normally. EmployeeConvert converts AddEmployeeDTO to AddEmployeeXmlReq. Since EmployeeConvert is implemented using Mapstruct, we can take a look at how the auto-generated transformation class implementation logic looks.

If you look at the source code, you can see that you need to pass in a List

to make the transformation and that Employee is addemployeedto.employee. I’m passing in AddEmployeeDTO, and there’s no Map in the class, Why sell Java. Util. HashMap always be cast to com.aixiao.inv.com mon. Dto. Tax. AddEmployeeDTO $Employee this exception?

Let’s Debug to see what’s going on.

When you receive AddEmployeeDTO. Employees, instead of an AddEmployeeDTO$Employee object, you will find a HashMap. Dubbo deserialized AddEmployeeDTO$Employee to HashMap. Leading to a Java. Util. HashMap always be cast to com.aixiao.inv.com mon. Dto. Tax. AddEmployeeDTO $Employee exception thrown.

You thought it was over?

Why does Dubbo deserialize AddEmployeeDTO$Employee into a Map? Before we go back see print log parameters, has prompted a warning log Java lang. ClassNotFoundException: com. Aixiao. Inv. API. The model. The form. The AddEmployeeForm $Employee, AddEmployeeForm$Employee = AddEmployeeDTO$Employee

AddEmployeeForm uses the fromToDTO() method to convert it to AddEmployeeDTO before making the dubbo call. So is there a problem here? Let’s go ahead and Debug.

Oh, oh, oh, oh, oh, oh, oh. It turned out that something went wrong during formToDTO. The Employee passed inside AddEmployeeDTO becomes AddEmployeeForm$Employee. . This is why the provider side throws Java lang, a ClassNotFoundException: com. Aixiao. Inv. API. The model. The form. The AddEmployeeForm $Employee. Take a look at the formToDTO code to see why this is happening:

public <T, F> T formToDTO(F form, T dto) {

    // Copy the data
    BeanUtils.copyProperties(form, dto);

    // Return data
    return dto;
}
Copy the code

The code in fromToDTO is very lean, just a beanutils.copyProperties () method, which is definitely the culprit. I found out the reason by travelling in the sea of Baidu. It turns out that BeanUtils are shallow copies. Shallow copy simply calls the set method of the child object and does not copy all the attributes. (that is, a referenced memory address), so the employees property in AddEmployeeDTO points to the memory address of Employees in AddEmployeeForm during conversion. So when the call is made, Dubbo will convert the corresponding class to Map because it could not be found during deserialization.

The subtotal

The above problem is mainly caused by shallow copies of BeanUtils. And cause a chain reaction that causes Dubbo deserialization exceptions and EmployeeConvert conversion exceptions, Finally throw the Java. Util. HashMap always be cast to com.aixiao.inv.com mon. Dto. Tax. AddEmployeeDTO $Employee error information.

The solution

Now that you know the cause of the problem, it’s easy to fix it. For a single attribute, there is no deep copy involved and it is appropriate to continue copying with BeanUtils. But when it comes to sets we can do this:

  1. Simply copy using foreach.

  2. Use labMDA implementation for conversion.

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()));
Copy the code
  1. Encapsulate a conversion class for conversion.
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);
}
Copy the code

conclusion

  1. Copying with beanutils.copyProperties () requires caution
  2. When dubbo deserializes a class, it converts it to a Map if it can’t find one.

reference

  • Use of beanutils.copyProperties (deep copy, shallow copy)

At the end

I am a different kind of tech nerd, making progress every day and experiencing a different life. See you next time!

If you feel helpful to you, you can comment more, more like oh, you can also go to my home page to have a look, maybe there is an article you like, you can also click a concern oh, thank you.