1. Overview
In this tutorial, we'll investigate the ResponseBodyAdvice interface in Spring MVC.
By using its implementations, we can modify the payload before Spring MVC writes it to the response body. It is the response-related counterpart of RequestBodyAdvice and has similar features in terms of usage and registration.
2. Sample Application
Let's start with our sample application.
@RestController
public class QuestionController {
@PostMapping(value = "/ask", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public Answer ask(@RequestBody Question question) {
System.out.println("In controller method");
Answer answer = new Answer();
answer.setAnswerMessage("I don't know!");
answer.setQuestion(question);
return answer;
}
}
In the QuestionController class, we have a single endpoint which returns an Answer in the response body. Since we're annotating QuestionController with @RestController, all endpoint methods will implicitly have the @ResponseBody annotation. As a result, Spring MVC will write the method return value to the response.
public class Question {
private String questionMessage;
private Date date;
// Getters & setters
}
public class Answer {
private String answerMessage;
private Question question;
// Getters & setters
}
Then we have the Question and Answer classes which hold the related fields.
3. Implementing ResponseBodyAdvice
ResponseBodyAdvice allows the customization of the response object before Spring MVC writes it to the response body.
Let's provide a basic implementation to investigate the interface methods:
@ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Answer> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
System.out.println("In supports() method of " + getClass().getSimpleName());
return returnType.getContainingClass() == QuestionController.class && returnType.getParameterType() == Answer.class;
}
@Override
public Answer beforeBodyWrite(Answer answer, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
System.out.println("In beforeBodyWrite() method of " + getClass().getSimpleName());
answer.setAnswerMessage(answer.getAnswerMessage() + " by Spring");
return answer;
}
}
In the CustomResponseBodyAdvice class, we're implementing two methods.
Firstly, the supports method decides whether this implementation should run for the current response. To perform this decision, Spring MVC provides the return value type and converter type. In our example, we're checking the types of the controller and return value.
Then, we have the beforeBodyWrite method. It runs after the execution of the controller method but before the response is written. Here we have the chance to make modifications to our response object. In our case, we're changing the answerMessage field of the Answer.
4. Invocation Order
Now, let's see in which order Spring MVC calls our ResponseBodyAdvice and controller methods.
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class QuestionControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void shouldApplyAdvices() throws Exception {
Question question = new Question();
question.setQuestionMessage("How is weather?");
mockMvc.perform(post("/ask")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(question))
.accept(MediaType.APPLICATION_JSON_VALUE))
.andExpect(status().isOk());
}
}
Here, we have a simple test case which calls our /ask endpoint. When we run the test, the print statements outline the order:
In controller method
In supports() method of CustomResponseBodyAdvice
In beforeBodyWrite() method of CustomResponseBodyAdvice
The controller methods run first. Then Spring MVC invokes the ResponseBodyAdvice methods.
5. Registering ResponseBodyAdvice
In order to register the ResponseBodyAdvice implementations, we have two ways.
Primarily, we can annotate our implementation with @ControllerAdvice - as we did previously:
@ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Answer> {
// Implementation
}
Also, we can register a ResponseBodyAdvice implementation manually by using the RequestMappingHandlerAdapter class:
@Configuration
public class CustomWebMvcConfiguration extends DelegatingWebMvcConfiguration {
@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.requestMappingHandlerAdapter();
List<ResponseBodyAdvice<?>> responseBodyAdvices = new ArrayList<>();
responseBodyAdvices.add(new CustomResponseBodyAdvice());
requestMappingHandlerAdapter.setResponseBodyAdvice(responseBodyAdvices);
return requestMappingHandlerAdapter;
}
}
Here, we're extending the DelegatingWebMvcConfiguration class and marking it as @Configuration. Then we're creating a RequestMappingHandlerAdapter bean where we're also registering our ResponseBodyAdvice implementation.
6. Summary
In this tutorial, we've investigated theResponseBodyAdvice interface to customize the way Spring MVC writes to the response body.
Finally, check out the source code for all examples in this tutorial over on Github.