Spring Framework Advance (Web)
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
- Implement AbstractController in StudentController. create a method index in it and render index.jsp view from it which displays messages "Hello from index.gsp"
- Now remove AbstractController and use MultiActionController for StudentController which contains 2 actions one action renders a jsp view and another action uses HttpServletResponse to show the output on the Web browser.
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
- Use annotation in StudentController to define a default action index which renders a jsp page.
- Create one more annotation based action hello inside the StudentController which and use @ResponseBody annotation from it to display Hello world.
- Create one more action which sends Model HelloWorld to the jsp file.
@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
- Use @PathVariable annotation to access firstname and lastname in an action inside StudentController place both the parameters in different arguments and access them.
- Now place both the arguments inside a Map and access the Map instead.
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
- Use @RequestParam annotation to access the firstname and lastname in formData action of StudentController.
- Create a StudentCO and bind firstname and lastname with instance variable of StudentCO.
- Use @ModelAttribute annotation to display the values firstname and lastname in submittedData.jsp.
- Use @ModelAttribute annotation to add Heading "Spring MVC Demo" in every model.
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
- Introduce following instance variables inside UserCO:
(1) Long phoneNumber
(2) List<String> subjects
(3) Date dateOfBirth(with @DateTime formatter annotation)
- Introduce the input type for above instance variables and bind it with UserCO intsance.
- Introduce a field Address in UserCO and create changes in jsp form for the same.
@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
- Use InitBinder to validate dateOfBirth.
- Create Custom data editor in order which stops binding of firstname from UserCO.
- Use <fom:error/> tag to display the errors.
JSR annotation for Binding validation
- @Size(min:2,max:20)
-
@NotEmpty
-
@Email
-
@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.)
- Create messeges.properties file and place the key of the message in the pattern. Annotation.ObjectRefrence.FieldName e.g
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
- Place the following bean in Configuration XML
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/messages"/>
</bean>
Exercise 7
- Use @NotBlank, @Size, @Email annotation for validations.
- Give custom validation messages via message tag of annotations
- Give custom validation messages messageSource Bean.
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
- Create custom annotation isHobbyValid for student.
- Introduce a new fields hobby in StudentCO and apply isHobbyValid annotation on it.
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
- Create a SecurityInterceptor which will ensure that only logged in users can view secured resource.
- create a login and admin controller
- secure all controller except login
- set logged in user's object in every request so that it is available in every jsp page
Spring Framework Advance (Web)
By Pulkit Pushkarna
Spring Framework Advance (Web)
- 2,041