In Spring MVC, the following is a pattern of a simplified request handling mechanism:
DispatcherServlet
receives a request and confers the request with handler mappings to find out which controller can handle the request, and then passes the request to that controller.DispatcherServlet
for user display or response. Instead of sending the information (model) directly to the user, the controller returns a view name that can render the model.DispatcherServlet
then resolves the physical view from the view name and passes the model object to the view. This way, DispatcherServlet
is decoupled from the view implementation.The following sequence diagram represents the flow and interaction of Spring MVC components:
We will build a Spring web application and unit test the code using JUnit. The following are the steps to be performed:
SpringMvcTest
.web.xml
and enter the following lines:<display-name>SpringMVCTest</display-name> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/dispatcher-servlet.xml </param-value> </context-param> </web-app>
The dispatcher is named DispatcherServlet
, and it maps all requests. Note the contextConfigLocation
parameter. This indicates that the Spring beans are defined in /WEB-INF/dispatcher-servlet.xml
.
dispatcher-servlet.xml
in WEB-INF
and add the following lines:<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
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">
<context:component-scan base-package="com.packt" />
<bean class= "org.springframework.web.servlet.view.
InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
This XML defines a Spring view resolver. Any view will be found under the /WEB-INF/pages
location with the .jsp
suffix, and all beans are configured under the com.packt
package with Spring annotations.
LoginInfo
in the com.packt.model
package. This class represents the login information. Add two private String
fields, userId
and password
, and generate the getters and setters.login.jsp
under /WEB-INF/view
, and add the following lines to create a form using the Spring tag library. Modify the form and add normal HTML input for username and password:<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%> <sf:form method="POST" modelAttribute="loginInfo" action="/onLogin"> </sf:form>
com.packt.controller.LoginController
to handle login requests. Add the following lines:@Controller @Scope("session") public class LoginController implements Serializable { @RequestMapping({ "/", "/login" }) public String onStartUp(ModelMap model) { model.addAttribute("loginInfo", new LoginInfo()); return "login"; } }
The @Controller
annotation indicates that the class is a Spring MVC controller class. In smapl-servlet.xml
, we defined <context:component-scan base-package="com.packt" />
, so Spring will scan this @Controller
annotation and create a bean. The @RequestMapping
annotation maps any request with the default path /SpringMvcTest/
or /SpringMvcTest/login
to the onStartUp
method. This method returns a logical view name login
. The view resolver defined in the XML file will map the login request to the physical view login.jsp
page under /WEB-INF/pages
.
Login
class to handle the login and submit requests, as follows:@RequestMapping({ "/onLogin" }) public String onLogin(@ModelAttribute("loginInfo") LoginInfo loginInfo, ModelMap model) { if(!"junit".equals(loginInfo.getUserId())) { model.addAttribute("error", "invalid login name"); return "login"; } if(!"password".equals(loginInfo.getPassword())) { model.addAttribute("error", "invalid password"); return "login"; } model.addAttribute("name", "junit reader!"); return "greetings"; }
The onLogin
method is mapped with /onLogin
. The @ModelAttribute("loginInfo")
method is the model submitted from the login.jsp
form. This method checks whether the username is junit
and password is password
. If the user ID or password does not match, then an error message is shown on the login page, otherwise, the greetings
view is opened.
login.jsp
file to submit the form to /SpringMvcTest/onLogin
and the modelattribute
name to loginInfo
, as follows:<sf:form method="POST" modelAttribute="loginInfo" action="/SpringMvcTest/onLogin">
Also, add the <h1>${error}</h1>
JSTL expression to display the error message.
greetings.jsp
and add the following lines:<h1>Hello :${name}</h1>
http://localhost:8080/SpringMvcTest/
; this will open the login page. On the login page, do not enter any value and just click on Submit. It will show the invalid login name error message. Now, enter junit
in the User Id field and password
in the Password field and hit Enter. The application will greet you with the message shown in the following screenshot:We can unit test the controller
class. The following are the steps:
LoginControllerTest.java
class in com.packt.controller
.public class LoginControllerTest { LoginController controller = new LoginController(); @Test public void when_no_name_entered_shows_error_message(){ ModelMap model = new ModelMap(); String viewName = controller.onLogin(new LoginInfo(), model); assertEquals("login", viewName); assertEquals("invalid login name", model.get("error")); } }
@Test public void when_invalid_password_entered_shows_error_message() { ModelMap model = new ModelMap(); LoginInfo loginInfo = new LoginInfo(); loginInfo.setUserId("junit"); String viewName =controller.onLogin(loginInfo, model); assertEquals("login", viewName); assertEquals("invalid password", model.get("error")); }
happyPath
test, as follows:@Test public void happyPath(){ loginInfo.setUserId("junit"); loginInfo.setPassword("password"); String viewName =controller.onLogin(loginInfo, model); assertEquals("greetings", viewName); }
This is just an example of Spring MVC, so we checked the username and password with the hardcoded constants. In the real world, a service looks up the database for the user and returns an error message; the service can be autowired to the controller. This way, we can unit test the controller and the service layer.