I'm trying to learn the latest Spring Boot and am going through some of their documentation on handling form submission. I'm taking a look at the code for the controller they use for the GET that serves up the view containing the form, and which also handles capturing the information from the POST.
package hello;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
#Controller
public class GreetingController {
#GetMapping("/greeting")
public String greetingForm(Model model) { // where does 'model' come from?
model.addAttribute("greeting", new Greeting());
return "greeting";
}
#PostMapping("/greeting")
public String greetingSubmit(#ModelAttribute Greeting greeting) {
return "result";
}
}
What I don't understand is how does the greetingForm(Model model) method take a parameter? The GET request surely isn't sending a whole Model over in its request, just the URI, right? Also, they don't list what the Model class code is, so I can't examine that.
This is their explanation:
The greetingForm() method uses a Model object to expose a new Greeting
to the view template. The Greeting object in the following code
contains fields such as id and content that correspond to the form
fields in the greeting view, and will be used to capture the
information from the form.
I also don't understand how just returning the string "greeting" translates into a view being served. Is there another layer which is actually calling this method?
I come from a Play! Framework background, and I'm used to my controller endpoints having to initialize and send the entire template back with the response - so this is confusing.
Spring does a lot of work on it's own to determine what to inject into controller handler methods and what to do with the return value.
From the Spring docs:
Handler methods annotated with this annotation can have very flexible signatures. The exact details of the supported method arguments and return values depend on the specific #Controller model supported. Both Spring Web MVC and Spring WebFlux support this annotation with some differences. More details are available in the Spring Framework reference.
Spring analyzes the arguments of the method. Model is a type that Spring understands, so it is injected into the method when called.
Handler methods can also have a variety of return types. When the return type is a String, Spring understands that to mean the name of a view to render and return to the client. This is also where the Model comes in; the values you put into the Model instance are bound to the view during rendering.
Related
I'm walking through a tutorial on Spring Boot using Kotlin. I'm new to Kotlin and Spring, though I've already worked in a couple of different MVC style frameworks in other languages so get the gist.
Something that I don't get as I work through this tutorial is how in these controller methods:
package com.example.blog
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping
#Controller
class HtmlController(private val repository: ArticleRepository) {
#GetMapping("/")
fun blog(model: Model): String {
model["title"] = "Blog"
model["articles"] = repository.findAllByOrderByAddedAtDesc().map {
it.render()
}
return "blog"
}
}
Where we see return "blog" at the end Spring knows to return a mustache template with the provided model.
I would have expected something like return MustacheView("blog", model)(I know that's not a real class or method, just psudocoding to exemplify) or something like that where you're explicitly saying "hey return this as a mustache template and not just the string blog"..
I'm sure there's some magic behind the scenes where spring says "oh hey, you have the mustache dependency installed, when you return this string from a controller you must be referring to a template name", but I can't see in the documentation where that's spelled out.
It's not completely necessary to know to finish the tutorial, but it feels like a bit of unexplained magic at the moment and I'd like to know what's going on.
When you return a String, it is used as a view name. This view name is then resolved into a View implementation.
In the case of Mustache, Spring Boot auto-configures a MustacheViewResolver bean. This bean is then picked up by Spring MVC and used to turn your "blog" into a MustacheView backed by your Mustache template. It is then combined with the Model that was passed into your controller method and rendered.
In our REST-API we need to be multi-tenant capable. For achiving this all rest controllers subclass a common REST controller which defines a request mapping prefix and exposes a model attribute as follows
#RequestMapping(path = "/{tenantKey}/api")
public class ApiController {
#ModelAttribute
public Tenant getTenant(#PathVariable("tenantKey") String tenantKey) {
return repository.findByTenantKey(tenantKey);
}
}
Derived controllers make use of the model attributes in their request mapping methods:
#RestController
public class FooController extends ApiController {
#RequestMapping(value = "/foo", method = GET)
public List<Foo> getFoo(#ApiIgnore #ModelAttribute Tenant tenant) {
return service.getFoos(tenant);
}
}
This endpoint gets well documented in the swagger-ui. I get an endpoint documented with a GET mapping for path /{tenantKey}/api/foo.
My issue is, that the {tenantKey} path variable is not documented in swagger-ui as parameter. The parameters section in swagger is not rendered at all. If I add a String parameter to controller method, annotating it with #PathVariable("tenantKey) everything is fine, but I don't want a tenantKey parameter in my controller method, since the resolved tenant is already available as model attribute.
So my question is: Is there a way do get the #PathVariable from the #ModelAttriute annotated method in ApiController documented within swagger-ui in this setup?
Project-Setup is
Spring-Boot (1.4.2)
springfox-swagger2 (2.6.1)
springfox-swagger-ui (2.6.1)
This is certainly possible. Model attributes on methods are not supported currently. Instead, you could take the following approach.
Mark the getTenant method with an #ApiIgnore (not sure if it gets treated as a request mapping.)
In your docket you can add tenantKey global path variable (to all end points). Since this is a multi-tenant app it's assuming this applies to all endpoints.
I am trying to retrieve the arguments of a grails controller method using an annotation and an Aspect that executes before the method.
The aspect handler executes correctly but i cant access the argument(which implements grails.validation.Validateable) the list of arguments is empty.
experiment.aspect.validated
package experiment.aspect
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
/**
* Created by Vaggelis on 10/13/2016.
*/
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#interface Validated {
}
experiment.aspect.ValidatedAspect
package experiment.aspect
import grails.validation.ValidationException
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
/**
* Created by Vaggelis on 10/13/2016.
*/
#Aspect
class ValidatedAspect {
#Before("#annotation(experiment.aspect.Validated)")
public void preValidate(JoinPoint point) throws ValidationException{
println "parameters ${point.getArgs()}"
}
}
conf.spring.resources
import experiment.aspect.ValidatedAspect
// Place your Spring DSL code here
beans = {
validatedAspect(ValidatedAspect)
}
controllers.experiment.TestController
package experiment
import experiment.aspect.Validated
class TestController extends BaseController {
static responseFormats = ['json']
#Validated
def login(LoginCommand loginCommand){
println "Validated"
...
}
}
experiment.LoginCommand
package experiment
/**
* Created by Vaggelis on 9/14/2016.
*/
import grails.validation.Validateable
class LoginCommand implements Validateable {
// String id
String name
static constraints = {
name blank: false
}
}
I get the following output, which means that the aspect handler method runs before the controller method but it does not get the arguments.
parameters []
Validated
You're seeing no args because there aren't any.
To support databinding and to keep things simpler when the servlet decides which controller and method to call, an AST transform creates a new method with the same name and no args for all action methods that have any args. The zero-arg method is the one called initially by the servlet, and it has the logic added by the transform to make the data binding calls. After that's done it then it calls your parameterized method with param strings converted to int/long/boolean/command objects/etc.
The no-arg method will also be annotated with #grails.web.Action and the arg types specified in the commandObjects attribute. This will help find the other method, but it's actually simple because you'll get a compiler error if you declare overloaded public methods.
Having said all that, you probably don't want to use this approach even if it did work - there's already two existing standard ways to intercept controller action calls, Grails filters (now interceptors in 3.x) and servlet filters. Grails filters and interceptors make it trivial to inspect and optionally add, modify or delete request parameters, and if you return false from a 'before' call you will stop Grails from handling the request (e.g. because you rendered it yourself, or sent a redirect, etc.)
I am trying to build RESTful web service by using spring 4.0
I have a controller:
#Controller
#RequestMapping("test")
public class Controller{
#RequestMapping("fetch",method=RequestMethod.GET)
#ResponseStatus(value=HttpStatus.OK)
#ResponseBody
public ResultResponse fetchController(ResultRequest req){
if((req.getName).equals("John"))
return new ResultResponse(100);
else
return new ResultResponse(0);
}
}
and my ResultRequest.class
public class ResultRequest{
private String name;
//getter,setter
}
If I hit the url to //host//contextPath/test/fetch?name=John
the controller will return the object ResultResponse(100)
my question is, there no #RequestParam or other annotation in the request parameter,
how does the spring controller know to set the query parameter "name" as the property of wrapper class
ResultRequest ?
Thanks
Spring uses implementations of an interface called HandlerMethodArgumentResolver for resolving arguments to pass to handler methods, ie. methods annotated with #RequestMapping.
One of these is a ModelAttributeMethodProcessor. Its javadoc states
Resolves method arguments annotated with #ModelAttribute and handles
return values from methods annotated with #ModelAttribute.
Model attributes are obtained from the model or if not found possibly
created with a default constructor if it is available. Once created,
the attributed is populated with request data via data binding and
also validation may be applied if the argument is annotated with
#javax.validation.Valid.
When this handler is created with annotationNotRequired=true, any
non-simple type argument and return value is regarded as a model
attribute with or without the presence of an #ModelAttribute.
Spring registers two objects of this type. One to handle parameters annotated with #ModelAttribute and one to handle ones without.
Further reading:
Form submit in Spring MVC 3 - explanation
An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the #RequestBody or the #RequestPart arguments
I'm currently working on a Spring MVC application and as I mapped, within the web.xml file, all incoming URL to a single DispatcherServlet, I wanted to know whether it would be possible to retrieve the URI that has been effectively mapped. Here's an example to illustrate my concerns :
import static org.springframework.web.bind.annotation.RequestMethod.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class HomeController {
#RequestMapping(method={GET})
public String processAllRequest(){
return "viewName";
}
}
Since I've defined the URL-MAPPING in the web.xml as being "/*", all incoming requests will end up in my controller class, show above. For instance, both the following requests will be processed by the processAllRequest() method from my controller.
myApplicationContext/home
myApplicationContext/logout
Is it possible, somehow, to retrieve the mapped URI? That is, once I'm inside the processAllRequest(), how could I know if it's been called for .../home or .../logout?
Is it possible to retrieve this kind of info by injecting an HttpServletRequest or another object as argument of the method?
Spring does inject HttpServletRequest if you put it in your handler arguments, so you can do that if you like.
But if you need to distinguish between different URLs, just place them in different handlers:
#Controller
public class HomeController {
#RequestMapping(value="/home", method={GET})
public String processHome(){
return "viewName";
}
#RequestMapping(value="/login", method={GET})
public String processLogin(){
return "viewName";
}
}
The mapping in web.xml forwards all requests to the spring servlet. You can still write as many #Controllers as you like, and play with class-level and method-level #RequestMapping to split the application into logical components.
I might have formulated my question in an ambiguous way but what I was looking for, was rather path variable. So my problem was solved this way :
#Controller
public class HomeController {
#RequestMapping(value="/{uri}", method={GET})
public String processMappedUri(#PathVariable uri){
return uri;
}
}
With this solution whenever I request the following requests, the uri argument from my processMappedUri() method will hold the variable value :
myApplicationContext/home --> uri = home
myApplicationContext/logout --> uri = logout