I need to build an object before it even reaches the controller and one way I found to do that is by using HandlerMethodArgumentResolver.
Basically, I have a pojo which gets mapped to the request parameters, but I want to set some other fields in that pojo before it reaches the controller.
POJO: UserParams.java
#AllArgsConstructor
public class UserParams {
private String firstName;
private String lastName;
private String sessionId;
}
Let's say my request comes in as localhost:8080/user?firstName=John&lastName=Doe
So, in my resolver I want to bind the UserParams object using the request params from the above request and populate sessionId field and return the bound object with additional value.
#Component
public class UserParamsResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(UserParams.class);
}
#Override
public Object resolveArgument(final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest,
final WebDataBinderFactory binderFactory) {
WebDataBinder binder = new WebDataBinder(BeanUtils.instantiateClass(parameter.getParameterType()));
ServletRequestParameterPropertyValues values = new ServletRequestParameterPropertyValues(((ServletWebRequest) webRequest).getRequest());
binder.bind(values);
BindingResult result = binder.getBindingResult();
// UserParams userParams = how to get this object?
// userParams.setSessionId(userParams.getLastName + Math.random())
return userParams;
}
So, when the request eventually reaches the controller, I've the userParams with sessionId in it.
I tried looking at many places (programcreek.com had lot of examples for WebDataBinder) and tried to find out how Spring binds the objects with request params before coming to the Controller, but I had no luck.
One solution is to use spring AOP in order to process all #Controller methods having UserParams as a parameter. Then you have to get access to WebRequest in order to get sessionId and this is a little bit trickier; you'll have to create a #Bean #Scope("request") (e.g. name it WebRequestAccessor) which to contain an #Autowired field of type WebRequest. Autowire that WebRequestAccessor bean into your #Aspect in order to use its WebRequest field which then gives you access to sessionId. Set the sessionId on the UserParams parameter then let the advised method continue its work.
Take a look here about how to write & use an #Aspect.
UPDATE
You could use JSESSIONID instead of HttpSession.getId(). Just annotate private String sessionId with #CookieValue("JSESSIONID").
UPDATE 2
I'm pretty sure you should use ServletModelAttributeMethodProcessor (or ModelAttributeMethodProcessor) instead of HandlerMethodArgumentResolver.
Related
Consider a #PostMapping in Spring MVC, and we want to map the request body to a DTO, as well as other request parameters like query and path variables.
For mapping the request body we can use the #RequestBody annotation on a parameter, which will tell Spring to use the RequestResponseBodyMethodProcessor.
For mapping the request parameters we can use the #ModelAttribute annotation (or avoid any annotations; same effect), which will tell Spring to use the ServletModelAttributeMethodProcessor.
But is there an easy way to combine these two? Is there a way to make spring first map the DTO with request parameters and then override with data deserialized from the JSON in the body?
The only way I see it at the moment is to create a custom HandlerMethodArgumentResolver but I'd like to reuse any existing Spring functionality first.
Turns out no, not at time of writing anyway.
RequestMappingHandlerAdapter.getDefaultArgumentResolvers holds the default configuration for the argument resovlers, and we have:
RequestResponseBodyMethodProcessor handling the #RequestBody annotation, and
ServletModelAttributeMethodProcessor handling the #ModelAttribute, or any parameter without any annotations.
It's always just one single resolver that gets executed, even if you try using a composite.
My solution
#ControllerAdvice
#RequiredArgsConstructor
#Order(HIGHEST_PRECEDENCE)
public class ServletRequestBinderRequestBodyAdvice extends RequestBodyAdviceAdapter {
private final ServletRequest servletRequest;
#Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
copyDefaultPropertiesThatWhereOverwritenWithNull(parameter, body);
new ExtendedServletRequestDataBinder(body).bind(servletRequest);
return body;
}
private void copyDefaultPropertiesThatWhereOverwritenWithNull(MethodParameter parameter, Object arg) {
Object argWithDefaults = instantiateClass(parameter.getParameterType());
copyPropertiesSkippingNulls(argWithDefaults, arg);
}
}
I'm new to Spring, and since Spring provides many ways to map an HTTP request to Java objects, I'm hoping someone could advice me how to resolve this:
I have a client that sends a request having
ContentType: application/x-www-form-urlencoded
Some of the request parmeters have names such as
"form_data[orderStatus]", "form_data[orderNumber]", etc'
I have no control over this client!
I have a java class (#Component) called MyOrder which looks as follows:
#component
#Scope("prototpe")
public class MyOrder {
private String orderStatus;
private String orderNumber;
//etc'
public void setOrderStatus(String orderStatus) {
this.orderStatus = orderStatus;
}
//public setter for all other properties, as the above
}
What is the simplest way to create an instance of MyOrder
populated with all values of all "form_data[]", so that I can have a controller method having a signature that includes a MyOrder parameter, such as:
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
#ModelAttribute("myOrder") MyOrder anOrder) {
//... impl'n here
}
The best solution I could think of was to use a Web Filter which would flaten request params names such as "form_data[attrib1]" to "attrib1", and then Spring would do all the work of populating the MyOrder instance.
One disadvantage of this is that the request may have both "form_data[attrib1]" and "attrib1" parameters. For example:
form_data[orderStatus]=ok
orderStatus=fail
In this case i want MyOrder.orderStatus to have the value "ok".
Any good way of utilizing Spring create MyOrder from the request?
As an alternative, that does not use the class MyOrder, is there a way to have Spring map all the form_data[] parameters and their values to a map, so that i can have the controller method below?
public ModelAndView saveNewOrder( #RequestParam("foo") String foo,
<some annotation> #Map<String,String> formFieldsOfAnOrder) {
//... impl'n here
orderStatus = formFieldsOfAnOrder.get("orderStatus");
//or at least:
orderStatus = formFieldsOfAnOrder.get("form_data[orderStatus]");
}
I have a lot of Spring RestControllers with methods annotated with RequestMapping. I now would like to inject a custom object into these RequestMapping methods, and create an custom instance for each request.
I would like to write something like the following:
#RequestMapping("/search")
public SomeReturnObject foobar(#RequestParam("query") String query, MyRequestFoo foo) {
// ...
}
Now I would like to create a mechanism, where each call to that method (i.e. each request) get a new instance of MyRequestFoo created and injected into the method. If this would work better with an parameter annotation instead of injecting by type, that would also be okay (e.g. #MyRequestInject MyRequestFoo foo).
I need to know if I can create now a method that creates a new instance of MyRequestFoo especially for that request, like the following:
public MyRequestFoo createRequestInstanceSomehow(HttpServletRequest request) {
// extract some values from the HttpServletRequest and create a
// new MyRequestFoo instance from that and return it
}
Is this possible by any means to create such a mechanism, so that I can inject custom per request objects into my request handling methods?
Spring MVC has a arguments resolver construct that directly supports your request. Every handler method annotated with #RequestMapping will be subject to argument resolving, where the framework scans through the handler arguments, checks the type and instantiates an appropriate object. That is the mechanism behind injecting request, model and a number of other types, just by declaring the object in the handler's method signature.
You can write a custom argument resolver to have the custom types resolved and available in the method. The procedure is simple three step process
Make a POJO class, in your case MyRequestFoo
Make a resolver, e.g.
public class MyRequestFooResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(MyRequestFoo.class);
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory)
throws Exception {
return new MyRequestFoo();
}
}
3.Register a resolver
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="your.package.MyRequestFooResolver "></bean>
</mvc:argument-resolvers>
</mvc:annotation-driven>
or in java config
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List< Handlermethodargumentresolver > argumentResolvers) {
MyRequestFooResolver myRequestFooResolver = new MyRequestFooResolver ();
argumentResolvers.add(myRequestFooResolver );
}
}
Than you use it just by adding the type as a handler method argument
#RequestMapping("/search")
public SomeReturnObject search(MyRequestFoo foo) {
// ...
}
What about putting an instance variable of type MyRequestFoo on the Controller class and Autowire it changing the default scope from "Singleton" to "Request" on the Bean definition?
Check out this link or the Spring reference sheet!
I found a solution, that does what I was trying to do.
Just create the MyRequestFoo as a bean with scope "request" and you can access the current request via the RequestContextHolder:
#Component
#Scope("request")
public class MyRequestFoo {
private final HttpServletRequest request;
public MyRequestFoo() {
request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
}
// do whatever you want with the request
}
And now i can just inject a new instance of this by its type into any request handler method:
#RequestMapping("/search")
public SomeReturnObject search(MyRequestFoo foo) {
// ...
}
And Spring will automatically take care of instantiating a new instance.
It does not work to autowire MyRequestFoo as an instance variable, since you cannot autowire request scoped beans into non request scoped beans.
Since you need to pass in request-specific information to your bean, I recommend instead injecting a builder bean into your controller and calling builder.build(request) from your method to create the new per-request instance.
I am working with Spring MVC controller. I have one of my controller as DataController.
I am thinking to add HttpServletRequest as injectable at the top of DataController class using #Inject.
#Controller
public class DataController {
#Inject
HttpServletRequest request;
// .. some code here
#RequestMapping(value = "process", method = RequestMethod.GET)
public #ResponseBody
DataResponse processTask(#RequestParam("workflow") final String workflow) {
String ipAddress = request.getRemoteAddr();
System.out.println(ipAddress);
}
So my question is - Is this the right way to use #Inject? I have never used #Inject before so trying to learn whether the way I am doing it is right or not? Since everytime, who is making call to processTask method, I need to grab its ipAddress whoever is calling that processTask method.
In terms of acquiring HttpServletRequest: semantically speaking, it is definitely wrong.
Reason: HttpServletRequest is an object that is created only when users send requests and is destroyed once the requested user action is completed. You simply can store it that way (from syntax angle) but you shouldn't (from semantic angle). You need to realize that the way how web application works is not exactly same as a desktop application (and don't observe them from the same angle).
Suggestion:
#RequestMapping(value = "process", method = RequestMethod.GET)
public #ResponseBody
DataResponse processTask(#RequestParam("workflow") final String workflow, HttpServletRequest request) {...}
In this way you will get the corresponding request each time the processTask method is called. (HttpServletRequest object is injected by #RequestMapping.)
(If you would like to preserve something through out a session, consider use a bean that is
Suggestion: #Inject private UserService userService;
(assume we have a class registered called UserService.)
You cannot "inject" the HttpServletRequest the only way to use it as far as I know is to added as a method member. Like this:
#Controller
public class DataController {
// .. some code here
#RequestMapping(value = "process", method = RequestMethod.GET)
public #ResponseBody
DataResponse processTask(#RequestParam("workflow") final String workflow,HttpServletRequest request) {
String ipAddress = request.getRemoteAddr();
System.out.println(ipAddress);
}
look also at Spring MVC #AutoWired response not working
In Spring MVC, it is easy to bind request parameter to method paramaters handling the request. I just use #RequestParameter("name"). But can I do the same with request attribute? Currently, when I want to access request attribute, I have to do following:
MyClass obj = (MyClass) request.getAttribute("attr_name");
But I really would like to use something like this instead:
#RequestAttribute("attr_name") MyClass obj
Unfortunately, it doesn't work this way. Can I somehow extend Spring functionality and add my own "binders"?
EDIT (what I'm trying to achieve): I store currently logged user inside request attribute. So whenever I want to access currently logged user (which is pretty much inside every method), I have to write this extra line user = (User) request.getAttribute("user");. I would like to make it as short as possible, preferably inject it as a method parameter. Or if you know another way how to pass something across interceptors and controllers, I would be happy to hear it.
Well, I finally understood a little bit how models work and what is #ModelAttribute for. Here is my solution.
#Controller
class MyController
{
#ModelAttribute("user")
public User getUser(HttpServletRequest request)
{
return (User) request.getAttribute("user");
}
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String HandleSomeUrl(#ModelAttribute("user") User user)
{
// ... do some stuff
}
}
The getUser() method marked with #ModelAttribute annotation will automatically populate all User user parameters marked with #ModelAttribute. So when the HandleSomeUrl method is called, the call looks something like MyController.HandleSomeUrl(MyController.getUser(request)). At least this is how I imagine it. Cool thing is that user is also accessible from the JSP view without any further effort.
This solves exactly my problem however I do have further questions. Is there a common place where I can put those #ModelAttribute methods so they were common for all my controllers? Can I somehow add model attribute from the inside of the preHandle() method of an Interceptor?
Use (as of Spring 4.3) #RequestAttribute:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute User user) {
// ... do some stuff
}
or if the request attribute name does not match the method parameter name:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute(name="userAttributeName") User user) {
// ... do some stuff
}
I think what you are looking for is:
#ModelAttribute("attr_name") MyClass obj
You can use that in the parameters for a method in your controller.
Here is a link a to question with details on it What is #ModelAttribute in Spring MVC?
That question links to the Spring Documentation with some examples of using it too. You can see that here
Update
I'm not sure how you are setting up your pages, but you can add the user as a Model Attribute a couple different ways. I setup a simple example below here.
#RequestMapping(value = "/account", method = RequestMethod.GET)
public ModelAndView displayAccountPage() {
User user = new User(); //most likely you've done some kind of login step this is just for simplicity
return new ModelAndView("account", "user", user); //return view, model attribute name, model attribute
}
Then when the user submits a request, Spring will bind the user attribute to the User object in the method parameters.
#RequestMapping(value = "/account/delivery", method = RequestMethod.POST)
public ModelAndView updateDeliverySchedule(#ModelAttribute("user") User user) {
user = accountService.updateDeliverySchedule(user); //do something with the user
return new ModelAndView("account", "user", user);
}
Not the most elegant, but works at least...
#Controller
public class YourController {
#RequestMapping("/xyz")
public ModelAndView handle(
#Value("#{request.getAttribute('key')}") SomeClass obj) {
...
return new ModelAndView(...);
}
}
Source : http://blog.crisp.se/tag/requestattribute
From spring 3.2 it can be done even nicer by using Springs ControllerAdvice annotation.
This then would allow you to have an advice which adds the #ModelAttributes in a separate class, which is then applied to all your controllers.
For completeness, it is also possible to actually make the #RequestAttribute("attr-name") as is.
(below modified from this article to suit our demands)
First, we have to define the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestAttribute {
String value();
}
Then we need a [WebArgumentResolver] to handle what needs to be done when the attribute is being bound
public class RequestAttributeWebArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
// Get the annotation
RequestAttribute requestAttributeAnnotation = methodParameter.getParameterAnnotation(RequestAttribute.class);
if(requestAttributeAnnotation != null) {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getAttribute(requestAttributeAnnotation.value);
}
return UNRESOLVED;
}
}
Now all we need is to add this customresolver to the config to resolve it:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolver">
<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
</property>
</bean>
And we're done!
Yes, you can add your own 'binders' to the request attribute - see spring-mvc-3-showcase, or use #Peter Szanto's solution.
Alternatively, bind it as a ModelAttribute, as recommended in other answers.
As it's the logged-in user that you want to pass into your controller, you may want to consider Spring Security. Then you can just have the Principle injected into your method:
#RequestMapping("/xyz")
public String index(Principal principle) {
return "Hello, " + principle.getName() + "!";
}
In Spring WebMVC 4.x, it prefer implements HandlerMethodArgumentResolver
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestAttribute.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getAttribute(parameter.getParameterAnnotation(RequestAttribute.class).value(), NativeWebRequest.SCOPE_REQUEST);
}
}
Then register it in RequestMappingHandlerAdapter