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
}
}
Related
I want to pass some variables to my server. I did it this way, like shown in the example:
#Controller
#RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
#RequestMapping("/pets/{petId}")
public void findPet(#PathVariable String ownerId, #PathVariable String petId, Model model) {
// implementation omitted
}
}
This works totally fine when I send a request like this:
domain/owners/123/pets/123
But what I want to do is getting all pets of one owner. This means I dont need/want to pass a pet-ID:
domain/owners/123/pets/
But then I get an Excpetion that there is no Handler for this request. Is it possible to send a request like this or is it limited by Spring?
You have to add a second method:
#RequestMapping("/pets/")
public void findPetByOwner(#PathVariable String ownerId Model model) {
// implementation omitted
}
I am doing a Spring web. For a controller method, I am able to use RequestParam to indicate whether a parameter it is required or not. For example:
#RequestMapping({"customer"})
public String surveys(HttpServletRequest request,
#RequestParam(value="id", required = false) Long id,
Map<String, Object> map)
I would like to use PathVariable such as the following:
#RequestMapping({"customer/{id}"})
public String surveys(HttpServletRequest request,
#PathVariable("id") Long id,
Map<String, Object> map)
How can I indicate whether a path variable is required or not? I need to make it optional because when creating a new object, there is no associated ID available until it is saved.
Thanks for help!
VTTom`s solution is right, just change "value" variable to array and list all url possibilities: value={"/", "/{id}"}
#RequestMapping(method=GET, value={"/", "/{id}"})
public void get(#PathVariable Optional<Integer> id) {
if (id.isPresent()) {
id.get() //returns the id
}
}
There's no way to make it optional, but you can create two methods with one having the #RequestMapping({"customer"}) annotation and the other having #RequestMapping({"customer/{id}"}) and then act accordingly in each.
I know this is an old question, but searching for "optional path variable" puts this answer high so i thought it would be worth pointing out that since Spring 4.1 using Java 1.8 this is possible using the java.util.Optional class.
an example would be (note the value must list all the potential routes that needs to match, ie. with the id path variable and without. Props to #martin-cmarko for pointing that out)
#RequestMapping(method=GET, value={"/", "/{id}"})
public void get(#PathVariable Optional<Integer> id) {
if (id.isPresent()) {
id.get() //returns the id
}
}
VTToms answer will not work as without id in path it will not be matched (i.e will not find corresponding HandlerMapping) and consequently controller will not be hit. Rather you can do -
#RequestMapping({"customer/{id}","customer"})
public String surveys(HttpServletRequest request, #PathVariable Map<String, String> pathVariablesMap, Map<String, Object> map) {
if (pathVariablesMap.containsKey("id")) {
//corresponds to path "customer/{id}"
}
else {
//corresponds to path "customer"
}
}
You can also use java.util.Optional which others have mentioned but it requires requires Spring 4.1+ and Java 1.8..
There is a problem with using 'Optional'(#PathVariable Optional id) or Map (#PathVariable Map pathVariables) in that if you then try to create a HATEOAS link by calling the controller method it will fail because Spring-hateoas seems to be pre java8 and has no support for 'Optional'. It also fails to call any method with #PathVariable Map annotation.
Here is an example that demonstrates the failure of Map
#RequestMapping(value={"/subs","/masterclient/{masterclient}/subs"}, method = RequestMethod.GET)
public List<Jobs> getJobListTest(
#PathVariable Map<String, String> pathVariables,
#RequestParam(value="count", required = false, defaultValue = defaultCount) int count)
{
if (pathVariables.containsKey("masterclient"))
{
System.out.println("Master Client = " + pathVariables.get("masterclient"));
}
else
{
System.out.println("No Master Client");
}
//Add a Link to the self here.
List list = new ArrayList<Jobs>();
list.add(linkTo(methodOn(ControllerJobs.class).getJobListTest(pathVariables, count)).withSelfRel());
return list;
}
I know this is an old question, but as none of the answers provide some updated information and as I was passing by this, I would like to add my contribution:
Since Spring MVC 4.3.3 introduced Web Improvements,
#PathVariable(required = false) //true is default value
is legal and possible.
#RequestMapping(path = {"/customer", "/customer/{id}"})
public String getCustomerById(#PathVariable("id") Optional<Long> id)
throws RecordNotFoundException
{
if(id.isPresent()) {
//get specific customer
} else {
//get all customer or any thing you want
}
}
Now all URLs are mapped and will work.
/customer/123
/customer/1000
/customer - WORKS NOW !!
I've a legacy system where query parameters are used to determine the class/method for a request using a simple in-house framework. E.g.
/endpoint?product=foo&action=bar&amount=1.0
/endpoint?product=foo&action=baz&amount=1.0
And I'd like map all actions for a product to one class so the plumbing can be greatly simplified, e.g.
#Controller
#RequestMapping("/endpoint/foo/**")
public class FooController {
#AutoWire
private FooProductService s; // one of many beans that have to be wired into lots of classes
#RequestMapping("/bar")
public void bar(#PathVariable String amount, Model model) {
// implementation omitted
}
#RequestMapping("/baz")
public void baz(#PathVariable String amount, Model model) {
// implementation omitted
}
}
It's a published API so we can't change the public API -> the URLs cannot change.
I thought that perhaps this could be done using configuration, as aspect or even a custom framework with out own annotations.
I believe you can do something like this for product=foo&action=bar&amount=1.0
#RequestMapping(value = "product={productName}&action={someaction}&amount={value}", method=RequestMethod.GET)
something(#PathVariable String productName, #PathVariable String someaction, #PathVariable String value)
if(someaction.equals("bar")) {
//do bar
}
if(someaction.equals("baz")) {
//do baz
}
}
HTH
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.
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.