In spring MVC, a void return type means that the view would be derived from the URL or from the method name? - spring

I am using Spring 3.0.6, and have noted a few (apparently) contradictory statements regarding what happens when a controller method declares a void return type (or returns a null). Consider the following :
#Controller
#RequestMapping(value="admin/*")
public class AdminController {
#RequestMapping
public ResponseEntity<String> hello() {
System.out.println("hellooooooo");
}
}
This takes the view name as the method name as stated here. But as stated in the accepted answer here, the view name is derived from url (not the method name). The method in question was :
#Controller
#RequestMapping("/form")
public class FormController {
#RequestMapping(method=RequestMethod.POST)
public String processSubmit(#Valid FormBean form,
BindingResult result,
WebRequest webRequest,
HttpSession session, Model model) {
if (result.hasErrors()) {
return null;
} else {
session.setAttribute("form", form);
return "redirect:/form";
}
}
}

skaffman's answer in What does it mean when Spring MVC #Controller returns null view name? is the correct one, because code can not lie.
So according to the docs and to the code: the view name is derived from the url (not from the method name).
I think that the other question (Spring MVC #RequestMapping … using method name as action value?) (and its answer) are a bit missleading, because there the request url and the method name are the same.

Related

Spring Boot - mapping

In the code below there are two methods annotated with #GetMapping annotation, one expects empty path, another one expects a path variable.
#Controller
#RequestMapping("/")
public class BasicController {
#GetMapping()
public String get(Model model) {
// doing something
}
#GetMapping("/{variable}")
public String getWithPathVar(#PathVariable("variable") String variable, Model model) {
// doing something different
}
}
Problem: When the app is running and I hit "www.myurl.com/" it enters both methods even though there is no path parameter. How can I fix this?
If so it sounds like a bug or some misconfiguration with filters. I can't reproduce this behaviour on the Spring 5.2.7. Here's an article that explains how Spring works under the hood.
If you can't upgrade the Spring version you can use only single endpoint as a workaround.
#GetMapping("/{variable}")
public String getWithPathVar(#PathVariable("variable") String variable, Model model) {
// doing something different
if(variable != null) {
// fulfill the normal workflow
} else {
// call ex get() workflow
}
}

Add attribute to wildcard Requestmapping path?

Is there a way to add an attribute to all paths of a certain user?
I.e I am trying to reach the current logged in administrator on all pages the administrator can reach, but I don't want to add this attribute to every single controller.
Something like this, where I don't need to return anything:
#RequestMapping(value = {"admin/**"}, method = RequestMethod.GET)
public void adminPaths(ModelMap model) {
model.addAttribute("user", getPrincipal());
}
You can use #ModelAttributes on a method in a controller. An #ModelAttribute on a method indicates the purpose of that method is to add one or more model attributes to all controller methods:
#Controller
#RequestMapping("/admin")
public class AdminController {
...
#ModelAttribute
public void populateModel(Model model) {
model.addAttribute("user", getPrincipal());
// add more ...
}
...
}
#ModelAttribute methods in a controller are invoked before #RequestMapping methods, within the same controller.
For truly wildcard matching, you can use ControllerAdvice and ModelAttributes on methods together. Something like following:
#ControllerAdvice(annotations = Controller.class)
public class AdminPopulatorAdvice {
#ModelAttribute
public void populateModel(HttpServletRequest request, Model model) {
// examine the request
// if its path contains /admin, then add attribute
model.addAttribute("user", getPrincipal());
// add more ...
}
}
I forgot to mention that I was using Spring security.
Bohuslav pointed me into the right direction and ended up here: https://docs.spring.io/spring-security/site/docs/current/reference/html/taglibs.html

Spring MVC : Common param in all requests

I have many controllers in my Spring MVC web application and there is a param mandatoryParam let's say which has to be present in all the requests to the web application.
Now I want to make that param-value available to all the methods in my web-layer and service-layer. How can I handle this scenario effectively?
Currently I am handling it in this way:
... controllerMethod(#RequestParam String mandatoryParam, ...)
and, then passing this param to service layer by calling it's method
#ControllerAdvice("net.myproject.mypackage")
public class MyControllerAdvice {
#ModelAttribute
public void myMethod(#RequestParam String mandatoryParam) {
// Use your mandatoryParam
}
}
myMethod() will be called for every request to any controller in the net.myproject.mypackage package. (Before Spring 4.0, you could not define a package. #ControllerAdvice applied to all controllers).
See the Spring Reference for more details on #ModelAttribute methods.
Thanks Alexey for leading the way.
His solution is:
Add a #ControllerAdvice triggering for all controllers, or selected ones
This #ControllerAdvice has a #PathVariable (for a "/path/{variable}" URL) or a #RequestParam (for a "?variable=..." in URL) to get the ID from the request (worth mentioning both annotations to avoid blind-"copy/past bug", true story ;-) )
This #ControllerAdvice then populates a model attribute with the data fetched from database (for instance)
The controllers with uses #ModelAttribute as method parameters to retrieve the data from the current request's model
I'd like to add a warning and a more complete example:
Warning: see JavaDoc for ModelAttribute.name() if no name is provided to the #ModelAttribute annotation (better to not clutter the code):
The default model attribute name is inferred from the declared
attribute type (i.e. the method parameter type or method return type),
based on the non-qualified class name:
e.g. "orderAddress" for class "mypackage.OrderAddress",
or "orderAddressList" for "List<mypackage.OrderAddress>".
The complete example:
#ControllerAdvice
public class ParentInjector {
#ModelAttribute
public void injectParent(#PathVariable long parentId, Model model) {
model.addAttribute("parentDTO", new ParentDTO(parentId, "A faked parent"));
}
}
#RestController
#RequestMapping("/api/parents/{parentId:[0-9]+}/childs")
public class ChildResource {
#GetMapping("/{childId:[0-9]+}")
public ChildDTO getOne(#ModelAttribute ParentDTO parent, long childId) {
return new ChildDTO(parent, childId, "A faked child");
}
}
To continue about the warning, requests are declaring the parameter "#ModelAttribute ParentDTO parent": the name of the model attribute is not the variable name ("parent"), nor the original "parentId", but the classname with first letter lowerified: "parentDTO", so we have to be careful to use model.addAttribute("parentDTO"...)
Edit: a simpler, less-error-prone, and more complete example:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
public #interface ProjectDependantRestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
*
* #return the suggested component name, if any
*/
String value() default "";
}
#ControllerAdvice(annotations = ParentDependantRestController.class)
public class ParentInjector {
#ModelAttribute
public ParentDTO injectParent(#PathVariable long parentId) {
return new ParentDTO(parentId, "A faked parent");
}
}
#ParentDependantRestController
#RequestMapping("/api/parents/{parentId:[0-9]+}/childs")
public class ChildResource {
#GetMapping("/{childId:[0-9]+}")
public ChildDTO getOne(#ModelAttribute ParentDTO parent, long childId) {
return new ChildDTO(parent, childId, "A faked child");
}
}

AOP for Spring Controllers

Spring's AOP functionality is pretty great, and it makes it easy to add cool and useful annotations to controllers. For example, I wrote an #Authenticated annotation that either allows authenticated users through to the controller method or redirects to the login page. Fun stuff.
However, Spring's controllers can return all sorts of different types. They can return Strings, ModelAndView objects, or even void. There are methods in my code base that use all three types. However, I'd like to change my #Authenticated annotation to render and return a particular page, which I was hoping to do by returning a ModelAndView object. Is the only way to accomplish this by requiring all of my controller methods to return a ModelAndView?
Example of a controller I'd like to have:
#Controller
public class MyController() {
#Authenticated
#RequestMapping("/myscore")
public String myScorePage(ModelMap model) {
return "myScorePage";
}
#Authenticated
#RequestMapping("/anotherPage")
public ModelAndView something() {
return new ModelAndView("anotherPage",someModelStuff());
}
}
#Aspect
public class NotVeryUsefulAspect {
#Around("#annotation(Authenticate)")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
if( isAuthenticated() ) {
return pjp.proceed();
} else {
return /* Oh no what goes here, I want to render a FAILURE page without redirecting */
}
}
}
Ha, figured it out!
I decided to use the ProceedingJoinPoint passed to the aspect method to figure out the return type of the original method. Then I made a set of possible "failure" results for the aspect method based on what type of return is passed. For example, if the method originally returned a String, I return "failure_page", and if the method returned a ModelAndView, I return a new ModelAndView("failure_page").
Works quite well! Unfortunately, I may not have an opportunity to set a model object if it returns a string and doesn't take a ModelMap as a parameter, but I can deal with that for an error page just fine.
Yes it seams that you are right.
You need to change your methods so that all return an ModelAndView.
Or you need two Aspects, one for return type ModelAndView and one for String - and then all your controller methods must match
But Authorization is already build in in Spring Security and you do not need to implement it by your own.

I am confused about how to use #SessionAttributes

I am trying to understand architecture of Spring MVC. However, I am completely confused by behavior of #SessionAttributes.
Please look at SampleController below , it is handling post method by SuperForm class. In fact, just field of SuperForm class is only binding as I expected.
However, After I put #SessionAttributes in Controller, handling method is binding as SubAForm. Can anybody explain me what happened in this binding.
-------------------------------------------------------
#Controller
#SessionAttributes("form")
#RequestMapping(value = "/sample")
public class SampleController {
#RequestMapping(method = RequestMethod.GET)
public String getCreateForm(Model model) {
model.addAttribute("form", new SubAForm());
return "sample/input";
}
#RequestMapping(method = RequestMethod.POST)
public String register(#ModelAttribute("form") SuperForm form, Model model) {
return "sample/input";
}
}
-------------------------------------------------------
public class SuperForm {
private Long superId;
public Long getSuperId() {
return superId;
}
public void setSuperId(Long superId) {
this.superId = superId;
}
}
-------------------------------------------------------
public class SubAForm extends SuperForm {
private Long subAId;
public Long getSubAId() {
return subAId;
}
public void setSubAId(Long subAId) {
this.subAId = subAId;
}
}
-------------------------------------------------------
<form:form modelAttribute="form" method="post">
<fieldset>
<legend>SUPER FIELD</legend>
<p>
SUPER ID:<form:input path="superId" />
</p>
</fieldset>
<fieldset>
<legend>SUB A FIELD</legend>
<p>
SUB A ID:<form:input path="subAId" />
</p>
</fieldset>
<p>
<input type="submit" value="register" />
</p>
</form:form>
When processing POST request, Spring does the following:
Without #SessionAttributes: Spring instantiates a new instance of SuperForm (type is inferred from the signature of register()), populates its properties by values from the form fields and passes it to the register() method.
With #SessionAttributes: Spring obtains an instance of model attribute from the session (where it was placed when processing GET due to presence of #SessionAttributes), updates its properties by values from the from fields and passes it to the register() method.
That is, with #SessionAttributes , register() gets the same instance of the model attribute object that was placed into the Model by getCreateForm().
Adding on to what #axtavt said: Suppose, in getCreateForm you are putting some values for a drop-down (list or map), or you are putting some values in form that you want in register method but you don't want them to show in form (not even in hidden fields). Now suppose that an error occurred in register method and you need to show the form again. To populate drop down values and other values that you would need in next post, you would have to repopulate them in form. The #SessionAttribute helps here as #axtavt very well described above.
#Controller
#SessionAttributes("test")
public class Controller{
Customer customer;
public Controller() {
super();
customer = new Customer();
}
#ModelAttribute("test")
public Customer getCustomer() {
customer.setName("Savac");
return customer;
}
#RequestMapping({"/index"})
public ModelAndView showMainPage (#ModelAttribute("test") Customer customer, ModelMap model, method = RequestMethod.GET) {
//in the view you set the name
return new ModelAndView("index");
}
#RequestMapping(value = "customer/{customerID}", method = RequestMethod.GET)
public ModelAndView viewAdvice(#PathVariable("customerID") int customerID, #ModelAttribute("test") Customer customer, ModelMap model) {
customer.setName("AnotherName");
model.addAttribute("test", customer);
return new ModelAndView("customer");
}
}
According to Spring reference documentation #ModelAttribute annotated method argument is resolved as follows:
Retrieve from model object if it is present (normally added via #ModelAttribute annotated methods)
Retrieve from HTTP session by using #SessionAttributes.
Create using URI path variable that matches the #ModelAttribute name through a converter
Create using default constructor and add it to Model.
A handler class can be annotated with #SessionAttributes with a list of names as its arguments. This is to instruct Spring to persist (in session) those data items present in the model data which match the names specified in #SessionAttributes annotation.
Thus in the SampleController, the post method's #ModelAttribute argument is resolved with #SessionAttributes field due to the resolution method mentioned above.

Resources