Before asking question first i describe the scenario. I have multiple servlet contexts. suppose /**book**/review url is corresponding to book-servlet.xml and /**report**/update corresponds to report-servlet.xml.
Here are two controllers
#Controller
#SessionAttributes(BookController.COMMAND_NAME)
public class BookController {
public static final String COMMAND_NAME = "login";
#GetMapping("/book/review")
public String show(ModelMap modelMap) {
modelMap.put(COMMAND_NAME, getBook());
return "redirects:" + "/report/update"; //it redirects to second controller method
}
}
#Controller
#SessionAttributes(ReportController.COMMAND_NAME)
public class ReportController {
public static final String COMMAND_NAME = "report";
#GetMapping("/report/update")
public String show(ModelMap modelMap) {
modelMap.put(COMMAND_NAME, getReport());
return "redirects:" + "/report/done";
}
}
Observe this two controller. When i put getBook() in model it stores this object in session after method excution since #SessionAttributes(BookController.COMMAND_NAME) is added above class definition. But, after redirection to /report/update (different servlet context) we are also trying to put getReport() in model. And after handler method excution spring should put the object in session as it does for first case.Unfortunately for second controller it doesn't store it in session.
My question is if the first controller method can successfully save it in session why the second controller can't? Or is it for servlet context switching / multiple get requests or something like this. Please i need to know the reason it behaves weirdly. I am now totally confused about #SessionAttributes. It should work same for both controllers.
After studying on this i found that it is wise to use RedirectAttributes for this type of redirect scenario. Cause ModelMap is intended for scenario where you use this ModelMap attributes to rendering a view. And FlashAttribute will survive after immediate redirection to another handler method and then be erased. So, i did my solution using RedirectAttrinute support from spring.
Related
What's a good way to structure spring web controllers when there are different web pages that share functionality?
Let's say there are a Tasks and a Task web page. I can start a task from both pages and I expect that I will remain on the same page when doing so .
What's the best way to do this? Am i forced to duplicate logic as follows:
#Controller
#RequestMapping("/tasks")
public class TasksController {
#GetMapping("/{id}/start")
public String start(#PathVariable String id) {
tasks.start(id);
return "redirect:/tasks.html";
}
}
and
#Controller
#RequestMapping("/task")
public class TaskController {
#GetMapping("/{id}/start")
public String start(#PathVariable String id) {
tasks.start(id);
return "redirect:/task.html";
}
}
ps. I'm not interested in async JavaScipt solutions.
You could use the Regex feature in path variables and get the page name in another variable. So, I would solve this the following way:
#Controller
public class TaskController {
...
#GetMapping({"/{page:tasks?}/{id}/start")
public String start(#PathVariable String page, #PathVariable String id) {
tasks.start(id);
return "redirect:/" + page + ".html";
}
}
If there's more logic or the entry points are quite different extract the common code to a service.
My thought: It is just a matter of how your design and name the controllers. Ideally, I create One controller per business domain object. instead of creating controllers for each page. If we name the Controller and Services for each business domain object, you could avoid this. So i would just have TaskController and call the same URI irrespective of where you call it either from Task page, TaskDetail page or TaskReport page.
I want to return a model from my Spring MVC controller, which will be visible to all pages in my application.
So, I need a variable which return some user details.
This is my code:
#RequestMapping(value = "/*", method = RequestMethod.GET)
public void status(Model model){
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
String username = auth.getName();
Manager mgr = managerService.findOne(username);
model.addAttribute("buget", mgr.getTeam().getTeamBuget());
}
And in my jsp page, i write something like this:
<li> <c:out value="${buget}" /> <span class="glyphicon glyphicon-euro"></span></li>
I want to be able to print the ${buget} in every page from my app.
So, my method don't work, nothing is appear in my pages and the biggest problem
for me is that I don't get any exceptions or errors which could help me. Who can help me with some advices?
I'm not entirely sure what it is that you're trying to do. Do you mean that you want buget to be part of every page that you hit? If so, you have to insert it into the model. Based on your code, I'm assuming that you have the mistaken impression that status is going to be called regardless of whatever page you hit. Spring will resolve to the most-specific handler and so if you have another handler method in another controller that is more specific, Spring will use that. But even if the one you have was called, how would Spring know that it has to call the most-specific one next? So how would you add page-dependent model attributes to the model?
If you want buget to be part of every response, you can use #ControllerAdvice (see here for a blog post with more details):
#ControllerAdvice
public class BugetControllerAdvice {
#ModelAttribute
public void addBugetToModel(Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Manager mgr = managerService.findOne(username);
model.addAttribute("buget", mgr.getTeam().getTeamBuget());
}
}
Spring will now call this method before every handler-method in every controller. You can also take a look at the Spring Reference for more information.
I'm assuming this is very easy to do, but I've been searching for a couple hours on Google and StackOverflow with no luck.
For starters, I have a jsp page using Dojo. I use Dojo's request module to make AJAX calls to Spring controllers for all sorts of things. The controllers call services, the services call DAOs, etc etc.
Originally, I had a properties file that contained several configuration settings for my site. These were injected into various Spring components using #Value. I wanted to make these dynamically configurable from my admin page, so I added a domain object called "SiteSettings" that holds the values from my properties file. When my webapp starts up, if it can't fine a SiteSettings object in persistence, it creates a new one, populates it with the settings from my properties file, and persists it. If one is already in persistence, it just uses it.
All of the site properties are used on the back-end except for one, a boolean called "authenticationRequired". I need to access this value in a javascript value on the front-end. This would be very easy to obtain via AJAX after the page loads, but I thought it's kind of silly to have to make an additional request, when I'm sure I should just be able to get the value from my SiteSettingsService (which uses the DAO to get the domain object).
Right now, I'm getting the value directly from the properties file:
<fmt:bundle basename="swtc">
<fmt:message key="swtc.authenticationRequired" var="authenticationRequired"/>
</fmt:bundle>
<script type="text/javascript">
window.authenticationRequired = <c:out value="${authenticationRequired}"/>;
</script>
How can I modify this get the value from my service/dao/domain object instead of directly from my properties file? Here are my controller and service files... nothing fancy:
Controller:
#RequestMapping(value = "/getSiteSettings", method = RequestMethod.GET)
#ResponseBody
public ModelMap getSiteSettings(ModelMap model) {
try {
SiteSettings siteSettings = siteSettingsService.getSiteSettings();
model.addAttribute("siteSettings", siteSettings);
model.addAttribute("success", true);
} catch (Exception ex) {
logger.error("There was an error getting the site settings data. ", ex);
model.addAttribute("exceptionMessage", ex.getLocalizedMessage());
model.addAttribute("success", false);
}
return model;
}
Service:
#Override
public SiteSettings getSiteSettings(){
List<SiteSettings> siteSettings = siteSettingsDao.findAll();
if (siteSettings != null && !siteSettings.isEmpty()){
return siteSettingsDao.findAll().get(0);
}
return null;
}
In the controller method that is responsible for rendering the overall jsp that requires the property, you should just be able to put the authenticationRequired attribute on the Model so it becomes available in the page for output into the Javascript. You'd need to autowire an instance of the SiteSettingsService into that controller:
#Controller
public class MainPageController { // or whatever it happens to be called
#Autowired
private SiteSettingsService siteSettingsService;
#RequestMapping(value="/mainPage", method=RequestMethod.GET)
public String displayMainPage(Model model) {
SiteSettings siteSettings = siteSettingsService.getSiteSettings();
// Make the property available to the view
model.addAttribute("authenticationRequired", siteSettings.isAuthenticationRequired());
return "mainPage";
}
}
Using spring MVC, I need to store my object into session and I should use the same object in several jsp pages using jstl. I have tried like this:
ModelAndView modelAndView = new ModelAndView("admin/addproduct", "category", categorynameList);
But I can't access the category to other jsp pages except addproduct page.
How can I do that?
Spring MVC provides more than one mechanisms that hide the plain use of HttpSession from you.
Have a look at the #SessionAttributes annotation, which allows you to define the attributes that will be stored in the session by your controller; this mechanism is mainly intended to maintain the conversational state for your handler and that state is usually cleared once the conversation is complete.
Also, you can define your bean as session scoped in the application context and then make use of a ScopedProxyFactoryBean (by simply adding an <aop:scoped-proxy/> element in your bean definition), thus being able to inject that bean in your singleton controllers.
You can pass the session directly to any annotated controller method:
#RequestMapping("somePathName")
public String someHandler(HttpSession session) {
session.setAttribute(...
The annotation #SessionAttributes is used on class level. Typically it's used on the #Controller class. It's 'value' element is of type String[] whose values are the matching names used in #ModelAttribute either on method level or on handler's method parameter level.
Let's consider following arrangement:
#Controller
#SessionAttributes("visitor")
#RequestMapping("/trades")
public class TradeController {
#ModelAttribute("visitor")
public Visitor getVisitor (....) {
return new Visitor(....);
}
....
}
In above code, when a request comes in, the first thing Spring will do is to notice #SessionAttributes('visitor') and then attempt to find the value of 'visitor' in javax.servlet.http.HttpSession. If it doesn't find the value, then the method with #ModelAttribute having the same name 'visitor' (method getVisitor()) will be invoked. The returned value from such method will be used to populate the session with name 'visitor'.
Now that 'visitor' with it's value is already in HttpSession, let's consider following arrangement:
#Controller
#SessionAttributes("visitor")
#RequestMapping("/trades")
public class TradeController {
#RequestMapping("/**")
public String handleRequestById (#ModelAttribute("visitor") Visitor visitor,
Model model,
HttpServletRequest request) {
model.addAttribute("msg", "trades request, serving page " +
request.getRequestURI());
visitor.addPageVisited(request.getRequestURI());
return "traders-page";
}
.......
}
On finding #ModelAttribute("visitor") in the target handler method, Spring will retrieve the value of 'visitor' from the session and will assign the value to the Visitor parameter and will invoke the method. At this step, Spring doesn't care how the session was populated with 'visitor', Whether it was populated using the last arrangement or some other way, it doesn't matter, Spring only requires the annotation #SessionAttributes('visitor'), the handler method parameter with #ModelAttribute("visitor") and the value of 'visitor' in HttpSession. If spring can't find it in the session and last arrangement is not available then following exception will be thrown:
org.springframework.web.HttpSessionRequiredException: Expected session attribute 'visitor'
In other words: when the target handler method is invoked first time in a given session, then method with #ModelAttribute('visitor) is invoked, the returned value is populated in HttpSession and the handler method with the same value is invoked. For the subsequent requests within the same HTTPSession, Spring retrieves the same object from the session and doesn't call the method with #ModelAttribute('visitor') again till the end of the session
Following is the complete controller class:
#Controller
#SessionAttributes("visitor")
#RequestMapping("/trades")
public class TradeController {
#RequestMapping("/**")
public String handleRequestById (#ModelAttribute("visitor") Visitor visitor,
Model model,
HttpServletRequest request) {
model.addAttribute("msg", "trades request, serving page " +
request.getRequestURI());
visitor.addPageVisited(request.getRequestURI());
return "traders-page";
}
#ModelAttribute("visitor")
public Visitor getVisitor (HttpServletRequest request) {
return new Visitor(request.getRemoteAddr());
}
}
In above example we are tracking the user visited pages within the same HTTP session. We are using the wildcard '/**' so that all request starting with 'trades' will map to this controller.
Inject session to your controller classes, store your object there and use it when you need it, e.g.:
#Controller
public class SomeController {
//#Autowired
//private HttpSession session; This is not desired. See the discussion in the
//comments.
#RequestMapping("somePathName")
public String someHandler(HttpSession session) { //Session will be injected by
//Spring without any additional annotations.
//...
session.setAttribute("category", yourObject);
}
}
If you need to access category on other pages than admin/addproduct you will need to add it to respective models as follows:
#RequestMapping("somePathName")
public String someHandler(Model model) {
//...
model.addAttribute("category", yourObject);
//...
return "yourPageName";
}
Update: according to ALex's comment that injecting HttpSession instance into field is strongly not desired due to thread safety concerns I've changed the source respectively.
This is the code example from Spring 3.1 Spring Source Blog: From XML to #Configuration I'm trying to implement in my application (which was done in Spring 2.0 not by me so it's lot of learning).
#FeatureConfiguration
class MvcFeatures {
#Feature
public MvcAnnotationDriven annotationDriven(ConversionService conversionService) {
return new MvcAnnotationDriven().conversionService(conversionService)
.argumentResolvers(new CustomArgumentResolver());
}
// ...
}
However, I can't understand the point of .argumentResolvers(new CustomArgumentResolver()) and their CustomArgumentResolver looks like bellow. What's the point of it?
public class CustomArgumentResolver implements WebArgumentResolver {
#Override
public Object resolveArgument(MethodParameter param, NativeWebRequest request) throws Exception {
RequestAttribute attr = param.getParameterAnnotation(RequestAttribute.class);
if (attr != null) {
return request.getAttribute(attr.value(), WebRequest.SCOPE_REQUEST);
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
To add to #GaryF's answer, and to clarify some points, Spring 2.5 introduced annotated controllers, which replaced the old interface-style controllers of Spring 2.0. These new controllers have methods with no fixed parameters - the method declares the parameters that it needs to do its job, and nothing more.
For example, say a controller method needed one thing to do its job - a request parameter that contains the ID of an object from the database. In Spring 2.0, you would need to implement something like AbstractController.handleRequestInternal(), e.g
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
String id = request.getParameter("id");
MyThing obj = getObjById(id);
//... do stuff and return ModelAndView
}
Spring 2.5 made that easier:
#RequestMapping
public ModelAndView handle(String id) {
MyThing obj = getObjById(id);
//... do stuff and return ModelAndView
}
Here, we only declare parameters for the stuff we need.
So far so good, but this is where a custom WebArgumentResolver comes in. Say I want to remove the getObjById from my controller altogether, because maybe I think it clutters up the code, and maybe it's used across many other controller methods. Instead, I want to do this:
#RequestMapping
public ModelAndView handle(MyThing id) {
//... do stuff and return ModelAndView
}
It's even simpler, and has a bare minimum of boilerplate code. A custom WebArgumentResolver can be registered with the app-context which recognises parameters of type MyThing, and knows how to extract the information from the request. Spring invokes that resolver, and passes the result to the controller method.
Custom resolvers aren't commonly used, but can be very handy in the right situation.
The example in your question uses CustomArgumentResolver to resolve the example's custom RequestAttribute class. The resolver pulls out request attributes and binds them to RequestAttribute objects, so that they can be declared as controller method parameters.
WebArgumentResolvers are a way for you to specify how the parameters of MVC-mapped methods should be resolved. If you'd like to use a custom object as a parameter for an MVC-mapped method, Spring tries to figure out how make sense of it in it's own way. Typically this would happen through binding, where some http parameters you submit match up with the fields of the object and Spring matches them up and creates a new object for you.
If you ever have a situation where the submitted parameters don't match up quite so neatly with your method parameters, WebArgumentResolvers are there to fill in the gap: you provide custom logic so Spring doesn't have to figure it out.
In your example, param is one such parameter to be matched up. This piece of custom code first checks if the parameter has an #RequestAttribute annotation. If it does, then the custom code pulls the value from that object and looks it up as an attribute on the http request, returning it. It it does not have that annotation, then the method returns the UNRESOLVED value, which simply indicates that this WebArgumentResolver doesn't know anything about this particular parameter and Spring should try a different method (such as binding).