Understanding view resolution in spring mvc - java

I'm trying to understand how spring mvc generates a markup. For instance, consider the simple controller:
#Controller
public class HelloController{
#RequestMapping("/hello")
public String hello(){
return "hello";
}
}
and say, that we're applying UrlBasedViewResolver defined in the dispatcher-servlet.xml as follows:
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="prefix" value="/WEB-INF/views/">
<property name="suffix" value="jsp">
</bean>
What the javadoc of the resolver does is says that we have three methods that return the instance of the View interface.
The first one is
protected AbstractUrlBasedView buildView(String viewName),
the second is
protected View createView(String viewName, Locale locale)
and the third is
protected View loadView(String viewName, Locale locale).
As long as the View interface has the method render(Map<String,?> model, HttpServletRequest request, HttpServletResponse response) I'd assume that once the instance of View has been created we call this method to render the markup to the client. But I'm not sure if it actually works that way.
In general, my question is what method takes the a jsp-page and return the instance of View to be rendered to the client.

In general, my question is what method takes the a jsp-page and return the instance of View to be rendered to the client.
In the case of UrlBasedViewResolver, that would be the createView method, which for a JSP will return an InternalResourceView.
As far as the view resolution framework is concerned, the ViewResolver interface is the entry point, and has a method resolveViewName which takes the view name ("hello" in your example") and returns a View object, then calls render on that.
The buildView, createView and loadView methods are all internal specific to the UrlBasedViewResolver implementation of ViewResolver.

Related

What is the relation between #RequestMapping and Locale in Spring Boot?

If my understanding is correct, you can add/remove params for saveEmployee() freely. For example, when you add "loc" as follows, saveEmployee() receives the "non-null object" when the event happens. And the same goes for queryParams.
#Controller
public class Employee {
#RequestMapping("/save")
public void saveEmployee(Locale loc,
#RequestParam Map<String, String> queryParams) {
// saving employee
}
}
How could this method receive non-null Locale object by just adding a param "loc" here?
I would like to know the logic behind this.
Spring does it for you by using LocaleResolver or LocaleContextResolver, for the current request locale, determined by the most specific locale resolver available, in effect, the configured LocaleResolver / LocaleContextResolver in an MVC environment.
21.3.3 Defining #RequestMapping handler methods
An #RequestMapping handler method can have a very flexible signatures. The supported method arguments and return values are described in the following section. Most arguments can be used in arbitrary order with the only exception of BindingResult arguments.
Supported method argument types
java.util.Locale for the current request locale, determined by the most specific locale resolver available, in effect, the configured LocaleResolver / LocaleContextResolver in an MVC environment.
Spring's DispatcherServlet which forwards request from client to your controller gives you that parameters. In order to do that, it search the object from ApplicationContext to which bean(Controller) belongs.
Spring looks at the method arguments, their types and annotations, then determines if it can provide an object of that type/annotation.
If it cannot, it'll throw an exception, otherwise it will call the method with the object it decided fits the type/annotation.
For the list of supported types/annotations, read the documentation:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-arguments
As you can see, java.util.Locale is listed.
I think you need xml setting for your locale
Do you want to try the followning xml setting
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="en" />
</bean>
<mvc:interceptors>
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="language" />
</bean>
</mvc:interceptors>

Using Spring "forward:" prefix resulting in StackOverflowError

I'm attempting to forward a request received from one Controller to a second Controller using Spring's "forward:" prefix. However, instead of forwarding the request on to the second Controller as I would expect, the request is instead handled by the first Controller over and over again (until I receive a StackOverflowError). This made me think it might be treating the "/app/pong" as a relative path of some sort, but I'm not sure why this would be the case. Am I misunderstanding how "forward:" is supposed to work? Is there something that I'm missing here?
Controllers:
#Controller
public class ControllerOne {
#RequestMapping(value = "/ping", method = RequestMethod.GET)
public String doPing(HttpServletRequest request) {
log.debug("Ping?");
return "forward:/app/pong";
}
}
#Controller
public class ControllerTwo {
#RequestMapping(value = "/pong", method = RequestMethod.GET)
public String doPong(HttpServletRequest request) {
log.debug("Pong!");
return "pong";
}
}
servlet-mapping:
<servlet-mapping>
<servlet-name>test-servlet</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
view resolver:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
I think the problem is about the InternalResourceViewResolver that you configed. Because the inner logic of this viewResolver, it will do a forward automatically, you can config another kinds of viewResolver: UrlBasedViewResolver .
Below is the information I found in official document:
It is also possible to use a special forward: prefix for view names
that are ultimately resolved by UrlBasedViewResolver and subclasses.
This creates an InternalResourceView (which ultimately does a
RequestDispatcher.forward()) around the rest of the view name, which
is considered a URL. Therefore, this prefix is not useful with
InternalResourceViewResolver and InternalResourceView (for JSPs for example). But the prefix can be helpful when you are primarily
using another view technology, but still want to force a forward of a
resource to be handled by the Servlet/JSP engine. (Note that you may
also chain multiple view resolvers, instead.)

Creating a custom view resolver in Spring MVC: Property 'url' is required

I'm trying create a simple view resolver that returns hello world regardless of what view you want (as a starting point).
I have this so far:
public class MyViewResolver extends AbstractTemplateView {
#Override
protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
doRender(model, request, response);
}
protected void doRender(Map<String,Object> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
PrintWriter writer = response.getWriter();
writer.write("hi from my resolver!");
}
}
Now I am getting this error:
2012-03-29 16:51:28.855:WARN:/:unavailable
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'viewResolver' defined in ServletContext resource [/WEB-INF/application-context.xml]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'url' is required
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1455)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
I have implemented whatever the AbstractTemplateView required, not sure what url property it is asking for?
Also, where is the name of the view that is passed to this viewresolver?
Update
So I added:
#Override
public boolean isUrlRequired() {
return false;
}
And now I am just getting an error like:
HTTP ERROR 404
Problem accessing /home/index. Reason:
NOT_FOUND
My application-context.xml has:
<bean id="viewResolver" class="com.example.MyViewResolver">
</bean>
What am I missing something?
You extend (indirectly) AbstractUrlBasedViewResolver, so it's logical that a URL is required to resolve the view. However, if in your case it is not required, you can override the isUrlRequired() method and return false
You have mixed up two concepts in Spring MVC. In Spring MVC you need both a view resolver and a view.
In your question you want two things:
A view that returns "hello world".
A view resolver that passes all
returned view names to the above view.
To create the view:
Extend: org.springframework.web.servlet.View
Your implementation can then write "hello world" to the http response.
To create the view resolver:
Extend: org.springframework.web.servlet.ViewResolver
Your implementation should always te return your previously created view.
You can see that the viewresolver base class is where you have acceess to the returned view name. See: https://fisheye.springsource.org/browse/spring-framework/spring-webmvc/src/main/java/org/springframework/web/servlet/ViewResolver.java
This is the most basic way to answer your question. Spring offers many implementations of these classes for more specialised purposes that may suit your use case.
Hope this helps. Let me know if you need any more details.
You have started out wrong: instead of a ViewResolver, you are implementing a View. See Spring's documentation on this, there are many base classes you can start from. As for the view name, it will be quite obvious once you get on the right track since the method View resolveViewName(String viewName, Locale locale) is the only one in the ViewResolver interface.
But, judging from your confusion of these concepts, maybe your real question is "help me make a Hello World in Spring MVC". In that case I should really direct you to the samples that come with the Spring MVC distribution, but here's something for a quick start. First of all, you don't need to implement a ViewResolver or even a View. The most basic thing you need is a Controller, which can directly produce the response:
public class MyController implements Controller
{
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
{
response.getWriter().println("Hello, world!");
return null;
}
}
Next, you need a HandlerMapping, this is the simplest variant:
<bean id="handlerMapping"
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
Finally, declare your controller and set its name to the URL you want it to serve:
<bean name="/home/index" class="MyController" />
If you dig into the nested exception you will see
Caused by: java.lang.IllegalArgumentException: Property 'url' is required
at org.springframework.web.servlet.view.AbstractUrlBasedView.afterPropertiesSet(AbstractUrlBasedView.java:67)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$6.run(AbstractAutowireCapableBeanFactory.java:1504)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1502)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
... 37 more
So Exception came at AbstractUrlBasedView.java: line 67, if you see the spring source file #fisheye.springsource.com you will find the following code
public void afterPropertiesSet() throws Exception {
if (isUrlRequired() && getUrl() == null) {
throw new IllegalArgumentException("Property 'url' is required");
}
}
/**
* Return whether the 'url' property is required.
* <p>The default implementation returns <code>true</code.
* This can be overridden in subclasses.
*/
protected boolean isUrlRequired() {
return true;
}
So it is checking for if isUrlRequired and getURL is null it will show the above exception, so by changing the isUrlRequired to false you can avoid the 1st exception

Add attributes to the model of all controllers in Spring 3

Every single view in my Spring 3 app has a set of attributes they can rely on. So the first line of every controller is something like:
ControllerHelper.addDefaultModel(model, personManager, request);
In there I'll add
user object and full name retrieved from the database if person is logged in
set of variables which are typically set once (e.g. imagesHost)
set of languages a visitor can switch to
current language
some statistics (e.g. total # of people in our system)
This all allows each view to display the logged in user's name, easily reference an image location, a list of languages and some overall stats about the site.
So the question is, is the controller model object the best place to store all the data or is there a more convenient place which makes it just as easy for views to access this info?
And secondly, I'd REALLY love not to have to have the ControllerHelper line above as the first line in every controller. It's actually not always the first line, sometimes I first check if I need to redirect in that controller, because I don't want to waste resources filling the model for no reason. Maybe a filter or annotation or some Spring callback mechanism could make sure the ControllerHelper code is called after the controller is finished but right before the view is rendered, skipping this if a redirect was returned?
You could write an org.springframework.web.servlet.HandlerInterceptor. (or its convenience subclass HandlerInterceptorAdapter)
#See: Spring Reference chapter:
15.4.1 Intercepting requests - the HandlerInterceptor interface
It has the method:
void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception;
This method is invoked after the controller is done and before the view is rendered. So you can use it, to add some properties to the ModelMap
An example:
/**
* Add the current version under name {#link #VERSION_MODEL_ATTRIBUTE_NAME}
* to each model.
* #author Ralph
*/
public class VersionAddingHandlerInterceptor extends HandlerInterceptorAdapter {
/**
* The name under which the version is added to the model map.
*/
public static final String VERSION_MODEL_ATTRIBUTE_NAME =
"VersionAddingHandlerInterceptor_version";
/**
* it is my personal implmentation
* I wanted to demonstrate something usefull
*/
private VersionService versionService;
public VersionAddingHandlerInterceptor(final VersionService versionService) {
this.versionService = versionService;
}
#Override
public void postHandle(final HttpServletRequest request,
final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
modelAndView.getModelMap().
addAttribute(VERSION_MODEL_ATTRIBUTE_NAME,
versionService.getVersion());
}
}
}
webmvc-config.xml
<mvc:interceptors>
<bean class="demo.VersionAddingHandlerInterceptor" autowire="constructor" />
</mvc:interceptors>
You can also use #ModelAttribute on methods e.g.
#ModelAttribute("version")
public String getVersion() {
return versionService.getVersion();
}
This will add it for all request mappings in a controller. If you put this in a super class then it could be available to all controllers that extend it.
You could use a Controller Class annotated with #ControllerAdvice
"#ControllerAdvice was introduced in 3.2 for #ExceptionHandler, #ModelAttribute, and #InitBinder methods shared across all or a subset of controllers."
for some info about it have a look at this part of the video recorded during SpringOne2GX 2014
http://www.youtube.com/watch?v=yxKJsgNYDQI&t=6m33s
like #blank answer this work for me:
#ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {
#Autowired
UserServiceImpl userService;
#ModelAttribute("currentUser")
public User getCurrentUser() {
UserDetails userDetails = (UserDetails)
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userService.findUserByEmail(userDetails.getUsername());
}
}
There is one issue that occurs with redirection when using #ModelAttribute or HandlerInterceptor approach. When the handler returns Redirect view, the model attributes added this way are appended as query parameters.
Another way to handle this situation is to create session-scoped bean, that can be autowired in base application controller, or explicitelly in every controller where access is needed.
Details about available scopes and usage can be found here.
if you need add some global variables that every view can resolve these variables,
why not define into a properties or map?
then use spring DI, refer to the view resolver bean.
it is very useful,such as static veriable, e.g. resUrl
<property name="viewResolvers">
<list>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="attributes" ref="env" />
<property name="exposeContextBeansAsAttributes" value="false" />
<property name="prefix" value="${webmvc.view.prefix}" />
<property name="suffix" value="${webmvc.view.suffix}" />
</bean>
</list>
</property>

Spring MappingJacksonJsonView, how to tell to use it instead of JSP view?

I'm trying to use MappingJacksonJsonView with Spring 3.0, without success. I don't know what I'm doing wrong, I think the problem is that I don't know how to tell to use the MappingJacksonJsonView to render a request. I tried to use the same name for view name and bean name of MappingJacksonView, but didn't work. I built a sample test application here: https://github.com/stivlo/restjson
In web.xml I've defined ContextLoaderListener and the mapping for dispatcherServlet.
In servlet-context.xml I've added
<mvc:annotation-driven/>
and
<bean name="jsonView"
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
In org.obliquid.restjson.web.ToDoList.java I set the logical view name as jsonView.
However, instead of using MappingJacksonJsonView, it looks for a JSP file, according to my JSP mapping.
message /restjson/WEB-INF/jsp/jsonView.jsp
description The requested resource (/restjson/WEB-INF/jsp/jsonView.jsp)
is not available.
What should I change to use MappingJacksonJsonView as a renderer?
UPDATE 1: In following tests I've found that if I add the following to my servlet-context.xml, JSON rendering works, but my other view, rendered as JSP (home) is not working anymore.
<!-- Resolve views based on string names -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
UPDATE 2: I removed the BeanNameViewResolver and changed my ToDoList.java to return directly the Collection to be converted in JSON, instead of ModelAndView, with a #ResponseBody annotation, as follows:
#RequestMapping("/toDoList")
public #ResponseBody List<ToDoItem> test() {
List<ToDoItem> toDoList = new ArrayList<ToDoItem>();
toDoList.add(new ToDoItem(1, "First thing, first"));
toDoList.add(new ToDoItem(1, "After that, do the second task"));
return toDoList;
}
In this way it works. Even though the mapping is even more "magical". It makes me wonder, if a similar renderer exists for XML for instance, how does Spring know which renderer to pick?
Spring will use Accept header sent by the client to return most appropriate view. Here you will find my complete Spring MVC application that returns both JSON and XML.
As you can see, I only needed:
<mvc:annotation-driven />
I also used the same annotations: #RequestMapping to map request to a method and #ResponseBody to tell Spring that what I am returning from the controller is the actual response. It might however need some tweaking/formatting, and here Spring takes care of marshalling your object into most appropriate type like JSON.
You should do it this way:
In your xml file set the following: set
<mvc:annotation-driven />
After it you need to set Jackson serializer:
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jacksonMessageConverter"/>
</list>
</property>
</bean>
after it you can use it in your Controller:
#RequestMapping(value="/getObjects",method = RequestMethod.POST)
#ResponseBody
public List<MyObject> getCategories(){
List<MyObject> objects = daoService.gettAllObjects();
return objects;
}
Adding the following worked in my case
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="0" />
</bean>
So basically we should try to resolve any view as a bean first
you will need to see ContentNegotiatingViewResolver,and set defaultviews property to MappingJacksonJsonView, and #ResponseBody uses HttpMessageConverter to instead of ViewSolver,see the differences between them
http://ufasoli.blogspot.com/2013/08/viewresolver-vs-messageconverter-spring.html

Categories