|
|
SPRING MVC introduction | spring mvc webapp basics | ajax and jquery | spring mvc webapp features | practicalities
Last updated: Sept 2009
Spring can be used in combination with many web frameworks to help wire things together. However, Spring
also has its own web framework, called Spring MVC, which takes full advantage of
Spring's IoC, AOP, and testability. This version of WebOfContacts also demonstrates how a Web
application can be made to feel a bit more desktop-like with the use of JavaScript/jQuery/Ajax.
|
|
|
introduction For the Spring MVC/Ajax version of WebOfContacts these technologies were used:
contactsdb. Then
download the WAR
(make sure to retain the .war extension)
from this site
into your Tomcat's webapps directory, and view the application in your browser at
http://localhost:8080/WebOfContactsSpringMVCAjax/.
The full source is available for download as an
Eclipse project.
|
|
|
spring mvc webapp basics The first thing to do to get a Spring MVC web application working (after copying the Spring jars into your webapp) is set up Spring's DispatcherServlet in your web.xml and define a servlet mapping
so that it handles all requests ending in .html. The DispatcherServlet,
when it receives a request, consults its handler mappings to decide what
Controller will deal with it. It used to be customary to define these mappings
in your application context (a bit like the way Struts has its mapping configuration
in an XML file); however, the 2.5 release of Spring MVC introduced a radical change of
syntax. Using annotations each mapping can be defined in the Controller Java files.
Here is an example to show the home page:
@Controller
public class HomePageController
{
@RequestMapping("/home.html")
public ModelAndView showHome() {
// Set up the model with a view name that leads to home.jsp
ModelAndView mv = new ModelAndView("home");
return mv;
}
}
The first annotation, @Controller simply tells the Spring infrastructure to
recognize (scan) this Java class as a Controller. The @RequestMapping annotation
declares what URL requests this Controller handles. The ModelAndView object
takes a view name as a parameter: this effectively means that the home.jsp file will be
used for the view. Inside ModelAndView there is also a map of name-value pairs
which you could write to in the Java above (if this were a non-trivial example), and then read
from in the JSP.
Spring MVC has a whole hierarchy of Controller types. However, you will notice the above
class does not extend anything. In earlier versions of Spring MVC it would have extended
MultiActionController because it is a Controller that allows each method to
be an action (like DispatchAction in Struts). However, here it is not
constrained by an implementation hierarchy at all. You can name the methods whatever you
like; you can even add parameters at will! For example, if you need to access the request
object inside that method, you would simply add HttpServletRequest request as
a parameter to showHome() and it would be injected and ready for use.
Not surprisingly the magic of annotations will not work unless you do a bit of general
configuration work. This is Spring so you do it in your application context. Notice in
the first line, you say what package in your application needs to be scanned for annotated
controllers:
<context:component-scan base-package="com.oranda.webofcontacts.springmvc.controller" />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
Inside the JSPs, JSTL tags are used for logic. For forms Spring MVC provides its own tag library
form. A small form looks like the following. It is based on a domain object
User with attributes username and password.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <form:form commandName="user"> <tr><td>Username</td><td><form:input path="username"/></td></tr> <tr><td>Password</td><td><form:input path="password"/></td></tr> <tr><td colspan="2"><input type="submit" value="Create User"/></td></tr> </form:form>This code is inside userForm.jsp. By default it will submit to userForm.html as a POST. Inside UserFormController the following method handles the submission
of this form:
@RequestMapping(value = "/userForm.html", method = RequestMethod.POST)
public ModelAndView submitForm(@ModelAttribute("user") User user,
BindingResult result) {
ModelAndView mv = new ModelAndView();
if (result.hasErrors()) {
// Redisplay the form
mv.setViewName("userForm");
} else {
this.userService.createUser(user);
mv.setViewName("redirect:listUsers.html");
}
return mv;
}
Again the method name submitForm is entirely arbitrary: the thing that
matters is the annotation line above it which says that this method will handle any
POST to userForm.html (a GET is handled by a different method which just displays the
form). Also we have added parameters to the method simply because we need them.
Notice that the ModelAttribute has the same name ("user") as the
commandName in the form. The framework will bind the form elements to
the User attributes. If there was a problem we will see it in the other
argument, the BindingResult and redisplay the form. If there is no
problem, a call to the service layer to actually create the user is made, and there
is a redirect to the list of users.
A question: where does the userService come from? The answer: it is
"autowired". In the UserFormController it is declared as an instance variable like
this:
@Autowired private UserService userService;The presence of the @Autowired tag means you don't even have to declare
in XML that the UserFormController bean uses the UserService;
it is picked up by the scanner.
|
|
|
ajax and jquery
In a traditional web application, each action requires a new web page to be loaded,
even if the parts (header, footer, navigation, etc) of the new page are the same as
on the old page. Ajax (Asynchronous JavaScript with XML) allows calls to the
server to made using JavaScript without loading or reloading a web page. This allows
the webapp more like a desktop application, and the user perceives an increase in
responsiveness.
In an Ajax call, data is passed back to the page in an |
|
|
spring mvc webapp features
Form validation in Spring MVC is done by implementing the
Any logon attempt should submit to a special URL
|
|
|
practicalities In terms of usability Spring MVC is one of the best Java frameworks in my opinion. The annotation style can be confusing at first, but once you get used to it, its flexibility and power become apparent. Along with Spring come many utilities and "Template" classes which enable you to do just about anything you want to. You also have the option to use the Spring Web Flow add-on if your presentation logic gets complicated. Because Spring MVC has undergone a major revision with 2.5, a lot of the documentation on the Web and in books is outdated and this can be confusing. Guides to the new version are still a bit thin on the ground but the basics are solidly covered at the Spring Framework main site, and Juergen Hoeller's blog. With this framework I am doing unit testing with JUnit and EasyMock. EasyMock is not specific to Spring MVC -- it can be used with any framework -- but here is as good a place as any to demonstrate its use. EasyMock lets you test components in isolation by mocking out the other components they interact with. In TestContactFormController the setup method has this
code:
contactFormController = new ContactFormController();
mockContactService = EasyMock.createMock(ContactService.class);
contactFormController.setContactService(mockContactService);
This creates a real ContactFormController but makes it
use a mock ContactService. Now to test showing the form
we have this code:
public void testShowFormWithContact() throws Exception {
Contact testContact = new Contact();
testContact.setId(1);
testContact.setFirstName("testContactFirstName");
EasyMock.expect(this.mockContactService.getContact(1L)).andReturn(testContact);
EasyMock.replay(this.mockContactService);
ModelAndView mv = this.contactFormController.showForm(1L);
EasyMock.verify(this.mockContactService);
assertEquals("contactForm", mv.getViewName());
Contact returnedContact = (Contact) mv.getModel().get("contact");
assertEquals(1, returnedContact.getId());
assertEquals("testContactFirstName", returnedContact.getFirstName());
}
The main line in this code, the line that actually runs the test is
ModelAndView mv = this.contactFormController.showForm(1L);.
Now, when this line is run, it will try to show the Contact with the ID 1, but
it won't hit the database: it will call the mock contact service. We need
to specify what this pretend service should return, and this is exactly what
is done in the preceding lines: specifically with EasyMock.expect.
The EasyMock.replay line just switches the state, declaring, "We
have finishing stating our expectations. Now we are ready to run the code
we are testing." Then afterwards, EasyMock.verify is called to
check that getContact was actually called on the mock object. Then
we do the normal JUnit tests to see that the returned ModelAndView
contains what it should.
Notice that the mock object approach is white box unit testing. It doesn't
just test preconditions and postconditions; it also checks to see that the
interactions between the object under test and other parts of the system are
as expected.
|