Saturday, February 11, 2012

Spring BareMvc - a bare expanation

Spring 3.0 MVC tutorial - a detailed breakdown - part 1.
I'm having a bit of a problem getting off the blocks with Spring. I've decided to get a simple Spring MVC web app running based on this tutorial:

http://blog.springsource.org/2011/01/04/green-beans-getting-started-with-spring-mvc/

And I got *one* working. But, when I add another, I get a not found and the server shows a mapping error.

So, I'm just going to review the tutorial to see if I can glean any hints from that:

Spring MVC, a part of the core Spring Framework, is a mature and capable action-response style web framework, with a wide range of capabilities and options aimed at handling a variety of UI-focused and non-UI-focused web tier use cases.



All this can potentially be overwhelming to the Spring MVC neophyte. I think it's useful for this audience to show just how little work there is to get a bare Spring MVC application up and running (i.e. consider my example something akin to the world's simplest Spring MVC application), and that's what I'll spend the rest of this article demonstrating.


Off to a good start.


I'm assuming you are familiar with Java, Spring (basic dependency injection concepts), and the basic Servlet programming model, but do not know Spring MVC. After reading this blog entry, readers may continue learning about Spring MVC by looking at Keith Donald's Spring MVC 3 Showcase, or the variety of other online and print resources available that cover Spring and Spring MVC.


I might check out the MVC showcase later on.

A note on dependencies and build systems: this article does not assume that you are using a particular build system, e.g. Maven, Gradle or Ant. A fairly minimal sample Maven POM file is includes as an example at the end of the article.


I badly want to use maven. I can use that on my resume as well.

Spring MVC includes most of the same basic concepts as other so-called web MVC frameworks.

Incoming requests enter the framework via a Front Controller. I

n the case of Spring MVC, this is an actual Java Servlet called DispatcherServlet.


Think of DispatcherServlet as the gatekeeper.

It doesn't perform any real web or business logic, but rather delegates to POJOs called Controllers where the real work is done (either in whole or via the back-end).

When the work has been done, it's the responsibility of Views to produce the output in the proper format (whether that's a JSP page, Velocity template, or JSON response).

Strategies are used to decide which Controller (and which method(s) inside that Controller) handles the request, and which View renders the response.

The Spring container is used to wire together all these pieces. It all looks something like this:






Bootstrapping the DispatcherServlet and Spring Container
As mentioned, all incoming requests flow through a DispatcherServlet.

Like any other Servlet in a Java EE application, we tell the Java EE container to load this Servlet at web app startup time via an in the web app's WEB-INF/web.xml.

The DispatcherServlet is also responsible for loading a Spring ApplicationContext that is used to perform wiring and dependency injection of managed component.

On this basis, we specify some init parameters to the Servlet which configure the Application Context.

Let's look at the config in web.xml:
WEB-INF/web.xml




xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">



appServlet
org.springframework.web.servlet.DispatcherServlet

contextConfigLocation
/WEB-INF/spring/appServlet/servlet-context.xml

1



appServlet
/




Ok, there were a couple of important points made. The dispatcher servlet is loaded when the web-app is loaded. This may imply that each app has it's own dispatcher servlet.

This would make sense. each app should have its own front controller. When the request comes into the server, via port 8080, there should be something that lets whatever processes that request know which directory it needs to go to - like the get command parameter. Once it get's there, the DispatcherServlet takes over.


A number of things are being done here:
We register the DispatcherServlet as as a Servlet called appServlet

We map this Servlet to handle incoming requests (relative to the app path) starting with "/"

// the relative to the app path is key. This must be the directory.
// It's actually saying "start looking at the part after the "/"
// It would be good to see an example.


We use the ContextConfigLocation init parameter to customize the location for the base configuration XML file for the Spring Application Context that is loaded by the DispatcherServlet, instead of relying on the default location of -context.xml).



So, this is just telling the server where the spring application context configuration xml file is.


Wait, What if Somebody Doesn't Want to Configure Spring via XML?
The default type of Application Context loaded by the DispatcheServlet expects to load at least on XML file with Spring bean definitions. As you'll see, we'll also enable Spring to load Java-based config, alongside the XML.
Everybody will have their own (sometimes very strong) opinion in this area, but while I generally prefer-Java based configuration, I do believe that smaller amounts of XML config for certain areas can sometimes still make more sense, for one of a number of reasons (e.g. ability to change config without recompilation, conciseness of XML namespaces, toolability, etc.). On this basis, this app will use the hybrid approach, supporting both Java and XML.
Rest assured that if you prefer a pure-Java approach, with no Spring XML at all, it's pretty trivial to achieve, by setting one init param in web.xml to override the default Application Context type and use a variant called AnnotationConfigWebApplicationContext instead.


All this is saying is you can use java-based configurations (how? through annotations?) instead of an xml based one by specifying a param in the web.xml saying the context type is annotation. Then you don't have to specify the path of the spring configuration file.


Here's the controller:

package xyz.sample.baremvc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;


/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {

@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "WEB-INF/views/home.jsp";
}
}



So, this is telling the controller what the view to return is. That's abundantly clear.


Let's walk through the key aspects of this class:
The class has been annotated with the @Controller annotation, indicating that this is a Spring MVC Controller capable of handling web requests. Because @Controller is a specialization of Spring's @Component Stereotype annotation, the class will automatically be detected by the Spring container as part of the container's component scanning process, creating a bean definition and allowing instances to be dependency injected like any other Spring-managed component.

// So, the @Controller annotation means it's going to be a spring-managed bean.

The home method has been annotated with a @RequestMapping annotation, specifying that this method should handle web requests to the path "/", i.e. the home path for the application.

// I guess this means anything with "/" or after. In this case I think maybe
// something like "/baremvc

The home method simply logs a message to system out, and then returns WEB-INF/views/home.jsp, indicating the view which should handle the response, in this case a JSP page. (If hardcoding the entire view path including WEB-INF prefix, and the fact that it's a JSP, seems wrong to you, you are right. We'll deal with this later)

// Ok, that's maybe what I didn't change on the first try




Now, we need to create the view. This JSP page will simply print a greeting.
WEB-INF/views/home.jsp
view sourceprint?
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>


Home


Hello world!





Ok. Next up:


Finally, as previously mentioned, we need to create a minimal Spring Application Context definition file.
WEB-INF/spring/appServlet/servlet-context.xml


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">














Let's examine the contents of this file:
You'll note that a few different Spring XML namespaces are being used: context, mvc, and the default beans
The declaration ensures the Spring container does component scanning, so that any code annotated with @Component subtypes such as @Controller is automatically discovered. You'll note that for efficiency, we limit (to xyz.sample.baremvc in this case) what part of the package space Spring should scan in the classpath


The declaration sets up Spring MVC's support for routing requests to @Controllers, as well as how some things like conversion, formatting and validation are handled (with some sensible defaults based on what (libraries) is present in your classpath, and the ability to override if needed)



Ok, apparently annotation-driven means you can use controllers. Fair enough.

The web app is now ready to run. Assuming the Servlet container (tc Server in my case) is set to listen on localhost:8080, starting the application and then hitting the URL http://localhost:8080/baremvc via our browser results in a display of the expected greeting:




As trivial as it is, running this application involves all the major pieces of a working Spring MVC application. Let's walk through the major sequences and component interactions:

When the web app starts up, the DispatcherServlet is loaded and initialized because of the entry in web.xml.

// right.

The DispatcherServlet loads an annotation-based Application Context, which has been configured to scan for annotated components via a regular expression specifying the base package(s).

// That would've been the entry in the spring context file:



Annotated components such as the HomeController are detected by the container.

// because of the component-scan command.

The HTTP request to http://localhost:8080/baremvc hits the servlet engine and is routed to our (baremvc) webapp.

The servlet engine of the container. The servlet engine sees the "baremvc" and knows to route it to our web app. Question - which part of the web deployment identifies it as baremvc?


The implicit "/" path at the end of the URL matches the regex that has been registered for the DispatcherServlet, and the request is routed to it

// That's what we saw in web.xml:


appServlet
/



The DispatcherServlet needs to decide what to do with the request. It uses a strategy called a HandlerAdapter to decide where to route the request. The specific HandlerAdapter type (or types, since they can be chained) to be used can be customized, but by default, an annotation-based strategy is used, which routes requests appropriately to specific methods in classes annotated as @Controller, based on matching criteria in @RequestMapping annotations found in those classes. In this case, the regex on the home method is matched, and it's called to handle the request.

// So, the implicit "/" at the end of the url is mapped by:

@Controller
public class HomeController {

@RequestMapping(value = "/")


The home method does its work, in this case just printing something to system out. It then returns a string that's a hint (in this case, a very explicit one, WEB-INF/views/home.jsp) to help chose the View to render the response.
The DispatcherServlet again relies on a strategy, called a ViewResolver to decide which View is responsible for rendering the response. This can be configured as needed for the application (in a simple or chained fashion), but by default, an InternalResourceViewResolver is used. This is a very simple view resolver that produces a JstlView which simply delegates to the Servlet engine's internal RequestDispatcher to render, and is thus suitable for use with JSP pages or HTML pages.
The Servlet engine renders the response via the specified JSP



Taking It to the Next Level
At this point, we've got an app which certainly qualifies as the world's simplest Spring MVC application, but frankly, doesn't really meet the spirit of that description. Let's evolve things to another level.
As previously mentioned, it's not appropriate to hard-code a path to a view template into a controller, as our controller curretly does. A looser, more logical coupling between controllers and views, with controllers focused on executing some web or business logic, and generally agnostic to specific details like view paths or JSP vs. some other templating technology, is an example of separation of concerns. This allows much greater reuse of both controllers and views, and easier evolution of each in isolation from the other, with possibly different people working on each type of code.
Essentially, the controller code ideally needs to be something like this variant, where a purely logical view name (whether simple or composite) is returned:

//...
@Controller
public class HomeController {

@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "home";
}
}



This is actually the default of the wizard that using the spring version I downloaded.


Spring MVC's ViewResolver Strategy is actually the mechanism meant to be used to achieve this looser coupling between the controller and the view. As already mentioned, in the absence of the application configuring a specific ViewResolver, Spring MVC sets up a default minimally configured InternalResourceViewResolver, a very simple view resolver that produces a JstlView.

There are potentially other view resolvers we could use, but to get a better level of decoupling, all we actually need to do is set up our own instance of InternalResourceViewResolver with slightly tweaked configuration. InternalResourceViewResolver employs a very simple strategy; it simply takes the view name returned by the controller, and prepends it with an optional prefix (empty by default), and appends it with an optional suffix (empty by default), then feeds that resultant path to a JstlView it creates. The JstlView then delegates to the Servlet engine's RequestDispatcher to do the real work, i.e. rendering the template. Therefore, to allow the controller to return logical view names like home instead of specific view template paths like WEB-INF/views/home.jsp, we simply need to configure this view resolver with the prefix WEB-INF/views and the suffix .jsp, so that it prepends and appends these, respectively, to the logical name returned by the controller.


One easy way to configure the view resolver instance is to introduce the use of Spring's Java-based container configuration, with the resolver as a bean definition:
package xyz.sample.baremvc;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
public class AppConfig {

// Resolve logical view names to .jsp resources in the /WEB-INF/views directory
@Bean
ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}


I get it. He specified a scan in the spring context xml file for the package, the spring will find it and apply it to its configuration. The @Bean tells it to use this method to create a bean.







It's hard to make a case for this object that one particular approach is much better than the other, so it's really a matter of personal preference in this case (and we can actually see one of the strengths of Spring, its flexible nature).


Ok, this is a good cutoff point before getting into the next part. Oh, yeah - you can get rid of the error by running a "clean" on the project.

No comments:

Post a Comment