I have written a book catalog in Spring.
It collects books (pdf, epub, mobi, ebook) from a directory, collects some metadata from them, stores them in a DB and then puts them in a List that is made available to my views:
#Slf4j
#Controller
public class BookCatalogController {
// == Fields ==
private final BookService bookService;
#Autowired
public BookCatalogController(BookService bookService){this.bookService = bookService; }
// == Model attributes ==
#ModelAttribute
public List<Book> bookData(){ return bookService.getBooksFromMemory(); }
public static final File bookDirectory= new File("D:\\edu_repo\\ebooks_test\\");
.
.
.
// Catalog Simple View
#GetMapping(Mappings.CATALOG_SIMPLE)
public String catalogSimple(Model model){
log.info("catalogSimple method called");
// This is adding the entire BookManager book list into the model.
model.addAttribute(AttributeNames.BOOK_DATA, bookData());
return ViewNames.CATALOG_SIMPLE;
}
// Catalog Detail View
#GetMapping(Mappings.CATALOG_DETAIL)
public String catalogDetail(Model model){
log.info("catalogDetail method called");
// This is adding the entire BookManager book list into the model.
model.addAttribute(AttributeNames.BOOK_DATA, bookData());
return ViewNames.CATALOG_DETAIL;
}
.
.
.
#GetMapping(Mappings.LOAD_BOOKS)
public void loadBooks(Model model) {
bookService.loadBooksFromDirectory(bookDirectory);
}
}
Obviously I'm not using #GetMapping(Mappings.LOAD_BOOKS) properly as you can see in the error below:
The error:
There was an unexpected error (type=Internal Server Error, status=500).
Error resolving template [load-books], template might not exist or might not be accessible by any of the configured Template Resolvers
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [load-books], template might not exist or might not be accessible by any of the configured Template Resolvers
How does one invoke a method like I am doing but without Spring trying to redirect the user to another view?
I'm not expecting the page to update at all since I'm not returning a View!
When you click a link in your browser with a load-books anchor, your browser sends it to the server and waits for result, which causes your page to be reloaded. Once the request to a load-books endpoint reached to the server, Spring MVC handles this and starting to looking up an appropriate controller with its method. It founds public void loadBooks(Model model) in your case. When Spring MVC invokes the method, it expects to obtain a view name to resolve and return back to your browser.
Since you haven't provided a View or String as a return type, Spring MVC used the endpoint's path as a view name (I'm not seeing your Mappings.LOAD_BOOKS constant, but it supposed to be load-books).
If you're not going to return any view back to the browser, you can annotate the method like that:
#GetMapping(Mappings.LOAD_BOOKS)
#ResponseBody
public void loadBooks(Model model) {
which tells Spring to treat void as a response body.
But it's not preventing a page refreshing, you'll just see an empty page after clicking the link. In order to fix this you can redirect a user to another page by returning the following string (without ResponseBody annotation on the method)
return "redirect:/path-to-redirect";
When Spring MVC sees this prefix it redirects you to another controller, but user going to notice that too.
If you really don't want to see a blank page for a moment, you'll have to use some JavaScript to perform AJAX request to the server when button is clicked.
Actually, it seems that you want to preload some files in a service by a given path. If it's all you want to do, you can use Spring's runners like that:
#Component
class Preloader implements ApplicationRunner {
private final BookCatalogService bookService;
#Autowired
public Preloader(BookCatalogService service) {
this.bookService = service;
}
#Override
public void run(ApplicationArguments args) throws Exception {
bookService.loadBooksFromDirectory(BookCatalogController.bookDirectory);
}
}
Spring automatically calls all registered runners when application is ready, so your code will be executed without having a user to visit load-books endpoint.
Related
I tested the below using Spring web MVC 4.3.5.
The following controller shows an example of a flash attribute working properly after a redirect:
#Controller
#RequestMapping("/redirection")
public class RedirectionController {
private static final Logger logger = Logger.getLogger(RedirectionController.class);
#RequestMapping(path="/pageA", method=RequestMethod.GET)
public String foo(RedirectAttributes redirectAttributes
, HttpServletRequest request
) {
redirectAttributes.addFlashAttribute("foo", "bar");
return String.format("redirect:%s", "/redirection/pageB.do");
}
#RequestMapping(path="/pageB", method=RequestMethod.GET)
public String boo(final HttpServletRequest request, final Model model) {
printModelAttributes(model);
return "page-b";
}
private static void printModelAttributes(final Model model) {
Map<String, Object> map = model.asMap();
logger.info(String.format("model attributes are: %s", map.keySet()));
}
}
When we visit the /pageA path with our browser we are automatically redirected to /pageB and the controller method for /pageB sees the flash attribute as expected:
14:35:18,740 INFO [webgui.RedirectionController] (http-/127.0.0.1:8082-3) model attributes are: [foo]
However, if I change the return statement the produces the redirect String from:
return String.format("redirect:%s", "/redirection/pageB.do");
… to:
return String.format("redirect:%s", "/.."+request.getContextPath()+"/redirection/pageB.do");
… we are still redirected to the same /pageB path but this time the flash attribute is missing:
14:34:51,892 INFO [webgui.RedirectionController] (http-/127.0.0.1:8082-3) model attributes are: []
I am assuming that this is due to Spring MVC trying to be clever and trying to guess if the redirection will take us to another application (different context path) in which case it does not bother placing the flash attributes in the session as they won't be available anyways in the other application.
Apparently the presence of the /.. in the redirect String causes Spring MVC to think that we are changing application.
My questions are:
is this feature documented?
is my explanation plausible or is this a bug?
In a Spring 4.2 webapp, I have a rights object in the session from which I can query whether the logged in user has access to a specific page. I can check the access rights from a controller and redirect to another page with a message. The problem is I have to repeat this for every request method in every controller.
I can move almost all the code to an interceptor but I have no access to RedirectAttributes from there, so I don't know how I could add the error message. Is there a way?
#Controller
#SessionAttributes("rights")
#RequestMapping("/f")
public class FController {
...
#RequestMapping(value="/edit/{id}", method=RequestMethod.GET)
public String edit(#PathVariable("id") int id,
#ModelAttribute("rights") UserRights rights,
RedirectAttributes redA, ModelMap model) throws SQLException {
if (!rights.canAccess("/f/edit")) {
redA.addFlashAttribute("errormessage", messages.getMessage("error.noright", null, Locale.getDefault()));
return "redirect:/f/list";
}
... // set up model
return "fEdit";
}
...
}
Yes you can! It is not as well integrated as what you get in controller methods, but you can get access to the output flash map in an interceptor. You can find references for that in the chapter on Using flash attributes in Spring Framework ReferenceManual. And thanks to the ModelAndViewDefiningException, you can still ask Spring to do the redirect and process the output flash map
You could just put something like this in an interceptor:
if (!rights.canAccess("/f/edit")) {
// get access to the output flash map
FlashMap redA = RequestContextUtils.getOutputFlashMap(HttpServletRequest request);
// add the redirect attributes
redA.put("errormessage", messages.getMessage("error.noright", null, Locale.getDefault()));
// prepare the redirection
ModelAndView redirMav = new ModelAndView("redirect:/f/list");
// ask the spring machinery to process the redirection
throw new ModelAndViewDefiningException(redirMav);
}
I have a webstore with a bunch of Spring MVC controllers defined for each JSP page such as HomePageController, CategoryPageController, ProductPageController, CartPageController etc. and all these controllers makes a call to the DB to fetch and display the menu on top of JSP pages. Each of the controllers mentioned above makes a call to the DB in order to fetch the menu.
I am using Spring 3.0 framework.
HomePageController.java
#Controller
#RequestMapping(HOME_PAGE_CONTROLLER)
public class HomePageController extends BasePageController {
#RequestMapping(method = RequestMethod.GET)
protected ModelAndView processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ModelAndView mav = new ModelAndView();
mav.addObject("menuCategories", masterDataService.getAllMenuCategories());
return showPage(mav, HOME_PAGE_URL);
}
}
I kind of feel that this call is redundant because
1) all the controllers are making this call to build the menu
2) difficult to maintain because change in business logic will require changes in multiple controllers
What is the best practice in this type of scenario? How can I avoid this call getting make from multiple controllers? Is there a way to make this call at a central location so that this piece of code does not cut across all the controllers?
Avoid duplicating code across controller classes - If all controller classes extend BasePageController:
public abstract class BasePageController {
...
#ModelAttribute
public void initMenuCategories(Model model) {
model.addAttribute("menuCategories", masterDataService.getAllMenuCategories());
}
...
}
Reduce database calls per page - Consider caching the menu categories if they don't change too often:
#Service
public class MasterDataService {
...
#Cacheable(key = "menu", value = "menu")
public List<MenuCategory> getAllMenuCategories() { ... }
...
}
You will have to add a cache named menu to your service layer configuration for this to work. See the documentation on Caching with Spring for details.
I was trying to redirect to a dynamic page from Interceptors and Handler Mapping program. I have already defined a controller which handles and redirects (/hello.htm) through model (I have only this controller in my program). Until this point it is working fine. Apart from this, I registered a handler which will redirect to a page once it satisfies some condition.
public class WorkingHoursInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("In Working Hours Interceptor-pre");
Calendar c=Calendar.getInstance();
if(c.get(Calendar.HOUR_OF_DAY)<10||c.get(Calendar.HOUR_OF_DAY)>20){
response.sendRedirect("/WEB-INF/jsp/failure.jsp");
return false;
}
return true;
..............
..............
}
But once it comes to response.sendRedirect, it is showing resource not found even though the mentioned page is present. I tried to redirect to "WEB-INF/jsp/hello.jsp" as well but keeps showing the same error. If the condition in the interceptor is not satisfied, the program works fine.
Below is shown the only controller present in the program.
#Controller
public class MyController {
#RequestMapping("/hello.htm")
public ModelAndView sayGreeting(){
String msg="Hi, Welcome to Spring MVC 3.2";
return new ModelAndView("WEB-INF/jsp/hello.jsp","message",msg);
}
}
(The controller for handling hello.html works fine if I change the interceptor condition)
Instead of redirecting, if I just print a message in the console, the program works fine. But once it comes to redirect it shows the error. Do I need to specify a separate controller to handle this request? Will this redirection request go to the dispatcher-servlet?
You need to add redirect: prefix in the view name, the code for redirect will look like:
#RequestMapping(value = "/redirect", method = RequestMethod.GET)
public String redirect() {
return "redirect:finalPage";
}
OR
#RequestMapping(value = "/redirect", method = RequestMethod.GET)
public ModelAndView redirect() {
return new ModelAndView("redirect:finalPage");
}
You may get a detail description from here:
enter link description here
I am learning the Spring MVC 3 now.
I meet some problems when set the URLs in my page, since the URL in the page are relatived to the current page, so I want to set the base URL in each page.
When I use Structs 2, I create a BaseAction like this:
public class BaseAction{
public BaseAction(){
string baseURL=getServletContext.getHost()+"...."+.....;
}
public getBaseURL(){xxxxx}
}
Then in the page:
<base href='<s:prototype value="baseURL"/>' />
Now, in Spring MVC 3, since the a new instance of related controller is not created for each request, so I do not know how to make it?
Finally, I think I can use the interceptor, so I create a interceptor:
public class BaseURLInterceptor extends HandlerInterceptorAdapter{
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
return true;
}
//after the handler is executed
public void postHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView)
throws Exception {
modelAndView.getModel().setObject("baseURL",requset.getHost()+"......");
}
}
In the page:
I can use :
<base href="${baseURL}" />
It works, however when a view is redirected, it will add this value to the URL.
For example:
#Controller
#RequsetMapping("/user")
public class UserController{
#RequsetMapping("edit/{userid}")
public String edit(#PathVariable String uid)
//edit logic
return "redirect:list"
}
#RequsetMapping("list")
public String list(){
//get users list
return "user_list"
}
}
When I make the submit button in the user edit page, I will redirect to :
http://localhost:8080/app/user/list?baseURL=http://localhost:8080
Where the parameter in the url ?baseURL=http://localhost:8080 is not what I want.
How to fix it?
Or what is the better way to solve the URL path using Spring MVC 3?
May I do not correct understand the problem, but if you want to have the base url in the jsp for some links than your approach is the wrong one.
normaly you would do something like this:
test
And for redirects in the controller you can specify how the url has to be interpreted if you use RedirectView instead of returning the String "redirect:xxx": the redirect view constructor RedirectView(String url, boolean contextRelative) can be used to specify context relative to the current ServletContext or relative to the web server.
If I understand the problem correctly this is what you're looking for <base href="<c:url value="${webappRoot}" />