In Spring, I'd like to add an exception handler programmatically, without relying on the system to scan for methods annotated with #ExceptionHandler. Is that possible?
Currently, I'm relying on the system to scan for the following method:
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<String> handle(
final HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException,
final HttpServletRequest httpServletRequest,
final HttpServletResponse httpServletResponse)
{
return ResponseEntity
.status(METHOD_NOT_ALLOWED)
.body(format(
"Unsupported method '%s' for URI '%s'; expected method among: %s.",
httpRequestMethodNotSupportedException.getMethod(),
httpServletRequest.getRequestURI(),
join(", ", asList(httpRequestMethodNotSupportedException.getSupportedMethods())
.stream()
.map(object -> "'" + object + "'")
.collect(toList()))));
}
And it works as expected, handling the HttpRequestMethodNotSupportedException and serializing the response as expected:
Unsupported method 'DELETE' for URI '/example'; expected method among: 'GET', 'POST'.
However, I know of no way to, say, use the Java config to indicate that it should use a particular class, or a particular instance, to handle specific exceptions.
I had a similar issue with adding implementations of ResponseBodyAdvice programmatically, and there is a way to do that - see here:
#Configuration
#ComponentScan(basePackageClasses = ExceptionHandler.class)
public class ConfigurationExample
extends WebMvcConfigurationSupport
{
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
#Qualifier(value = "mvcContentNegotiationManager") final ContentNegotiationManager contentNegotiationManager,
#Qualifier(value = "mvcConversionService") final FormattingConversionService conversionService,
#Qualifier(value = "mvcValidator") final Validator validator)
{
final RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.requestMappingHandlerAdapter(contentNegotiationManager, conversionService, validator);
requestMappingHandlerAdapter.setResponseBodyAdvice(Arrays.asList(
new ResponseBodyAdviceExample()));
return requestMappingHandlerAdapter;
}
}
Is there a similar method for adding custom exception handlers - or any one?
For background, my goal is to create a library of exception handlers which I can use across projects.
I don't know if there is an idiomatic way to do it, but I found a way to add exception handlers programmatically.
(I'll wait a few weeks to mark this as accepted, if anyone wants to offer an alternative.)
Code here:
https://gist.github.com/drewctaylor/f838eb8304d7c526872ed29934a1e830
Ultimately, I subclassed ExceptionHandlerExceptionResolver. Under the hood, ExceptionHandlerExceptionResolver essentially maps from java.lang.Class<java.lang.Exception> to java.lang.reflect.Method; if an exception of the given type occurs, the system returns the given method.
This implementation accepts a list of mappings from java.lang.Class<java.lang.Exception> to java.lang.reflect.Method. If an exception of a type in that list occurs, it returns the associated method; otherwise, defers to the superclass.
Code for the implementation, ExceptionHandlerExceptionResolverFromList:
public class ExceptionHandlerExceptionResolverFromList
extends ExceptionHandlerExceptionResolver
{
private final Map<Class<?>, ExceptionHandlerWithExceptionClassAndMethod<?>> map;
public ExceptionHandlerExceptionResolverFromList(
final List<ExceptionHandlerWithExceptionClassAndMethod<?>> list)
{
requireNonNull(list);
map = list.stream().collect(toMap(ExceptionHandlerWithExceptionClassAndMethod::getExceptionClass, Function.identity()));
}
#Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
#Nullable final HandlerMethod handlerMethod,
final Exception exception)
{
requireNonNull(exception);
return map.containsKey(exception.getClass()) ?
new ServletInvocableHandlerMethod(map.get(exception.getClass()), map.get(exception.getClass()).getMethod()) :
super.getExceptionHandlerMethod(handlerMethod, exception);
}
}
The mapping is defined by an implementation of the interface ExceptionHandlerWithExceptionClassAndMethod:
public interface ExceptionHandlerWithExceptionClassAndMethod<E extends Exception>
{
Class<E> getExceptionClass();
Method getMethod();
}
A specific implementation is ExceptionHandlerForHttpRequestMethodNotSupportedException, similar to the example in the question:
public class ExceptionHandlerForHttpRequestMethodNotSupportedException
implements ExceptionHandlerWithExceptionClassAndMethod<HttpRequestMethodNotSupportedException>
{
#Override
public Class<HttpRequestMethodNotSupportedException> getExceptionClass()
{
return HttpRequestMethodNotSupportedException.class;
}
#Override
public Method getMethod()
{
try
{
return getClass().getDeclaredMethod(
"handle",
HttpRequestMethodNotSupportedException.class,
HttpServletRequest.class,
HttpServletResponse.class);
}
catch (Exception exception)
{
throw new RuntimeException(exception);
}
}
public ResponseEntity<String> handle(
final HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException,
final HttpServletRequest httpServletRequest,
final HttpServletResponse httpServletResponse)
{
return ResponseEntity.status(METHOD_NOT_ALLOWED).body(
format(
"Unsupported method '%s' for URI '%s'; expected method among: %s.",
httpRequestMethodNotSupportedException.getMethod(),
httpServletRequest.getRequestURI(),
join(", ", asList(httpRequestMethodNotSupportedException.getSupportedMethods()).stream().map(object -> "'" + object + "'").collect(toList()))));
}
}
Note that the above combines the mapping with the implementation of the handler, which is not strictly necessary, but presently seems appropriate.
To add the handler to the application, extend the Configuration from WebMvcConfigurationSupport and override createExceptionHandlerExceptionResolver to return the instance of ExceptionHandlerExceptionResolverFromList:
#org.springframework.context.annotation.Configuration
public class Configuration
extends WebMvcConfigurationSupport
{
protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver()
{
return new ExceptionHandlerExceptionResolverFromList(asList(
new ExceptionHandlerForHttpRequestMethodNotSupportedException()));
}
}
The application will now use ExceptionHandlerForHttpRequestMethodNotSupportedException to handle HttpRequestMethodNotSupportedException; for other exceptions, it will use any other handlers defined.
Related
Given the following basic domain model:
abstract class BaseData { ... }
class DataA extends BaseData { ... }
class DataB extends BaseData { ... }
I want to write a Spring MVC controller endpoint thus ...
#PostMapping(path="/{typeOfData}", ...)
ResponseEntity<Void> postData(#RequestBody BaseData baseData) { ... }
The required concrete type of baseData can be inferred from the typeOfData in the path.
This allows me to have a single method that can handle multiple URLs with different body payloads. I would have a concrete type for each payload but I don't want to have to create multiple controller methods that all do the same thing (albeit each would do very little).
The challenge that I am facing is how to "inform" the deserialization process so that the correct concrete type is instantiated.
I can think of two ways to do this.
First use a custom HttpMessageConverter ...
#Bean
HttpMessageConverter httpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
#Override
public Object read(final Type type, final Class<?> contextClass, final HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// TODO How can I set this dynamically ?
final Type subType = DataA.class;
return super.read(subType, contextClass, inputMessage);
}
};
}
... which gives me the challenge to determine the subType based on the HttpInputMessage. Possibly I could use a Filter to set a custom header earlier when the URL is available to me, or I could use a ThreadLocal also set via a Filter. Neither sounds ideal to me.
My second approach would be to again use a Filter and this time wrap the incoming payload in an outer object which would then provide the type in a way that enables Jackson to do the work via #JsonTypeInfo. At the moment this is probably my preferred approach.
I have investigated HandlerMethodArgumentResolver but if I try to register a custom one it is registered AFTER the RequestResponseBodyMethodProcessor and that class takes priority.
Hmm, so after typing all of that out I had a quick check of something in the RequestResponseBodyMethodProcessor before posting the question and found another avenue to explore, which worked neatly.
Excuse the #Configuration / #RestController / WebMvcConfigurer mash-up and public fields, all for brevity. Here's what worked for me and achieved exactly what I wanted:
#Configuration
#RestController
#RequestMapping("/dummy")
public class DummyController implements WebMvcConfigurer {
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#interface BaseData {}
public static class AbstractBaseData {}
public static class DataA extends AbstractBaseData {
public String a;
}
public static class DataB extends AbstractBaseData {
public String b;
}
private final MappingJackson2HttpMessageConverter converter;
DummyController(final MappingJackson2HttpMessageConverter converter) {
this.converter = converter;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(
new RequestResponseBodyMethodProcessor(Collections.singletonList(converter)) {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(BaseData.class)
&& parameter.getParameterType() == AbstractBaseData.class;
}
#Override
protected <T> Object readWithMessageConverters(
NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
throws IOException, HttpMediaTypeNotSupportedException,
HttpMessageNotReadableException {
final String uri =
webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI();
return super.readWithMessageConverters(
webRequest, parameter, determineActualType(webRequest, uri));
}
private Type determineActualType(NativeWebRequest webRequest, String uri) {
if (uri.endsWith("data-a")) {
return DataA.class;
} else if (uri.endsWith("data-b")) {
return DataB.class;
}
throw new HttpMessageNotReadableException(
"Unable to determine actual type for request URI",
new ServletServerHttpRequest(
webRequest.getNativeRequest(HttpServletRequest.class)));
}
});
}
#PostMapping(
path = "/{type}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<? extends AbstractBaseData> post(#BaseData AbstractBaseData baseData) {
return ResponseEntity.ok(baseData);
}
}
The key to this is that I stopped using #RequestBody because that is what was preventing me overriding the built-in behaviour. By using #BaseData instead I get a HandlerMethodArgumentResolver that uniquely supports the parameter.
Other than that it was a case of assembling the two objects that already did what I needed, so autowire a MappingJackson2HttpMessageConverter and instantiate a RequestResponseBodyMethodProcessor with that one converter. Then pick the right method to override so that I could control what parameter type was used at a point that I had access to the URI.
Quick test. Given the following payload for both requests ...
{
"a": "A",
"b": "B"
}
POST http://localhost:8081/dummy/data-a
... gives a response of ...
{
"a": "A"
}
POST http://localhost:8081/dummy/data-b
... gives a response of ...
{
"b": "B"
}
In our real-world example this means that we will be able to write one method each that supports the POST / PUT. We need to build the objects and configure the validation possibly - or alternatively if we use OpenAPI 3.0 which we are investigating we could generate the model and validate without writing any further code ... but that's a separate task ;)
I've been searching for hours on the internet, as well as attempting the solutions i've found in order to work with a custom parameter annotation on a controller method.
The idea behind this is that i want to practice how to map requests, responses and all sort of things with custom annotations when working with spring.
So what i want is to create an annotation parameter which should create a Map instance, my interface is coded this way:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface SearchCriteria {
String value() default "";
}
The resolver:
public class SearchCriteriaResolver implements HandlerMethodArgumentResolver {
private Logger log = LoggerFactory.getLogger(SearchCriteriaResolver.class);
private Map<String, Object> parameters = new HashMap<>() {{
put("name", "");
put("limit", 10);
}};
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(SearchCriteria.class);
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
log.info("Parameter test: " + request.getParameter("test"));
return this.parameters;
}
}
And the configurer:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new SearchCriteriaResolver());
}
}
I've found on the internet multiple times that handlers are created this way. So in the controller, i am making use of the annotation like this:
#RestController
#RequestMapping("/series")
public class SeriesController {
private static final Logger log = LoggerFactory.getLogger(SeriesController.class);
#Autowired
SeriesService seriesService;
#GetMapping
public ResponseEntity<List<SeriesBatch>> getSeriesList(#SearchCriteria Map<String, Object> parameters) {
log.info("GET /series/ -> getSeriesList");
log.info(parameters.toString());
List<SeriesBatch> seriesList = this.seriesService.findAll();
return new ResponseEntity<>(seriesList, HttpStatus.OK);
}
...
}
So i've been checking in the logs everytime this endpoint is triggered, but the log on the resolver is not triggered, and the log in the controller method only shows an empty object. I've debugged the application start to see if the resolvers.add is being invoked, and it is, but for some reason i don't know, the logic for this annotation is not being executed.
NOTE: I am learning spring as well as taking back JAVA after a long time, so i would appreciate if an explanation on why it has to be that way on the answer is given.
Refactor your code in a way that the data you stored in a java.util.Map instance before will now be stored in some other object, e.g. an instance of a custom class SearchParams. You could even wrap your map as a member in that class to keep things simple for now:
class SearchParams {
private Map<String, Object> values = new HashMap<>();
public void setValue(String key, Object value) {
this.values.put(key, value);
}
public Object getValue(String key) {
this.values.get(key);
}
public Map<String, Object> list() {
return new HashMap<>(this.values);
}
}
Now change your controller method to accept a SearchParams object instead of Map<String, Object>:
public ResponseEntity<List<SeriesBatch>> getSeriesList(#SearchCriteria SearchParams parameters) { ... }
Last but not least you gotta change your #SearchCriteria annotation implementation, e.g. as follows:
public class SearchCriteriaResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(SearchCriteria.class);
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
// somehow determine search params
// setup + return your new SearchParams object to encapsulate your determined search params
SearchParams searchParams = new SearchParams();
searchParams.add("somekey", "somevalue");
return searchParams;
}
}
Now, plz try this out and let me know if it worked out well ;-)
Detailed explanation:
Have a look at Spring's org.springframework.web.method.support.HandlerMethodArgumentResolverComposite class, especially it's getArgumentResolver(MethodParameter parameter) method. Under the hood, Spring maintains a list of different HandlerMethodArgumentResolver instances and loops over them to find one that matches a given method argument (and later on map the method argument's value to some other object!). One of the registered HandlerMethodArgumentResolvers is of type org.springframework.web.method.annotation.MapMethodProcessor. MapMethodProcessor also matches if the parameter if of type java.util.Map and matches first (see attached screenshot below). That's why your custom HandlerMethodArgumentResolver never got called...
[[https://reversecoding.net/spring-mvc-requestparam-binding-request-parameters/]] shows an example where a java.util.Map is used as a controller method's argument -> search for '7. #RequestParam with Map'
Possible way to configure order / change priority of custom any HandlerMethodArgumentResolver in WebConfig configuration class:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// add custom resolver to beginning of resolver list
resolvers.add(0, new SearchCriteriaResolver());
}
I have an interface:
public interface ThirdPartySystemCaller {
void sendRequest(String request) throws ThirdPartySystemException;
}
And implementation:
#Slf4j
#Service
public class ThirdPartySystemCallerImpl implements ThirdPartySystemCaller {
#Override
public void sendRequest(String request) throws ThirdPartySystemException {
if (request == null) throw new ThirdPartySystemException();
log.info("send: {}", request);
}
}
And I have a CryptoService witch can sign request:
public interface CryptoService {
String signRequest(String request) throws CryptoException;
}
And It implementation:
#Slf4j
#Service
public class CryptoServiceImpl implements CryptoService {
#Override
public String signRequest(String request) throws CryptoException {
if (request.length() > 100) throw new CryptoException(); //just for example
return "signed " + request;
}
}
Now, I can use these services:
String signedRequest = cryptoService.signRequest("Hello");
thirdPartySystemCaller.sendRequest(signedRequest);
But I need to call both services each time. I want to create Proxy:
#Slf4j
#Service
public class ThirdPartySystemCallerSignedProxy implements ThirdPartySystemCaller {
private final ThirdPartySystemCaller thirdPartySystemCaller;
private final CryptoService cryptoService;
public ThirdPartySystemCallerSignedProxy(ThirdPartySystemCaller thirdPartySystemCaller, CryptoService cryptoService) {
this.thirdPartySystemCaller = thirdPartySystemCaller;
this.cryptoService = cryptoService;
}
#Override
public void sendRequest(String request) throws ThirdPartySystemException {
String signedRequest = cryptoService.signRequest(request);
thirdPartySystemCaller.sendRequest(signedRequest);
}
}
But my ThirdPartySystemCallerSignedProxy implement ThirdPartySystemCaller interface and sendRequest method throw only ThirdPartySystemException. But if cryptoService throw CryptoException I need throw it too.
How can I do it?
I was thinking to make unchecked exceptions, But I need to be checked.
Create base exception
You can create abstract exception BusinessException which can be a base exception for ThirdPartySystemException and CryptoException. Now, you can define that sendRequest method throws BusinessException and real exception depends from given problem.
Facade
ThirdPartySystemCallerSignedProxy is a bad name because it reminds Proxy pattern which is not what you have implemented. This class reminds Facade pattern because you want to create unified interface with simpler API for two different interfaces. In that case you can wrap CryptoException if it will be thrown into ThirdPartySystemException or also create base exception and declare it in method. It is even better because you do not know which kind of exception will be thrown but for sure it will be BusinessException.
Chain of Responsibility
Many libraries use Chain of Responsibility to handle request -> response communication. All chain cells need to implement the same interface with base exception in declaration if needed. You can build the chain in bean definition. It is a little bit easier to maintain because all cells are independent and does not have to know about each other as in Facade. You can build chain in #Bean method declaration like below:
#Bean
public ServiceChainCell thirdPartyCaller() {
CryptoService crypto = cryptoService();
ThirdPartySystemCaller caller = thirdPartySystemCaller();
// Create chain
crypto.setNext(caller);
return crypto;
}
setNext method comes from ServiceChainCell interface which also should have sendRequest(String request) method.
Read more about these patterns and you will find the best solution for you.
I'm trying to enrich the SLF4J MDC on each request with the user's ID. The problem is that the ID can be passed in many ways, sometimes as a path parameter, sometimes in the body, and sometimes injected by a custom ValueFactoryProvider that first decrypts it.
If I could somehow access all the injected (i.e. already deserialized) parameter values, I could handle all these cases easily.
E.g.
For a resource such as:
#GET
//#Encrypted params are injected by a custom ValueFactoryProvider
public Something getSomething(#Encrypted("userId") String userId) {
return ...;
}
#POST
public Something getSomething(#RequestBody RequestWithUserId requestWithUserId) {
return ...;
}
I could have a filter such as:
public class MdcFilter implements ContainerRequestFilter, ContainerResponseFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method theMethod = resourceInfo.getResourceMethod();
for (Parameter parameter : theMethod.getParameters()) {
//Deal with the #Encrypted case
if (parameter.isAnnotationPresent(Encrypted.class) && parameter.getAnnotation(Encrypted.class).value().equals("userId")) {
MDC.put("userId", somehowGetTheValue());
}
//Deal with the #RequestBody case
if (parameter.isAnnotationPresent(RequestBody.class) && parameter.getType().equals(RequestWithUserId.class)) {
MDC.put("userId", ((RequestWithUserId)somehowGetTheValue()).getUserId());
}
... //other possibilities
}
}
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
MDC.clear();
}
}
But I don't see a way to implement somehowGetTheValue either from a ContainerRequestFilter an interceptor or anything else...
Jersey uses HK2 under the hood for dependency injection. And HK2 has AOP support. One option for your use case would be use this AOP support. All you need to do is implement a MethodInterceptor and an InterceptionService. In the MethodInterceptor, you can get all the arguments from the MethodInvocation and you can get parameter annotation from the Method
class MyMethodInteceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
// do your logging or whatever with the args.
// invoke method and get return value.
Object returnValue = invocation.proceed();
// if you want to do something with the return
// value before returning it, you can.
return returnValue;
}
}
To use the interceptor, you configure the InterceptionService.
public class MyInterceptionService implements InterceptionService {
private final static MethodInterceptor METHOD_INTERCEPTOR
= new MyMethodInterceptor();
private final static List<MethodInterceptor> METHOD_LIST
= Collections.singletonList(METHOD_INTERCEPTOR);
#Override
public Filter getDescriptorFilter() {
return BuilderHelper.allFilter();
}
#Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
// you implement shouldIntercept
if (shouldIntercept(method)) {
return METHOD_LIST;
}
return null;
}
#Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
return null;
}
}
You determine which method should be intercepted in the getMethodInterceptors() method. If the method should be intercepted, then return a list of interceptors, otherwise return null. A common way of handling this is to create a custom annotation and just annotate the method. The in the above method, just check
if (method.isAnnotationPresent(YourAnno.class)) {
return METHOD_LIST;
}
To make it all work, you just need to register the InteceptionService with HK2. You can do that in an AbstractBinder, which is what is used in a Jersey app to configure your DI.
ResourceConfig config = new ResourceConfig();
config.register(new AbstractBinder() {
#Override
protected void configure() {
bind(MyInterceptionService.class)
.to(InterceptionService.class)
.in(Singleton.class);
}
});
You can see a complete example in this GitHub repo. There is also an official example in the HK2 site. Just see "AOP support" the link at the top of the post.
You can get it like this
StringWriter stringWriter = new StringWriter();
IOUtils.copy(new InputStreamReader(requestContext.getEntityStream()), stringWriter);
System.out.println(stringWriter.toString());// String representation of the payload
requestContext.setEntityInputStream(new ByteArrayInputStream(requestEntity));
Basically the idea is to copy the stream and do any processing and then set the stream back. Because if you don't do that, then in your controller method you would get null, becuase the stream was already read.
In Spring MVC. I'm able to use the WebRequest as a parameter that will automatically be set (along with other things such as Locale etc.).
I'm also using #RequestBody to pass in a JSON object which describes what and how data should be fetched.
Is it possible to get Spring to automatically set the WebRequest directly on the #RequestBody object EntriesRequestDTO (I would make a WebRequest field on EntriesRequestDTO). This would allow me to hide some complexity since I often need to get an attribute from WebRequest.
#RequestMapping(value = "/entries", method = { RequestMethod.POST })
public EntriesDTO getEntries(#RequestBody EntriesRequestDTO request, WebRequest webRequest){
...
}
You can use AOP to set the WebRequest to the DTO
#Around("execution(* (com.your.company..*).*(..))")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
final Object[] args = joinPoint.getArgs();
//get args[0] and args[1]
return joinPoint.proceed();
}
Make sure the regex is correct (or add custom annotation), check for cast exceptions.
I ended up making a custom HttpMessageConverter. It is not a very general solution, but it works for me:
#Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
#Bean
public DTOJackonMessageConverter customJackson2HttpMessageConverter() {
return new DTOJackonMessageConverter();
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(customJackson2HttpMessageConverter());
super.addDefaultHttpMessageConverters(converters);
}
}
The DTOJacksonMessageConverter extends MappingJackson2HttpMessageConverter and overrides read method.