In Spring MVC, I am writing something using a controller, essentially performing REST plus a few additions.
Most of the functionality is called from ExtJS to spring, and to adhere to their conventions, the return is always a json object in the format:
{
success: true,
data: { ... }
}
or in the case of an error:
{
success: false,
message: { ... }
}
For the exceptions it is easy - Spring provides #ControllerAdvice and #ExceptionHandler etc so that errors can be trapped in one place and the standard response works.
However is there an easy way to force all of the valid responses to be passed through in the same way, so that the success element could be sent every time with the data sent back in the data object. in other words, currently for every call I have to do something like
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public HashMap<String, Object> add(#RequestBody ManagedView targetTest) {
ManagedView result = service.add(targetTest);
HashMap<String, Object> ret = new HashMap<String, Object>();
ret.put("success", new Boolean(true));
ret.put("data", result);
return ret;
}
whereas I would like to do is have something like
#RequestMapping(method = RequestMethod.POST)
public ManagedView add(#RequestBody ManagedView targetTest) {
return service.add(targetTest);
}
// and each response intercepted by something like this
public HashMap<String, Object> addWrapper(Object result) {
HashMap<String, Object> ret = new HashMap<String, Object>();
ret.put("success", new Boolean(true));
ret.put("data", result);
return ret;
}
I appreciate I could just create a standard utility type function to do this but is there something that does this out of the box without me having to add the same code to all of the methods - preferably set globally.
Thanks in advance
I think you can use org.springframework.web.servlet.HandlerInterceptor.
It contains preHandle(),postHandle() and afterCompletion() methods. In your case you can use postHandle() method to manipulate the ModelAndView object before render it to view page,and it will be common point for all the controllers.
This is a solution I just used, I went through several approaches until I found this one, which is the simplest one I think:
#ControllerAdvice
public class StandardResponseBodyWrapAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
returnType.getMethodAnnotation(ResponseBody.class) != null);
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
ApiResponseDTO<Object> resp = new ApiResponseDTO<>();
resp.setVersion("1.0");
resp.setSuccess(true);
resp.setData(body);
return resp;
}
}
Related
I'm working on REST API for the application using SpringBoot 2 and I want to get a specific object as my request parameter. Assuming I have my endpoint declared like this:
#RestController("TestEndpoint")
#RequestMapping(path = "/", produces = MediaType.APPLICATION_JSON_VALUE)
public class TestEndpoint {
#RequestMapping(method = RequestMethod.GET, value = "/test")
String getTest(MyWeirdRequest myRequest) {
return myRequest.toString();
}
}
where MyWeirdRequest looks like this:
public class MyWeirdRequest {
private Map<String, String> startWithOne;
private Map<String, String> anythingElse;
// setters and getters here
}
and my GET request would look like this example:
http://localost:8088/test?first=aaa&second=1bbb&third=1ccc&fourth=2ddd
List of parameters is not defined anywhere and so they can contain any keys.
I want my endpoint to get MyWeirdRequest object where all the params with value starting with 1 to be in startWithOne map and the rest of params to be in anythingElse where key is request parameter name. So in case of request above I want the result where my endpoint receives a MyWeirdRequest containing
startWithOne=[second:1bbb, third:1ccc]
anythingElse=[first:aaa, fourth:2ddd]
I know I could use a Map as a getTest param and then do all the mapping inside this method, but MyWeirdRequest will be used as a param for multiple endpoints and I want to avoid working with Maps directly everywhere.
I tried to create a custom PropertyEditor and register it in WebDataBinder, but it is only used if there is a #Requestparam annotation, but if I add it to getTest method - a parameter named myRequest becomes mandatory.
How do I handle the request like that?
So I figured out that in case you need to parse request parameters as I needed - you should implement HandlerMethodArgumentResolver. It provides the ability to access a lot of different request data. In my case it could look like this (initially I've forgot that request parameters may contain arrays of values, so MyWeirdRequest field types were changed to Map<String, String[]>):
public class TestArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(MyWeirdRequest.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory)
throws Exception {
MyWeirdRequest result = new MyWeirdRequest();
Map<String, String[]> startsWithOne = new HashMap<>();
Map<String, String[]> anythingElse = new HashMap<>();
for (Map.Entry<String, String[]> paramEntry : webRequest.getParameterMap().entrySet()) {
String[] swoValues = Arrays.stream(paramEntry.getValue()).filter(v -> v.startsWith("1"))
.collect(Collectors.toList()).toArray(new String[0]);
if (swoValues.length > 0) {
startsWithOne.put(paramEntry.getKey(), swoValues);
}
String[] aeValues = Arrays.stream(paramEntry.getValue()).filter(v -> !v.startsWith("1"))
.collect(Collectors.toList()).toArray(new String[0]);
if (aeValues.length > 0) {
anythingElse.put(paramEntry.getKey(), aeValues);
}
}
result.setStartWithOne(startsWithOne);
result.setAnythingElse(anythingElse);
return result;
}
}
After that, I created a configuration to register my request resolver:
#Configuration
public class TestRequestConfiguration implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new TestArgumentResolver());
}
}
And that's pretty much it!
All requests and responses handled by our Spring Rest Controller has a Common section which has certain values:
{
"common": {
"requestId": "foo-bar-123",
"otherKey1": "value1",
"otherKey2": "value2",
"otherKey3": "value3"
},
...
}
Currently all my controller functions are reading the common and copying it into the response manually. I would like to move it into an interceptor of some sort.
I tried to do this using ControllerAdvice and ThreadLocal:
#ControllerAdvice
public class RequestResponseAdvice extends RequestBodyAdviceAdapter
implements ResponseBodyAdvice<MyGenericPojo> {
private ThreadLocal<Common> commonThreadLocal = new ThreadLocal<>();
/* Request */
#Override
public boolean supports(
MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return MyGenericPojo.class.isAssignableFrom(methodParameter.getParameterType());
}
#Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
var common = (MyGenericPojo)body.getCommon();
if (common.getRequestId() == null) {
common.setRequestId(generateNewRequestId());
}
commonThreadLocal(common);
return body;
}
/* Response */
#Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return MyGenericPojo.class.isAssignableFrom(returnType.getParameterType());
}
#Override
public MyGenericPojo beforeBodyWrite(
MyGenericPojo body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
body.setCommon(commonThreadLocal.get());
commonThreadLocal.remove();
return body;
}
}
This works when I test sending one request at a time. But, is it guaranteed that afterBodyRead and beforeBodyWrite is called in the same thread, when multiple requests are coming?
If not, or even otherwise, what is the best way of doing this?
I think that there is no need of your own ThreadLocal you can use request attributes.
#Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
var common = ((MyGenericPojo) body).getCommon();
if (common.getRequestId() == null) {
common.setRequestId(generateNewRequestId());
}
Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.map(ServletRequestAttributes::getRequest)
.ifPresent(request -> {request.setAttribute(Common.class.getName(), common);});
return body;
}
#Override
public MyGenericPojo beforeBodyWrite(
MyGenericPojo body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.map(rc -> rc.getAttribute(Common.class.getName(), RequestAttributes.SCOPE_REQUEST))
.ifPresent(o -> {
Common common = (Common) o;
body.setCommon(common);
});
return body;
}
EDIT
Optionals can be replaced with
RequestContextHolder.getRequestAttributes().setAttribute(Common.class.getName(),common,RequestAttributes.SCOPE_REQUEST);
RequestContextHolder.getRequestAttributes().getAttribute(Common.class.getName(),RequestAttributes.SCOPE_REQUEST);
EDIT 2
About thread safety
1) standard servlet-based Spring web application we have thread-per-request scenario. Request is processed by one of the worker threads through all the filters and routines. The processing chain will be executed by the very same thread from start to end . So afterBodyRead and beforeBodyWrite guaranteed to be executed by the very same thread for a given request.
2) Your RequestResponseAdvice by itself is stateless. We used RequestContextHolder.getRequestAttributes() which is ThreadLocal and declared as
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
And ThreadLocal javadoc states:
his class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via
its get or set method) has its own, independently initialized copy of
the variable.
So I don't see any thread-safety issues into this sulotion.
Quick answer: RequestBodyAdvice and ResponseBodyAdvice are invoked within the same thread for one request.
You can debug the implementation at: ServletInvocableHandlerMethod#invokeAndHandle
The way you're doing it is not safe though:
ThreadLocal should be defined as static final, otherwise it's similar to any other class property
Exception thrown in body will skip invocation of ResponseBodyAdvice (hence the threadlocal data is not removed)
"More safe way": Make the request body supports any class (not just MyGenericPojo), in the afterBodyRead method:
First call ThreadLocal#remove
Check if type is MyGenericPojo then set the common data to threadlocal
Also I have already answered this thread, but I prefer another way to solve such kind of problems
I would use Aspect-s in this scenario.
I have written included this in one file but you should create proper separate classes.
#Aspect
#Component
public class CommonEnricher {
// annotation to mark methods that should be intercepted
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface EnrichWithCommon {
}
#Configuration
#EnableAspectJAutoProxy
public static class CommonEnricherConfig {}
// Around query to select methods annotiated with #EnrichWithCommon
#Around("#annotation(com.example.CommonEnricher.EnrichWithCommon)")
public Object enrich(ProceedingJoinPoint joinPoint) throws Throwable {
MyGenericPojo myGenericPojo = (MyGenericPojo) joinPoint.getArgs()[0];
var common = myGenericPojo.getCommon();
if (common.getRequestId() == null) {
common.setRequestId(UUID.randomUUID().toString());
}
//actual rest controller method invocation
MyGenericPojo res = (MyGenericPojo) joinPoint.proceed();
//adding common to body
res.setCommon(common);
return res;
}
//example controller
#RestController
#RequestMapping("/")
public static class MyRestController {
#PostMapping("/test" )
#EnrichWithCommon // mark method to intercept
public MyGenericPojo test(#RequestBody MyGenericPojo myGenericPojo) {
return myGenericPojo;
}
}
}
We have here an annotation #EnrichWithCommon which marks endpoints where enrichment should happen.
If it's only a meta data that you copy from the request to the response, you can do one of the followings:
1- store the meta in the request/response header,and just use filters to do the copy :
#WebFilter(filterName="MetaDatatFilter", urlPatterns ={"/*"})
public class MyFilter implements Filter{
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("metaData", httpServletRequest.getHeader("metaData"));
}
}
2- move the work into the service layer where you can do the cope through a reusable common method, or have it run through AOP
public void copyMetaData(whatEverType request,whatEverType response) {
response.setMeta(request.getMeta);
}
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());
}
This may be a strange question, although I wonder why it hasn't been asked or proposed before... so please correct me if any ignorance.
First off, I am using Jackson in conjunction with Spring and the #ResponseBody annotation.
Currently, for every request handler I am returning a "Response" wrapper object, as that is what the client expects. This wrapper is quite simple:
{ "response": { "data" : ACTUAL_DATA } }
Thing is, I'm not a fan of explicitly wrapping each return value for all my request handlers. I also do not like having to unwrap these response wrappers in my unit tests.
Rather, I wonder if it were possible to return the ACTUAL_DATA as it were, and to intercept and wrap this data elsewhere.
If this is in fact possible, would it then be possible to read the annotations attached to the intercepted request handler? This way I can use custom annotations to decide how to wrap the data.
For example something like this would be amazing (note that #FetchResponse and #ResponseWrapper are made up proposed annotations):
#RequestMapping(...)
#FetchResponse
#ResponseBody
public List<User> getUsers() {
...
}
#ResponseWrapper(FetchResponse.class)
public Object wrap(Object value) {
ResponseWrapper rw = new ResponseWrapper();
rw.setData(value);
return rw;
}
Anyone familiar with this territory? Or alternatively, and reasons why this might be bad practice?
Well, looks like I'm looking for Spring's "ResponseBodyAdvice" and "AbstractMappingJacksonResponseBodyAdvice".
For anybody looking on more info on this topic: I was facing the same issue as well, and thanks to the tip of kennyg, i managed to come up with the following solution:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
#ControllerAdvice
public class JSendAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof JSendResponse) {
return body;
}
return new JSendResponse<>().success(body);
}
}
This solution wraps all the objects returned in your controllers inside a (for this example) JSendResponse class, which saves you the hassle of returning JSendResponses in all of your controller methods.
I know it's been a while since the answer was accepted but I recently stumbled onto an issue with Jackson that allowed me to discover a problem with using ResponseBodyAdvice.
Jackson will not correctly serialize your polymorphic types that use #JsonTypeInfo / #JsonSubTypes if during runtime the values of your types are not known: i.e. for example if you have a generic container type like class ResponseWrapper<T> { List<T> objects; }. That is unless you provide Jackson with specialization of that generic type before you ask it to serialize your value, refer to Why does Jackson polymorphic serialization not work in lists? . Spring does this for you when you return say a list of T and that T is known because it's provided explicitly in the method return type (as in public List<MyEntity> getAllEntities();).
If you simply implement ResponseBodyAdvice and return a new, wrapped value from beforeBodyWrite() then Spring will no longer know your full generic type with its specialization, and it will serialize your response as ResponseWrapper<?> instead of ResponseWrapper<MyEntity>.
The only way around this is to both extend from AbstractJackson2HttpMessageConverter and override writeInternal(). See how the method treats the type here: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java#L437
And you also need to implement a Controller advice using AbstractMappingJacksonResponseBodyAdvice and your own custom MappingJacksonValue that includes Type targetType that custom HttpMessageConverter will use.
ResponseWrapper
public class ResponseWrapper<T> {
#Nullable Error error;
T result;
public ResponseWrapper(T result) {
this.result = result;
}
}
WrappingAdvice
#Component
public class WrappingAdvice extends AbstractMappingJacksonResponseBodyAdvice {
#Override
protected MappingJacksonValue getOrCreateContainer(Object body) {
MappingJacksonValue cnt = super.getOrCreateContainer(body);
if (cnt instanceof MyMappingJacksonValue) {
return cnt;
}
return new MyMappingJacksonValue(cnt);
}
#Override
protected void beforeBodyWriteInternal(
MappingJacksonValue bodyContainer, MediaType contentType,
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
MyMappingJacksonValue cnt = (MyMappingJacksonValue) bodyContainer;
Type targetType = getTargetType(bodyContainer.getValue(), returnType);
cnt.setValue(new ResponseWrapper(cnt.getValue()));
cnt.setTargetType(TypeUtils.parameterize(
ResponseWrapper.class,
targetType));
}
/**
* This is derived from AbstractMessageConverterMethodProcessor
*/
private Type getTargetType(Object value, MethodParameter returnType) {
if (value instanceof CharSequence) {
return String.class;
}
Type genericType;
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
genericType = ResolvableType.forType(returnType.getGenericParameterType()).getGeneric().getType();
} else {
genericType = returnType.getGenericParameterType();
}
return GenericTypeResolver.resolveType(genericType, returnType.getContainingClass());
}
public static class MyMappingJacksonValue extends MappingJacksonValue {
private Type targetType;
public MyMappingJacksonValue(MappingJacksonValue other) {
super(other.getValue());
setFilters(other.getFilters());
setSerializationView(other.getSerializationView());
}
public Type getTargetType() {
return targetType;
}
public void setTargetType(Type targetType) {
this.targetType = targetType;
}
}
}
JsonHttpMessageBodyConverter
#Component
public class JsonHttpMessageBodyConverter extends AbstractJackson2HttpMessageConverter {
// omitted all constructors
#Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if (object instanceof WrapAPIResponseAdvice.MyMappingJacksonValue) {
type = ((WrapAPIResponseAdvice.MyMappingJacksonValue) object).getTargetType();
}
super.writeInternal(object, type, outputMessage);
}
}
I am using Spring MVC with Controllers, my question is how do I return a JSON response which is different from the #ResponseBody object which is returned and convereted to a JSON to be returned.
To elaborate further, I have the object called "UserDetails" which has two fields called "name", "emailAddress"
#ResponseBody UserDetails
now the json returned will look like
{ name : "TheUsersName",
emailAddress:"abc#abc123.com" }
Is there any way I can modify the json before returning (ALL jsons in all methods across all controllers) where a "status" field will be added and the other json data will be under the "data" key in the json.
Also how do I return a json to the frontend when the java server from somewhere throws an exception, the json should have "status : false" and the exception name (atleast the status part though)
Create a response class:
public class Response<T> {
T data;
boolean status = true;
public Response(T d) { data = d; }
}
Then return that from your controllers:
#ResponseBody public Response getUserDetails) {
//...
return new Response(userDetails);
}
For the exception you'll want to return an object like:
public class BadStatus {
String errorMessage;
boolean status = false;
public BadStatus(String msg) { errorMessage = msg; }
}
#ExceptionHandler(Exception.class)
public BadStatus handleException(Exception ex, HttpServletRequest request) {
return new BadStatus(ex.getMessage());
}
Yes. Return a model and a view instead.
public ModelMap getUserDetails() {
UserDetails userDetails; // get this object from somewhere
ModelMap map = new ModelMap()(;
map.addAttribute("data", userDetails);
map.addAttribute("success", true);
return map;
}
To add the exception you'd do it the same way with a key and success = false.
An alternate solution (works with spring 3.1), which is less invasive
in your spring config :
<bean id="jacksonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="mypackage.MyMessageConverter"
p:delegate-ref="jacksonConverter">
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
The idea is to provide your own HttpMessageConverter that delegates to the provided jackson converter.
public class MyMessageConverter implements HttpMessageConverter<Object> {
// setters and delegating overrides ommitted for brevity
#Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
// t is whatever your #ResponseBody annotated methods return
MyPojoWrapper response = new MyPojoWrapper(t);
delegate.write(response, contentType, outputMessage);
}
}
This way all your pojos are wrapped with some other json that you provide there.
For exceptions, the solution proposed by ericacm is the simplest way to go (remember to annotate the 'BadStatus' return type with #ResponseBody).
A caveat : your json-serialized BadStatus goes through MyMessageConverter too, so you will want to test for the object type in the overriden 'write' method, or have MyPojoWrapper handle that.