본문 바로가기

SPRING

Spring 4 MVC Form Validation and Resource Handling (Annotations)

이번 포스트에서는 Spring Form Tags JSR-303 validation annotations, hibernate-validators

를 사용하는 Form Validation 사용하는 방법과  MessageSource를 사용하는 국제화 지원, ResourceHandlerRegistry 사용하여 view 안에 있는 static resources(예를들면, css, jaacript, images)의 처리 방법을 배울 것이다. 모두 annotation 기반 환경설정을 사용할 것이다.

시작해보자!

Registration form을 포함하는 간단한 applIcation을 만들 것이다.

JSR-303 validation annotations를 통해서 the user-input을 유효성 검사하고

Properties 파일을 통해서 국제화된 validation messages를 사용하여 기본적인 메시지들을 오버라이딩할 것이다.

또한 (예를들어, 우리의 page들에 BootStrap Css를 적용) static resources에 접근한다.

 

   Spring 4.0.6.RELEASE

   validation-api 1.1.0.Final

   hibernate-validator 5.1.2.Final

   Bootstrap v3.1.0

   Maven 3

   JDK 1.6

   Tomcat 7.0.54

   Eclipse JUNO Service Release 2

 

Step 1: Create the directory structure

 

 

Step 2: Update pom.xml to include required dependencies

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.springbatch</groupId> <artifactId>Spring4MVCFormValidationExample</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>Spring4MVCFormValidationExample</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <springframework.version>4.0.6.RELEASE</springframework.version> <hibernate.validator.version>5.1.2.Final</hibernate.validator.version> <javax.validation.version>1.1.0.Final</javax.validation.version> </properties> <dependencies> <!-- Spring dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <!-- jsr303 validation dependencies --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>${javax.validation.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>${hibernate.validator.version}</version> </dependency> <!-- Servlet dependencies --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>Spring4MVCFormValidationExample</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <finalName>Spring4MVCFormValidationExample</finalName> </build> </project>

첫번째 알아야 할 점은 maven-war-plugin 선언이다. 완전한 annotation configuration을 사용하기 때문에, web.xml을 포함시키지 않을 거다. 그래서 war package build하기 위한 maven 실패를 피하기 위해서 plugin의 환경설정이 필요하다.

Validation 부분에서는, validation-api specification(명세서?)를 나타내고, 반면에 hibernate-validator은 이 명세서의 구현이다. 또한 Hibernate-validator는 명세서 부분에 속하지 않는 자신만의 annotation(@Email, @NotEmpty 등등)을 제공한다.

 

이러한 것들과 덧붙여서, servlet api jstl view를 사용하기 위해서, JSP/Servlet/Jstl dependences를 포함하고 있다. 일반적으로 containers는 이미 이러한 libaries를 포함하고 있을 수도 있지만, 또한 pom.xml ‘provied’로서 scope를 설정할 수 있다.

 

 

Step 3: Create POJO / Domain Object

Form submision을 통해서 사용자가 제공하는 data를 쥐고 있는 form backing bean으로써 역할을 하는 도메인 객체이다.  (validation annoation을 사용해서) 유효성 검사하길 원하는 properties annotate할 것이다.

 

package com.websystique.springbatch.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;

public class Student implements Serializable {
	
	 @Size(min=3, max=30)
	    private String firstName;
	 
	    @Size(min=3, max=30)
	    private String lastName;
	 
	    @NotEmpty
	    private String sex;
	 
	    @DateTimeFormat(pattern="dd/MM/yyyy")
	    @Past @NotNull
	    private Date dob;
	 
	    @Email @NotEmpty
	    private String email;
	 
	    @NotEmpty
	    private String section;
	 
	    @NotEmpty
	    private String country;
	 
	    private boolean firstAttempt;
	 
	    @NotEmpty
	    private List subjects = new ArrayList();
	 
	    public String getFirstName() {
	        return firstName;
	    }
	 
	    public void setFirstName(String firstName) {
	        this.firstName = firstName;
	    }
	 
	    public String getLastName() {
	        return lastName;
	    }
	 
	    public void setLastName(String lastName) {
	        this.lastName = lastName;
	    }
	 
	    public String getSex() {
	        return sex;
	    }
	 
	    public void setSex(String sex) {
	        this.sex = sex;
	    }
	 
	    public Date getDob() {
	        return dob;
	    }
	 
	    public void setDob(Date dob) {
	        this.dob = dob;
	    }
	 
	    public String getEmail() {
	        return email;
	    }
	 
	    public void setEmail(String email) {
	        this.email = email;
	    }
	 
	    public String getSection() {
	        return section;
	    }
	 
	    public void setSection(String section) {
	        this.section = section;
	    }
	 
	    public String getCountry() {
	        return country;
	    }
	 
	    public void setCountry(String country) {
	        this.country = country;
	    }
	 
	    public boolean isFirstAttempt() {
	        return firstAttempt;
	    }
	 
	    public void setFirstAttempt(boolean firstAttempt) {
	        this.firstAttempt = firstAttempt;
	    }
	 
	    public List getSubjects() {
	        return subjects;
	    }
	 
	    public void setSubjects(List

subjects) { this.subjects = subjects; } @Override public String toString() { return "Student [firstName=" + firstName + ", lastName=" + lastName + ", sex=" + sex + ", dob=" + dob + ", email=" + email + ", section=" + section + ", country=" + country + ", firstAttempt=" + firstAttempt + ", subjects=" + subjects + "]"; } }

 

위의 코드에서, @Size, @Past & @NotNull은 표준 annotation이고 반면에 @NotEmpty & @ Email specification의 부분이 아니다.

 

 

Step 4: Add controller

 

package com.websystique.springbatch.controller;

import java.util.ArrayList;
import java.util.List;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.websystique.springbatch.model.Student;

@Controller
@RequestMapping("/")
public class HelloWorldController {

	// 기본적인 Get handler 역할
	@RequestMapping(method = RequestMethod.GET)
	public String newRegistration(ModelMap model) {

		Student student = new Student();
		model.addAttribute("student", student);

		return "enroll";
	}

	/*
	 * form submission에 호출될 메소드
	 */
	@RequestMapping(method = RequestMethod.POST)
	public String saveRegistration(@Valid Student student, BindingResult result, ModelMap model) {

		if (result.hasErrors()) {
			System.out.println("에러존재");
			return "enroll";
		}

		model.addAttribute("success", "Dear " + student.getFirstName() + ",your Registration completed successful");
		return "success";

	}

	@ModelAttribute("sections")
	public List initializeSections() {

		List sections = new ArrayList();
		sections.add("Graduate");
		sections.add("Post Graduate");
		sections.add("Research");
		return sections;
	}

	@ModelAttribute("countries")
	public List initializeCountries() {

		List countries = new ArrayList();
		countries.add("USA");
		countries.add("CANADA");
		countries.add("FRANCE");
		countries.add("GERMANY");
		countries.add("ITALY");
		countries.add("OTHER");
		return countries;
	}

	@ModelAttribute("subjects")
	public List initializeSubjects() {

		List subjects = new ArrayList

(); subjects.add("Physics"); subjects.add("Chemistry"); subjects.add("Life Science"); subjects.add("Political Science"); subjects.add("Computer Science"); subjects.add("Mathmatics"); return subjects; } }

 

@Controller @ReuqesetMapping에 의해서 mapped 되어지는 pattern의 요청을 다루는 클래스이다.

NewRegistration Method는 기본적으로 GET request를 제공하는 @RequestMethod.GET으로 annotated 되어져있고, from data-holder 역할을 하는 model object를 추가하고 있다.

Method initializeSections, initializeCountries & initializeSubjects  view/jsp에서 사용되어지는 값을 가진 요청 object를 생성한다

SaveRegistration Method @RequestMethod.POST annotatied되어져 있다.

이 메소드에 있는 파라미터와 명령을 주목해야한다.

@Valid는 스프링에게 associated 되어진 객체(student)를 유효성 검사하라고 요청한다.

BindingResult는 유효성검사의 결과와 유효성 검사를 하는 동안 발생되어지는 에러를 포함하고 있다. 

주의) BindingResult는 유효성 검사 대상인 객체의 바로 뒤에 붙여져야한다. 그렇지 않는 spring이 유효성 검사를 하지 않을 수 있고, 에러를 발생시킬 수 있다.

유효성 검사 실패의 경우에, 기본적으로 발생되어지는 에러 메시지가 스크린에 보여진다. 그것이 바람직하지 않을 수 있다.

따라서 internationalized messages 사용함으로써 특정된 메시지를 각각의 필드에 제공할 수 있다. 그렇게 하기 위해서, 환경설정 클래스에 MessageSource의 환경설정을 해야하고 실제로 필요한 메시지를 포함하고있는 properties file을 제공해야 한다.

 

Step 5: Add Configuration Class

 

 

package com.websystique.springbatch.configuration; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.websystique.springbatch") public class HelloWorldConfiguration extends WebMvcConfigurerAdapter { /* * Configure View Resolver */ @Bean public ViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; } /* * Configure ResourceHandlers to serve static resources like CSS/ Javascript etc... * */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } /* * Configure MessageSource to provide internationalized messages * */ @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("messages"); return messageSource; } }

@Configuration은 이 클래스가 하나 이상의 bean method(@Bean spring container에 의해서 관리되어질 수 있도록 처리한다)를 포함하고 있다고 나타낸다.

@EnableWebMvc xml에 있는 mvc:annotation-driven과 동일하다. 이것은 들어오는 요청을 특정한 메소드에 map하기 위한 @RequestMapping을 사용하는 @Controller-annotated classes를 지원할 수 있게 해준다.

@ComponentScan context:component-scan base-package=”…”와 동일하다. 이것은 spring이 관리하는 beans/classes가 어디에 있는지 찾을 수 있게 해준다.

 

ViewResolver 메소드는 view를 정의하기 위한 view resolver를 환경설정한다.

addResourceHandlers 메소드는 페이지가 필요하는 static resources css,javascript, images 등을 위한 ResourceHandler를 환경설정하는 메소드이다.

위의 환경설정은 ‘/static’으로 시작하는 모든 resource 요청은 webapp 아래에 있는 /static 폴더로부터 제공받을 수 있게 해준다.

이 예제에서는, webapp 디렉토리 안에 있는 /static/css에 모든 css file을 넣을 것이다.

WebMvcConfigurerAdapter에 이 메소드가 정의되어 있기 때문에. 우리는 우리의 static resources를 등록하기 위해서 이 class를 상속해야 한다.

 

messageSource method properties파일에 있는 [internationalized] message를 지원하기 위한 Message bundle을 환경설정한다.

parameter (messages) baseName method에 제공했다. 스프링은 classpath에서messages,properties라고 이름지어진 파일을 찾을 것이다.

 

src/main/resources/messages.properties

 

Size.student.firstName=First Name must be between {2} and {1} characters long
Size.student.lastName=Last Name must be between {2} and {1} characters long
NotEmpty.student.sex=Please specify your gender
NotNull.student.dob=Date of birth can not be blank
Past.student.dob=Date of birth must be in the past
Email.student.email=Please provide a valid Email address
NotEmpty.student.email=Email can not be blank
NotEmpty.student.country=Please select your country
NotEmpty.student.section=Please select your section
NotEmpty.student.subjects=Please select at least one subject
typeMismatch=Invalid format

위의 메시지는 다음의 특정한 패턴을 가진다.

{ValidationAnnotationClass}.{modelObject}.{fieldName}

추가적으로,  특정한 annotation(예를들면, @Size)을 기반으로, {0}, {1},….{i}을 사용하는 메시지에 값을 넘겨줄 수 있다.

 

위의 환경설정은 xml에서 이와 같다.

 

<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"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
  
    <context:component-scan base-package="com.websystique.springmvc" />
    <mvc:annotation-driven/>
 
    <mvc:resources mapping="/static/**" location="/static/" />
    <mvc:default-servlet-handler />
 
 
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename">
            <value>messages</value>
        </property>
    </bean>
 
 
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/views/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>
  
</beans>

 

Step 6: Add Views (Simple JSP Pages)

두 가지의 간단한 jsp페이지를 추가할 것이다. 첫번째 유저로부터 입력 값을 받기 위한 Form을 포함하고, 두번째는 입력 값의 유효성 검사가 성공적으로 마무리 되면 유저에게 성공 메시지를 보내기 위한 Form이다.

 

아래는 static resources(bootstrap.css)를 포함하고 있는 코드 일부분이다.

경로를 주목해보자. 우리가 이미 이전 단계에서 ‘/static/**’을 사용하는 ResourceHandlers를 환경설정했기 때문에, /static/folder 안에 있는 css file이 찾아질 것이다.

 

완전한 JSP file은 아래와 같다.

WEB-INF/views/enroll.jsp

 

<div class="has-error"> <form:errors path="email" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="section">Section</label> <div class="col-md-7" class="form-control input-sm"> <form:radiobuttons path="section" items="${sections}" /> <div class="has-error"> <form:errors path="section" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="country">Country</label> <div class="col-md-7"> <form:select path="country" id="country" class="form-control input-sm"> <form:option value="">Select Country</form:option> <form:options items="${countries}" /> </form:select> <div class="has-error"> <form:errors path="country" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="firstAttempt">First Attempt ?</label> <div class="col-md-1"> <form:checkbox path="firstAttempt" class="form-control input-sm"/> <div class="has-error"> <form:errors path="firstAttempt" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="subjects">Subjects</label> <div class="col-md-7"> <form:select path="subjects" items="${subjects}" multiple="true" class="form-control input-sm"/> <div class="has-error"> <form:errors path="subjects" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-actions floatRight"> <input type="submit" value="Register" class="btn btn-primary btn-sm"> </div> </div> </form:form> </div> </body> </html>

 

 

WEB-INF/views/success.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Student Enrollment Detail Confirmation</title> </head> <body> <div class = "success"> Confirmation message : ${success } <br> We have also sent you a confirmation mail to your email address : ${student.email}. </div> </body> </html>

 

 

Step 7: Add Initializer class

 

package com.websystique.springbatch.configuration; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; public class HelloWorldInitializer implements WebApplicationInitializer { public void onStartup(ServletContext container) throws ServletException { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(HelloWorldConfiguration.class); ctx.setServletContext(container); ServletRegistration.Dynamic servlet = container.addServlet( "dispatcher", new DispatcherServlet(ctx)); servlet.setLoadOnStartup(1); servlet.addMapping("/"); } }

 

위의 내용은 DispatcherServlet을 사용하는 web.xml의 내용과 유사하다. 또한 Spring configuration file(spring-servlet.xml)을 제공하는 대신에, 여기서는 Configuration class를 등록하는 걸로 대신하고 있다.

 

Update: 위의 class는 심지어 더욱 간결하게 사용될 수 있는데, 아래처럼 AbstractAnnotationConfigDipatcherServletInitializer 클래스를 사용하는 것이다.

 

 

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
 
public class HelloWorldInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
 
    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] { HelloWorldConfiguration.class };
    }
  
    @Override
    protected Class

[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }

Step 8: Build and Deploy the application

명심해야 할 것이 있다.  WebApplicationInitializer과 같은 Spring java 기반의 환경설정은  Servlet 3.0 containers에 의존한다. 따라서 3.0 이하의 servlet이 선언되어 있다면, web.xml을 가져야 한다.우리의 경우는, web.xml을 제거할 수 있다.(3.1 사용 중)

 

에러가 존재할 경우

 

성공 페이지:

 

번역:http://websystique.com/springmvc/spring-4-mvc-form-validation-with-hibernate-jsr-validator-resource-handling-using-annotations/