使用Spring @RestController在null时返回HTTP 204

54

这将返回 200 OK,带有 Content-Length: 0

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id) {
       return null;
    }

}

简单地说,我希望它在空的情况下返回204无内容。

有没有办法强制spring-mvc/rest在空的情况下返回204而不是200? 我不想改变每个rest方法返回ResponseEntity之类的东西,只想将null映射到204。

7个回答

99
您可以使用@ResponseStatus注释。这样你就可以有一个void方法,而不必构建ResponseEntity。
@DeleteMapping(value = HERO_MAPPING)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long heroId) {
    heroService.delete(heroId);
}

顺便提一句,当对象存在时返回200,否则返回204在API REST设计中有点不寻常。通常情况下,在请求的对象不存在时返回404(未找到)。可以使用ControllerAdvice实现这一点。

在Spring REST中,最好使用异常处理程序来处理异常,而不是放置逻辑来决定响应状态等。这是一个使用@ControllerAdvice注释的示例:http://www.jcombat.com/spring/exception-handling-in-spring-restful-web-service


3
我认为原帖的意思是成功时返回200,如果为空则返回204。你可以在ResponseStatus中放置任何你想要的内容。但是想法是为一个端点设定多个可能的HTTP状态码。 - Jean-François Beauchef
我同意@Jean-FrançoisBeauchef的观点。这不是正确的处理方式。此响应状态适用于结果无论如何(排除异常情况)。提问者想知道如何满足null条件。 - Karthik R
抱歉,我不同意。在使用Spring REST时,最好使用异常处理程序来处理异常和响应代码。这是一个例子http://www.jcombat.com/spring/exception-handling-in-spring-restful-web-service - spekdrum
3
当然。你应该使用异常处理程序来处理异常。但这不是问题的答案。作者想要在正确响应时返回200,并且在没有内容(或如果您喜欢,则为null)时返回204(也是一种正确的响应)。作者甚至没有提到异常。在Java中(无论是Spring还是其他),异常不应用于流程控制。 - Jean-François Beauchef
看看mahieus和Marek Raszewski的答案,你就会明白OP的意图了。 - Jean-François Beauchef
显示剩余2条评论

41
当然可以。

选项1:

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id, HttpServletResponse response) {
       Object object = getObject();
       if( null == object ){
          response.setStatus( HttpStatus.SC_NO_CONTENT);
       }
       return object ;
    }
}

选项2:

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id) {
       Object object = getObject();
       if ( null == object ){
          return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
       }

       return object ;
    }
}

可能会有错别字,但你能理解概念。


10
可悲的是,这两个选项都不是一个好的(清洁的)解决方案。理想情况下,您的控制器方法应该返回一个类型化的对象,而不是ObjectResponseEntity,并且应该有某种拦截器注意到您正在返回null,并在那里设置NO_CONTENT状态代码(或者我只是被Jersey和RESTEasy宠坏了)。 - Shadow Man

15

我用一个过滤器解决了这个问题。它是全局的且简单易懂。

package your.package.filter;

import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class NoContentFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(httpServletRequest, httpServletResponse);
        if (httpServletResponse.getContentType() == null ||
                httpServletResponse.getContentType().equals("")) {
            httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
        }
    }
}

并在您的 web.xml 文件中添加以下内容:

<filter>
    <filter-name>restNoContentFilter</filter-name>
    <filter-class>your.package.filter.NoContentFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>restNoContentFilter</filter-name>
    <url-pattern>/rest/*</url-pattern>
</filter-mapping>

1
(HttpStatus.NO_CONTENT.value()) 我复制并粘贴了可行的解决方案 +1 票 - Yan Khonski
1
非常好的解决方案。 - Stephan

5
你可以尝试这样做:
@RestController
public class RepoController {

    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public ResponseEntity<String> getDocument(@PathVariable long id) {

       if(noError) {
           ............
           return new ResponseEntity<String>(HttpStatus.OK); 
       }
       else {
           return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
       }
   }
}

您需要将HttpStatus.BAD_REQUEST更改为204状态码的等效项。

2

虽然这个问题比较老,但对于那些需要全局答案并且使用Spring 4+的人来说,你可以创建一个ResponseBodyAdvice,根据控制器响应改变响应代码。以下示例适用于所有@RestController类:

@ControllerAdvice(annotations = { RestController.class })
public class NullToNoContentResponseBodyAdvice
    implements ResponseBodyAdvice<Object>
{
    /**
     * {@inheritDoc}
     */
    @Override
    public Object beforeBodyWrite(final Object p_responseBodyObject, final MethodParameter p_methodParameter,
                                  final MediaType p_mediaType, final Class<? extends HttpMessageConverter<?>> p_class,
                                  final ServerHttpRequest p_serverHttpRequest,
                                  final ServerHttpResponse p_serverHttpResponse)
    {
        // ------------------------- DECLARE -------------------------- //

        if (p_responseBodyObject == null)
        {
            p_serverHttpResponse.setStatusCode(HttpStatus.NO_CONTENT);
        }

        // Always return object unchanged or it will break response
        return p_responseBodyObject;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean supports(final MethodParameter p_methodParameter, final Class<? extends HttpMessageConverter<?>> p_class)
    {
        return AbstractGenericHttpMessageConverter.class.isAssignableFrom(p_class);
    }
}

我认为这是最好的答案。谢谢。 - hurelhuyag

1

使用AOP解决的相同答案:

@Aspect
public class NoContent204HandlerAspect {

  @Pointcut("execution(public * xx.xxxx.controllers.*.*(..))")
  private void anyControllerMethod() {
  }

  @Around("anyControllerMethod()")
  public Object handleException(ProceedingJoinPoint joinPoint) throws Throwable {

    Object[] args = joinPoint.getArgs();

    Optional<HttpServletResponse> response = Arrays.asList(args).stream().filter(x -> x instanceof HttpServletResponse).map(x -> (HttpServletResponse)x).findFirst();

    if (!response.isPresent())
      return joinPoint.proceed();

    Object retVal = joinPoint.proceed();
    if (retVal == null)
      response.get().setStatus(HttpStatus.NO_CONTENT.value());

    return retVal;
  }
}

你能提供更多关于这里实际发生的情况的细节吗?我正在尝试实现类似的解决方案。我的想法是自动返回204给所有具有void返回类型的方法。看起来这个解决方案需要该方法以HttpServletResponse作为输入。我想避免这种情况。 - mad_fox
尝试使用静态方式检索HttpServletResponse,例如此处。其余部分应该保持不变,但@Pointcut应仅覆盖void方法。 - Marek Raki

0

您可以自定义HttpMessageConverter以支持此功能,就像我所做的那样,将spring.http.converters.preferred-json-mapper=gson添加到application.properties配置文件中,并添加一个结果建议,如下:

@ControllerAdvice
public class CommonResultAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
    return true;
}

@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {

    if (null == o) {
    //set http code
        serverHttpResponse.setStatusCode(HttpStatus.NO_CONTENT);
        return BaseResult.success();
    }

    if (o instanceof String) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(BaseResult.success(o));
        } catch (JsonProcessingException ignore) {
        }
    }

    if (o instanceof BaseResult) {
        return o;
    }
    return BaseResult.success(o);
}

}

或者自定义一个像这样的HttpMessageConverter

@Configuration
public class BeanConfiguration {
@Bean
public HttpMessageConverter resultToJsonConverter() {
    return new GsonHttpMessageConverter();
}
}

希望能对你有所帮助。:)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接