Standard Java EE forward to internal resources go something like this:
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
req.getRequestDispatcher("/WEB-INF/page/my.jsp").forward(req, resp);
}
}
Admittedly, there’s no decoupling between the servlet code and the view technology, even without the JSP location.
Spring MVC introduces the notion of ViewResolver
.
The controller just handles logical names, mapping between the logical name and the actual resource is handled by the ViewResolver
.
Even better, controllers are completely independent from resolvers: just registering the latter in the Spring context is enough.
Here’s a very basic controller, notice there’s no hint as to the final resource location.
@Controller
public class MyController {
@RequestMapping("/logical")
public String displayLogicalResource() {
return "my";
}
}
Even better, there’s nothing here as to the resource nature; it could be a JSP, an HTML, a Tiles, an Excel sheet, whatever.
Each has a location strategy based on a dedicated ViewResolver
.
The most used resolver is the InternalResourceViewResolver
;
it meant to forward to internal resources, most of the time, JSPs.
It is initialized like this:
@Bean
public ViewResolver pageViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/page/");
resolver.setSuffix(".jsp");
return resolver;
}
Given this view resolver available in the Spring context, the logical name "my"
will tried to be resolved with the "/WEB-INF/page/my.jsp"
path.
If the resource exists, fine, otherwise, Spring MVC will return a 404.
Now, what if I’ve different folders with JSP? I expect to be able to configure two different view resolvers, one with a certain prefix, the other with a different one.
I also expect them to be checked in a determined order, and to fallback from the first to the last.
Spring MVC offers multiple resolvers with deterministic order, with a big caveat:
it does not apply to InternalResourceViewResolver
!
Quoting Spring MVC Javadoc:
When chaining ViewResolvers, an InternalResourceViewResolver always needs to be last, as it will attempt to resolve any view name, no matter whether the underlying resource actually exists.
This means I cannot configure two InternalResourceViewResolver
in my context, or more precisely I can but the first will terminate the lookup process.
The reasoning behind (as well as the actual code), is that the resolver gets an handle on the RequestDispatcher configured with the resource path.
Only much later is the dispatcher forwarded to, only to find that it does not exist.
To me, this is not acceptable as my use-case is commonplace.
Furthermore, configuring only "/WEB-INF"
for prefix and returning the rest of the path ("/page/my"
) is out of the question as it ultimately defeats the purpose of decoupling the logical name from the resource location.
Worst of all, I’ve seen controller code such as the following to cope with this limitation:
return getViews().get("my"); // The controller has a Map view property with "my" as key and the complete path as the "value"
I think there must be some more Spring-ish way to achieve that and I’ve come to what I think is an elegant solution in the form of a ViewResolver that checks if the resource exists.
public class ChainableUrlBasedViewResolver extends UrlBasedViewResolver {
public ChainableUrlBasedViewResolver() {
setViewClass(InternalResourceView.class);
}
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
String url = getPrefix() + viewName + getSuffix();
InputStream stream = getServletContext().getResourceAsStream(url);
if (stream == null) {
return new NonExistentView();
}
return super.buildView(viewName);
}
private static class NonExistentView extends AbstractUrlBasedView {
@Override
protected boolean isUrlRequired() {
return false;
}
@Override
public boolean checkResource(Locale locale) throws Exception {
return false;
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
// Purposely empty, it should never get called
}
}
}
My first attempt was trying to return null
within the buildView()
method.
Unfortunately, there was some NPE being thrown later in the code.
Therefore, the method returns a view that a.
tells caller that the underlying resource does not exist b.
does not allow for its URL to be checked (it also fails at some point if this is not set).
I’m pretty happy with this solution, as it enables me to configure my context like that:
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "ch.frankel.blog.spring.viewresolver.controller")
public class WebConfig {
@Bean
public ViewResolver pageViewResolver() {
UrlBasedViewResolver resolver = new ChainableUrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/page/");
resolver.setSuffix(".jsp");
resolver.setOrder(0);
return resolver;
}
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
resolver.setOrder(1);
return resolver;
}
}
Now, I’m pretty well inside Spring philosophy: I’m completely decoupled, and I’m using Spring nominal resolver ordering. The only con is that one resource can shadow another another by having the same logical name pointing to different resources given different view resolvers. As it is already the case with multiple view resolvers, I’m ready to accept the risk.
A showcase project can be found here in IntelliJ IDEA/Maven format.