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,022