SonarLint:Return 一个空集合而不是 null

SonarLint: Return an empty collection instead of null

我正在 ajax 调用一个 returns 对象列表的方法,如果在 try-catch 块中获取数据时发生某些事情,我有一个 response.setStatus(400) 然后在前端显示错误,在那里我返回 null,在那里我收到 SonarLint 通知。现在,如果我将其更改为空集合,则会出现以下错误:

getWriter() has already been called for this response

我认为以上是因为我返回的是空集合和 HTTP 响应状态 400。如果我将其保留为空,则一切正常,只是 SonarLint 通知。

@GetMapping("/runquery")
@ResponseBody
public List<Map<String, Object>> runQuery(@RequestParam(name = "queryId") String queryId, @RequestParam(name = "formData") String formData, HttpServletResponse response) throws IOException {
    (...)

    try {
        queryResult = namedParameterJdbcTemplateHive.queryForList(query, paramSource);

        for (Map<String, Object> map : queryResult) {
            Map<String, Object> newMap = new HashMap<>();
            for (Map.Entry<String, Object> entry : map.entrySet()) {                    
                String key = entry.getKey();
                Object value = entry.getValue();

                if (key.contains(".")) {
                    key = key.replace(".", "_");
                    newMap.put(key, value);
                } else {
                    newMap.put(key, value);
                }
            }
            queryResultFinal.add(newMap);
        }


    } catch (Exception e) {
        response.setStatus(400);
        response.getWriter().write(e.getMessage());
        return null;  <-- SonarLint notification
    }

    return queryResultFinal;        
}

知道如何修复此通知吗?

我建议不要在此方法中捕获异常,而是抛出它,并在控制器中使用 exception handler method 来处理它。在那种情况下,你永远不会 return null 从方法中,Sonar 将没有什么可抱怨的。这也意味着您正在按照设计使用的方式使用 Spring。

例如,如下所示:

@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public void handleException(Exception e) {
    log.error("Exception during request", e);
}

或您当前处理的直接等价物:

@ExceptionHandler
public ResponseEntity<?> handleException(Exception e) {
    return ResponseEntity.badRequest().body(e.getMessage()).build();
}

切换到异常处理程序后,您可以从常规方法中删除 HttpServletResponse response 参数。

我建议你创建一个 GenericReponse 来包装你的所有回复,这对前端来说非常好,因为你面对的是一个固定的模板。

因此,通过这个解决方案,您可以包装任何您想要的对象并将其发送到响应。


我这样编写场景:

1- 创建一个通用响应 Class

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class GenericResponse {

    private Boolean error;
    private List<ErrorPayload> errorPayload;
    private Object payload;

    public GenericResponse(Boolean error) {
        this.error = error;
    }

    public static GenericResponse ok() {
        return new GenericResponse(false);
    }

     public GenericResponse payload(Serializable o) {
         this.payload = o;
         return this;
     }

    //Getters and Setters and other Constructors

2-创建ErrorPayloadClass

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorPayload {

    private String errorMessage;
    private String errorType;

//Getters and Setters and Constructors
}

3-创建ExceptionConverter服务(当我们有异常时使用)

@Service
public class ExceptionConverterService {

    public GenericResponse convert(Exception x) {

        GenericResponse genericResponse = new GenericResponse();
        genericResponse.setError(true);
        String exceptionType = x.getClass().getSimpleName();
        String exceptionMessage = x.getClass().getSimpleName();
        genericResponse.setErrorPayload(Collections.singletonList(new ErrorPayload(exceptionType, exceptionMessage)));
        return genericResponse;

    }

}

4-使用 GenericResponse 改变您的场景

您只需:

  1. 创建上述类(复制我在1、2、3中写的代码)
  2. 将您的回复表单 List<Map<String, Object>> 更改为 GenericResponse
  3. 将您的 return 类型包装成 GenericResponse

我更改了你的代码如下(只需更改 3 行)

@RestController
public class TestController {

    @Autowired
    private ExceptionConverterService exceptionConverter;

    @GetMapping("/runquery")
    @ResponseBody
    //Changed (Change Return type to GenericResponse )
    public GenericResponse runQuery(@RequestParam(name = "queryId") String queryId, @RequestParam(name = "formData") String formData, HttpServletResponse response) throws IOException {


        try {
            //Your code
            }

        } catch (Exception e) {

            //Changed (Create GenericResponse for Exception)
            GenericResponse genericResponse = exceptionConverter.convert(e);
            return genericResponse;
        }

        //Changed (Create GenericResponse for main result)
        return GenericResponse.ok().payload(queryResultFinal);
    }

}

两种情况的示例(第一种情况无例外,第二种情况有例外)

示例 1

带有 GenericResponse 的控制器(我们在这个示例中也不例外)

@RestController
public class TestController {

    @GetMapping(value = "/getNameAndFamily")
    public GenericResponse getNameAndFamily() {

        Map<String, String> person = new HashMap<>();
        person.put("name", "foo");
        person.put("family", "bar");
        return GenericResponse.ok().payload((Serializable) person);
    }

}

结果如下:

{
    "error": false,
    "payload": {
        "name": "foo",
        "family": "bar"
    }
}

示例 2

当我们在业务中遇到异常时使用 GenericResponse 的控制器

@RestController
public class TestController {

    @Autowired
    private ExceptionConverterService exceptionConverter;

    @GetMapping(value = "/getNameAndFamily")
    public GenericResponse getNameAndFamily() {

        try {

            //Create Fake Exception
            int i = 1 / 0;
            return GenericResponse.ok();
        } catch (Exception e) {

            //Handle Exception
            GenericResponse genericResponse = exceptionConverter.convert(e);
            return GenericResponse.ok().payload((Serializable) genericResponse);

        }
    }

}

结果如下:

{
    "error": true,
    "errorPayload": [
        {
            "errorType": "ArithmeticException"
        }
    ]
}