I like to implement a REST-API into my SpringMVC application. At the moment, I have one method to handle POST-Requests, which "returns" a rendered ViewScript.
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
return "success";
}
It would be nice, to add a second method with the #ResponseBody Annotation for POST-Requests, e.g. to send a JSON-Response.
Furthermore, the old Method still has to exists, to handle "normal" Requests.
But a code like this doesn't work:
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
return "success";
}
#RequestMapping(method=RequestMethod.POST)
#ResponseBody
public Object add(User user, Model model)
{
// [...]
return myObject;
}
With this code, I'm getting a 405 (Method Not Allowed) Error from Tomcat. How can I fix this?
As it stands, Spring has no way to differentiate between these two requests: same URL, same request method.
You can further differentiate by mimetype:
#RequestMapping(method=RequestMethod.POST, headers="content-type=application/json")
Although there are several mimetypes associated with JSON :/ The headers value takes an array, however, so you can narrow/widen it as necessary.
See the headers docs.
Dont USE TWO ANNOTATION. It is a poor option. Just have one more method without annotation. But the method from the old method by checking the below condition.
JUST PASS ONE MORE ARGUMENT FROM UI by query parameter(request="JSON_Request").
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
if(request="JSON_Request") {
newMethod(user, model);
}
return "success";
}
private Object newMethod(User user, Model model)
{
// [...]
return myObject;
}
Related
How can I make it so the #SessionAttributes are scoped to their respective controllers, or clean up the #SessionAttributes when switching workflows between controllers prematurely?
Example:
User goes to webpage to edit a dictionary and comes into DictionaryController.java which creates a DictionaryForm.java object and stores it in #SessionAttributes under "form"
Normally, the dictionary entry is fetched in a GET request, then updated on POST and status.setComplete() is called on success;
However if the user does the GET request, then clicks away to another page such as OrganizationController.java the second controller appears to try and reuse the "form" #SessionAttribute object from the other controller and will fail before it even reaches the getOrganization() method. (The nature of how exactly it's failing is undetermined as my eclipse console isn't outputting any exceptions, but I suspect it's because the form types don't match up)
#SessionAttributes("form")
public class DictionaryController {
#ModelAttribute("form")
public DictionaryForm initForm() {
return new DictionaryForm();
}
#RequestMapping(value="/Dictionary" method=RequestMethod.GET)
public String getDictionary(
#ModelAttribute("form") DictionaryForm form) {
...
return "dictionaryView";
}
#RequestMapping(value="/Dictionary" method=RequestMethod.POST)
public String updateDictionary(
#ModelAttribute("form") DictionaryForm form,
SessionStatus status) {
...
status.setComplete();
return "successView";
}
}
#Controller
#SessionAttributes("form")
public class OrganizationController{
#ModelAttribute("form")
public OrganizationForm initForm() {
return new OrganizationForm();
}
#RequestMapping(value="/Organization" method=RequestMethod.GET)
public String getOrganization(
#ModelAttribute("form") OrganizationForm form) {
...
return "orgView";
}
#RequestMapping(value="/Organization" method=RequestMethod.POST)
public String updateOrganization(
#ModelAttribute("form") OrganizationForm form,
SessionStatus status) {
...
status.setComplete();
return "successView";
}
}
Solution I ended up using was having a BaseForm object type that all form types inherit from. Then in my request mapping methods for the GET requests, I would use #ModelAttribute("form") BaseForm form and manually check the form type in the body of the method, and if it doesn't match convert it and restore it in the session. (Replacing it in the session may be unnecessary if you attach it to your model object for the request)
ie.
#RequestMapping(value="/Organization" method=RequestMethod.GET)
public String getOrganization(HttpServletRequest request,
#ModelAttribute("form") BaseForm form) {
if (form.getClass() != OrganizationForm.class) {
form = new OrganizationForm();
request.getSession().setAttribute("form", form);
}
...
return "orgView";
}
#RequestMapping("/accounts")
public class controller {
#GetMapping("/get/{id}")
public final ResponseEntity<?> getHandler(){
}
#PostMapping(value = "/create")
public final ResponseEntity<?> createHandler(){
/*
trying to use some spring library methods to get the url string of
'/accounts/get/{id}' instead of manually hard coding it
*/
}
}
This is the mock code, now I am in createHandler, after finishing creating something, then I want to return a header including an URL string, but I don't want to manually concat this URL string ('/accounts/get/{id}') which is the end point of method getHandler(), so I am wondering if there is a method to use to achieve that? I know request.getRequestURI(), but that is only for the URI in the current context.
More explanation: if there is some library or framework with the implementation of route:
Routes.Accounts.get(1234)
which return the URL for the accounts get
/api/accounts/1234
The idea is, that you don't need to specify get or create (verbs are a big no-no in REST).
Imagine this:
#RequestMapping("/accounts")
public class controller {
#GetMapping("/{id}")
public final ResponseEntity<?> getHandler(#PathVariable("id") String id) {
//just to illustrate
return complicatedHandlerCalculation(id).asResponse();
}
#PostMapping
public final ResponseEntity<?> createHandler() {
//return a 204 Response, containing the URI from getHandler, with {id} resolved to the id from your database (or wherever).
}
}
This would be accessible like HTTP-GET: /api/accounts/1 and HTTP-POST: /api/accounts, the latter would return an URI for /api/accounts/2 (what can be gotten with HTTP-GET or updated/modified with HTTP-PUT)
To resolve this URI, you could use reflection and evaluate the annotations on the corresponding class/methods like Jersey does.
A Spring equivalent could be:
// Controller requestMapping
String controllerMapping = this.getClass().getAnnotation(RequestMapping.class).value()[0];
and
//Method requestMapping
String methodMapping = new Object(){}.getClass().getEnclosingMethod().getAnnotation(GetMapping.class).value()[0];
taken from How do i get the requestmapping value in the controller?
I've been trying to figure out what the best practice is for form submission with spring and what the minimum boilerplate is to achieve that.
I think of the following as best practise traits
Validation enabled and form values preserved on validation failure
Disable form re-submission F5 (i.e. use redirects)
Prevent the model values to appear in the URL between redirects (model.clear())
So far I've come up with this.
#Controller
#RequestMapping("/")
public class MyModelController {
#ModelAttribute("myModel")
public MyModel myModel() {
return new MyModel();
}
#GetMapping
public String showPage() {
return "thepage";
}
#PostMapping
public String doAction(
#Valid #ModelAttribute("myModel") MyModel myModel,
BindingResult bindingResult,
Map<String, Object> model,
RedirectAttributes redirectAttrs) throws Exception {
model.clear();
if (bindingResult.hasErrors()) {
redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.myModel", bindingResult);
redirectAttrs.addFlashAttribute("myModel", myModel);
} else {
// service logic
}
return "redirect:/thepage";
}
}
Is there a way to do this with less boilerplate code or is this the least amount of code required to achieve this?
First, I wouldn't violate the Post/Redirect/Get (PRG) pattern, meaning I would only redirect if the form is posted successfully.
Second, I would get rid of the BindingResult style altogether. It is fine for simple cases, but once you need more complex notifications to reach the user from service/domain/business logic, things get hairy. Also, your services are not much reusable.
What I would do is pass the bound DTO directly to the service, which would validate the DTO and put a notification in case of errors/warning. This way you can combine business logic validation with JSR 303: Bean Validation.
For that, you can use the Notification Pattern in the service.
Following the Notification Pattern, you would need a generic notification wrapper:
public class Notification<T> {
private List<String> errors = new ArrayList<>();
private T model; // model for which the notifications apply
public Notification<T> pushError(String message) {
this.errors.add(message);
return this;
}
public boolean hasErrors() {
return !this.errors.isEmpty();
}
public void clearErrors() {
this.errors.clear();
}
public String getFirstError() {
if (!hasErrors()) {
return "";
}
return errors.get(0);
}
public List<String> getAllErrors() {
return this.errors;
}
public T getModel() {
return model;
}
public void setModel(T model) {
this.model = model;
}
}
Your service would be something like:
public Notification<MyModel> addMyModel(MyModelDTO myModelDTO){
Notification<MyModel> notification = new Notification();
//if(JSR 303 bean validation errors) -> notification.pushError(...); return notification;
//if(business logic violations) -> notification.pushError(...); return notification;
return notification;
}
And then your controller would be something like:
Notification<MyModel> addAction = service.addMyModel(myModelDTO);
if (addAction.hasErrors()) {
model.addAttribute("myModel", addAction.getModel());
model.addAttribute("notifications", addAction.getAllErrors());
return "myModelView"; // no redirect if errors
}
redirectAttrs.addFlashAttribute("success", "My Model was added successfully");
return "redirect:/thepage";
Although the hasErrors() check is still there, this solution is more extensible as your service can continue evolving with new business rules notifications.
Another approach which I will keep very short, is to throw a custom RuntimeException from your services, this custom RuntimeException can contain the necessary messages/models, and use #ControllerAdvice to catch this generic exception, extract the models and messages from the exception and put them in the model. This way, your controller does nothing but forward the bound DTO to service.
Based on the answer by #isah, if redirect happens only after successful validation the code can be simplified to this:
#Controller
#RequestMapping("/")
public class MyModelController {
#ModelAttribute("myModel")
public MyModel myModel() {
return new MyModel();
}
#GetMapping
public String showPage() {
return "thepage";
}
#PostMapping
public String doAction(
#Valid #ModelAttribute("myModel") MyModel myModel,
BindingResult bindingResult,
RedirectAttributes redirectAttrs) throws Exception {
if (bindingResult.hasErrors()) {
return "thepage";
}
// service logic
redirectAttrs.addFlashAttribute("success", "My Model was added successfully");
return "redirect:/thepage";
}
}
One possible way is to use Archetype for Web forms, Instead of creating simple project, you can choose to create project from existing archetype of web forms. It will provide you with sufficient broiler plate code. You can also make your own archetype.
Have a look at this link to get deeper insight into archetypes.
Link To Archetypes in Java Spring
i am new Spring learner.i'm really confused about what is the difference between two concept:
#ModelAttribute
model.addAttribute
in below there are two "user" value.Are these same thing?Why should I use like this?
Thank you all
#RequestMapping(method = RequestMethod.GET)
public String setupForm(ModelMap model) {
model.addAttribute("user", new User());
return "editUser";
}
#RequestMapping(method = RequestMethod.POST)
public String processSubmit( #ModelAttribute("user") User user, BindingResult result, SessionStatus status) {
userStorageDao.save(user);
status.setComplete();
return "redirect:users.htm";
}
When used on an argument, #ModelAttribute behaves as follows:
An #ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument’s fields should be populated from all request parameters that have matching names. This is known as data binding in Spring MVC, a very useful mechanism that saves you from having to parse each form field individually.
http://docs.spring.io/spring/docs/4.1.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc-ann-modelattrib-method-args
That's a very powerful feature. In your example, the User object is populated from the POST request automatically by Spring.
The first method, however, simply creates an instance of Userand adds it to the Model. It could be written like that to benefit from #ModelAttribute:
#RequestMapping(method = RequestMethod.GET)
public String setupForm(#ModelAttribute User user) {
// user.set...
return "editUser";
}
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