使用Jackson和Spring Boot根据请求美化(或不美化)响应

3
我正在使用Spring Boot、Spring MVC和Jackson。我正在编写RESTful API,其中一个要求是在请求中只包含参数时,美化响应(json)。 例如,GET /examples/1必须回答:{"id":1,"name":"example"},而GET /examples/1?prettify则回答如下:
{
    "id": 1,
    "name": "example"
}

我知道我可以定义自己的 ObjectMapper,但是我不能让它依赖于请求。我可以将 spring.jackson.serialization.INDENT_OUTPUT 设为 true,但我不能显示一个未经美化处理的响应。
有没有人对此有什么想法?

只是一个“大声思考”,但我会尝试在REST API控制器中注入Jackson2ObjectMapper,然后根据请求调用其方法indentOutput(boolean)。 我相信这个主题可能会有所帮助:https://dev59.com/Ql4c5IYBdhLWcg3wFm84 - patrykos91
问题是,我可以注入一个新的Jackson2ObjectMapper,但是在注入之后(在应用程序启动时完成),我没有看到一种方法来“注入”请求到Jackson2ObjectMapper中。 - Happy
1个回答

2

好的,我已经完成了。

因此,要做到这一点的方法是使用注入的Jackson2ObjectMapper手动序列化响应 :)

让我们看一个简单的主应用程序类:

@SpringBootApplication
public class StackoverflowApplication {

    public static void main(String[] args) {
        SpringApplication.run(StackoverflowApplication.class, args);
    }

    @Bean
    public Jackson2ObjectMapperBuilder objectMapperBuilder() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.indentOutput(true);
        return builder;
     }
}

一个REST控制器类:

@RestController
public class TestRestController {
    private static final Logger LOGGER = Logger.getLogger(TestRestController.class);

    @Autowired
    Jackson2ObjectMapperBuilder objectMapperBuilder;

    @RequestMapping(value = "/testendpoint/{somevalue}", method = RequestMethod.GET, produces="application/json")
    public @ResponseBody String customersLastVisit(@PathVariable(value = "somevalue") Integer number,
            @RequestParam(value = "pretify", defaultValue = "false") Boolean pretify) throws JsonProcessingException {
        if (pretify) {
            LOGGER.info("Pretify response!");
            objectMapperBuilder.indentOutput(true);
        }
        else {
            objectMapperBuilder.indentOutput(false);
        }

        ObjectMapper mapper = objectMapperBuilder.build();
        String jsonResponse = mapper.writeValueAsString(new TestDTO());
        return jsonResponse;
    }
}

DTO类:

public class TestDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer id;
    private String name;

    public TestDTO() {
        super();
        id = new Integer(1);
        name = new String("SomeName");
    }
    //getters, setters etc ...
}

http://localhost:8080/testendpoint/1 返回 {"id":1,"name":"SomeName"}http://localhost:8080/testendpoint/1?pretify=true 返回

{
  "id" : 1,
  "name" : "SomeName"
}

编辑: 如果您想在每个控制器中使用它,请按照以下方式操作:

public class PretifyingInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = Logger.getLogger(PretifyingInterceptor.class);

    @Autowired
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        String pretify = request.getParameter("pretify");
        Boolean isPretify = Boolean.parseBoolean(pretify);

        LOGGER.info("Should be pretified: " + isPretify);

        if (isPretify) {
            mappingJackson2HttpMessageConverter.setPrettyPrint(true);
        }
        else {
            mappingJackson2HttpMessageConverter.setPrettyPrint(false);
        }
        return true;
    }
}

现在添加新的拦截器:

@Configuration
public class InterceptorConfigurerAdapter extends WebMvcConfigurerAdapter{


    @Bean
    PretifyingInterceptor pretifyingInterceptor() {
        return new PretifyingInterceptor();
    }

     @Override
        public void addInterceptors(final InterceptorRegistry registry) {
            registry.addInterceptor(pretifyingInterceptor())
                    .addPathPatterns("/**");
        }

}

现在控制器可以看起来像这样:

@RequestMapping(value = "/testendpoint/{somevalue}", method = RequestMethod.GET, produces = "application/json")
    public @ResponseBody TestDTO customersLastVisit(@PathVariable(value = "somevalue") Integer number,
            @RequestParam(value = "pretify", defaultValue = "false") Boolean pretify) throws JsonProcessingException {
        return new TestDTO();
    }

编辑2: 为了避免拦截器之间共享状态,将拦截器Bean的作用域定义为请求(request)
希望这有所帮助 :)

您可以直接使用@Autowired自动注入ObjectMapper,并使用writerWithDefaultPrettyPrinter() - Cyril
所以,我们已经有了一种方法来处理一个请求。但是我们希望对每个请求都进行处理,使用拦截器可能是一种方式。我正在寻找覆盖Jackson拦截器的方法,但我还没有找到。 - Happy
@patrykos91 感谢您的更新。看了您的回答,我想我们可能会遇到一些并发问题。例如:线程A在方法preHandle的末尾停止,带有pretiffy=true。线程B使用pretiffy=false完成整个工作。线程A结束工作:结果将不会被美化。 我认为我们需要重新实现JSON序列化器。 - Happy
不一定。在开始重新实现json序列化器之前最好先测试一下。我认为这完全取决于通过MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;获得的objectMapper的范围是什么。如果它的作用域是“请求”,那么每个请求都将有自己的实例,而且不会发生冲突。 - patrykos91
编辑:增加了一些基准测试,但是效果不好。我正在寻找一个好的方法,如果我找到了,我会发布我的答案。 - Happy
显示剩余2条评论

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