Agenda
1. Introduction to Spring MVC Architecture
2. Annotation based controller classes using @Controller and @RequestMapping
3. Creating multiple actions using @RequestMapping
4. Accessing data through @PathVariable
5. Using @ModelAttribute at method level
6. Binding with different data types
7. Binding with user Defined types
8. @InitBinder annotation, WebDataBinder, CustomDateEditor
9. Custom property Editor
10. Using JSR annotation for Binding validation
11. Custom error messages
12. Form Validation using @Pattern, @Past, @Max etc
14. Writing a custom form validation annotation
15. Understanding Interceptors
build.gradle
apply plugin: 'war'
apply plugin: 'com.bmuschko.tomcat'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.bmuschko:gradle-tomcat-plugin:2.2.2'
}
}
repositories {
mavenCentral()
jcenter()
}
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5', 'javax.servlet:jsp-api:2.0'
compile 'org.springframework:spring-webmvc:4.2.5.RELEASE'
def tomcatVersion = '7.0.57'
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
}
tomcatRun {
contextPath = ""
httpPort = 8080
File file = new File("src/main/webapp/WEB-INF/web.xml");
configFile = file
}
Dispatcher Servlet Configuration
Under the src/main/webapp/WEB-INF create an XML file web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-dispatcher-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Create spring-dispatcher-servlet.xml in WEB-INF folder
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<mvc:annotation-driven/>
</beans>
Using Abstract Controller
Create a class HelloController which extends AbstractController
package com.spring.demo.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView("hello");
return modelAndView;
}
}
xml configrations for Hello Controller
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<bean name="/hello.html" class="com.spring.demo.controller.Hello"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
MultiActionController implementation
package com.spring.demo.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MultipleController extends MultiActionController {
public ModelAndView dummy(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
ModelAndView modelAndView = new ModelAndView("dummy");
return modelAndView;
}
public void dummy2(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
httpServletResponse.setContentType("text/html");
httpServletResponse.getWriter().println("<b>HELLO World</b>");
}
}
XML configuration for MultipleControllerAction
<bean
class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
<bean class="com.spring.demo.controller.MultipleController"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
Excercise 1
Annotation based Mapping
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class DummyController {
@RequestMapping("/")
@ResponseBody
String index() {
return "index string";
}
}
<context:component-scan base-package="com.spring.demo.controller"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
XML configuration for annotation based mapping
Rendering a view from annotation based configuration
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class DummyController {
@RequestMapping("/")
ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("index");
return modelAndView;
}
}
Multiple Action controller throught annotation
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@ResponseBody
@RequestMapping("/index")
String index() {
return "index";
}
@ResponseBody
@RequestMapping("/demoAction")
String demoAction() {
return "demoAction";
}
}
Using Model
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@ResponseBody
@RequestMapping("/index")
ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("index");
modelAndView.addObject("msg", "Hello World");
return modelAndView;
}
}
<html>
<head>
<title></title>
</head>
<body>
${msg}
</body>
</html>
index.jsp
Exercise 2
@PathVariable
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@RequestMapping("/country/{countryName}")
@ResponseBody
String returnCountryName(@PathVariable("countryName") String countryName) {
return countryName;
}
@RequestMapping("/countryAndState/{countryName}/{stateName}")
@ResponseBody
String returnCountryAndState(@PathVariable Map<String, String> requestMap) {
return requestMap.get("countryName") + " " + requestMap.get("stateName");
}
}
Binding data of form
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<form method="post" action="/dummy/submitForm.html">
<label>Username</label>
<input name="username" type="text">
<label>Password</label>
<input name="password" type="text">
<input type="submit">
</form>
</body>
</html>
index.html
package com.spring.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@RequestMapping(value = "/index.html", method = RequestMethod.GET)
ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("index");
return modelAndView;
}
@RequestMapping(value = "/submitForm.html", method = RequestMethod.POST)
@ResponseBody
String submitForm(String username, String password) {
return "Username " + username + " Password " + password;
}
}
Binding data of form (Cont.)
package com.spring.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@RequestMapping(value = "/index.html", method = RequestMethod.GET)
ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("index");
return modelAndView;
}
@RequestMapping(value = "/submitForm.html", method = RequestMethod.POST)
@ResponseBody
String submitForm(@RequestParam("username") String username,
@RequestParam("password") String password) {
return "Username " + username + " Password " + password;
}
}
@RequestParam
Exercise 3
Binding with Objects
package com.spring.demo.controller;
import com.spring.demo.component.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@RequestMapping(value = "/index.html", method = RequestMethod.GET)
ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("index");
return modelAndView;
}
@RequestMapping(value = "/submitForm.html", method = RequestMethod.POST)
@ResponseBody
String submitForm(User user) {
return "Username " + user.getUsername() + " Password " + user.getPassword();
}
}
Using @ModelAttribute
package com.spring.demo.controller;
import com.spring.demo.component.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@RequestMapping(value = "/index.html", method = RequestMethod.GET)
ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("index");
return modelAndView;
}
@RequestMapping(value = "/submitForm.html", method = RequestMethod.POST)
@ResponseBody
ModelAndView submitForm(@ModelAttribute("user") User user) {
ModelAndView modelAndView = new ModelAndView("submittedData");
return modelAndView;
}
}
submittedData.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
${user.username} ${user.password}
</body>
</html>
Using @ModelAttribute with method
package com.spring.demo.controller;
import com.spring.demo.component.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/dummy")
public class DummyController {
@ModelAttribute
void addingObject(Model model){
model.addAttribute("heading","This is spring mvc");
}
@RequestMapping(value = "/index.html", method = RequestMethod.GET)
ModelAndView index() {
ModelAndView modelAndView = new ModelAndView("index");
return modelAndView;
}
@RequestMapping(value = "/submitForm.html", method = RequestMethod.POST)
@ResponseBody
ModelAndView submitForm(@ModelAttribute("user") User user) {
ModelAndView modelAndView = new ModelAndView("submittedData");
return modelAndView;
}
}
Exercise 4
Binding with different DataTypes
<form method="get" action="submittedData">
<p>
<label>Firstname</label>
<input type="text" name="firstname">
</p>
<p>
<label>Lastname</label>
<input type="text" name="lastname">
</p>
<p>
<label>Number</label>
<input type="text" name="phoneNumber">
</p>
<p>
<label>Date</label>
<input type="text" name="date">
</p>
<p>
<label>Skill Set</label>
<select name="skills" multiple>
<option value="Java core">Java Core</option>
<option value="Spring">Spring</option>
</select>
</p>
<input type="submit">
</form>
</html>
Binding with different DataTypes (Cont.)
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.util.List;
public class UserCO {
String firstname;
String lastname;
Long phoneNumber;
@DateTimeFormat(pattern = "dd/MM/yyyy")
Date date;
List<String> skills;
//Getters and Setters
}
@RequestMapping("/submittedData")
@ResponseBody
String submittedData(UserCO userCO) {
}
Binding User Defined Objects
public class UserCO {
// Other properies
Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
<form method="get" action="submittedData">
<!--Other elements of html form-->
<p>
<label>Address</label>
<input type="text" name="address.city">
<input type="text" name="address.street">
</p>
<input type="submit">
</form>
BindingResult
@RequestMapping("/submittedData")
@ResponseBody
ModelAndView submittedData(@ModelAttribute("userCO") UserCO userCO,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
ModelAndView modelAndView = new ModelAndView("form");
return modelAndView;
}
ModelAndView modelAndView = new ModelAndView("submittedData");
return modelAndView;
}
index.jsp
<form:errors path="userCO.*"/>
<form method="get" action="submittedData">
</form>
Exercise 5
(1) Long phoneNumber
(2) List<String> subjects
(3) Date dateOfBirth(with @DateTime formatter annotation)
@InitBinder
@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.setDisallowedFields(new String[]{"phoneNumber"});
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("dd-MM-yyyy");
webDataBinder.registerCustomEditor(Date.class,"date",
new CustomDateEditor(simpleDateFormat,false));
}
Writing custom property
@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.registerCustomEditor(String.class, "firstname", new StudentNameEditor());
}
import java.beans.PropertyEditorSupport;
public class StudentNameEditor extends PropertyEditorSupport {
@Override
public void setAsText(String name) throws IllegalArgumentException {
if (name.length() > 2) {
setValue(name);
}
}
}
Exercise 6
JSR annotation for Binding validation
@NotEmpty
@NotNull
@valid annotation need to be placed before the command objects in order to make all this annotation work for validation.
Custom Validation messages
We can simple use message attribute of the validation annotation to provide custom messages e.g
@Size(min = 2, max = 20,message = "Size must be between 2 to 20")
@Size(min = 2, max = 20,message = "Size must be between {min} to {max}")
Custom Validation messages(Cont.)
Size.userCO.firstname = Size must be between 2 to 20
other options
Size.firstname = Size must be between 2 to 20
Size.java.lang.String = Size must be between 2 to 20
Size = Size must be between 2 to 20
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/messages"/>
</bean>
Exercise 7
Custom annotation
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = GenderValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsGenderValid {
String message() default "Please provide a valid gender";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom annotation (Cont.)
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
public class GenderValidator implements ConstraintValidator<IsGenderValid, String> {
@Override
public void initialize(IsGenderValid constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return Arrays.asList("male", "female").contains(value);
}
}
Exercise 8
Interceptor
<mvc:interceptors>
<bean class="com.spring.demo.interceptor.DemoInterceptor"/>
</mvc:interceptors>
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("preHandle " + request.getRequestURI());
return true;
}
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView)
throws Exception {
System.out.println("postHandle " + request.getRequestURI());
}
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("afterCompletion " + request.getRequestURI());
}
}
Exercise 9