티스토리 뷰
Spring Advanced : Web MVC + Form Validation - Custom Annotation
Korean Eagle 2020. 5. 16. 02:011. 이 포스트의 내용은
1-1 Entity/DTO의 courseCode라는 속성에 @CourseCode라는 사용자 정의 validator를 사용하여
1-2 사용자가 입력한 코드가 지정된 단어로 시작한 경우에만 submit을 허용하도록 하는 예제이다.
1-3 아래 customer form(캡처)을 보면 마지막 Course Code 항목이 기능을 추가하는 부분이다.
1-4. 다른 속성에 대해서는 Spring : Web MVC + Form Validation - with InitBinder을 참고한다.
2. 기본 세팅 하기
2-0 org.apache.maven webapp archetype을 사용하면 https://kogle.tistory.com/70?category=867645 참조한다.
2-1 dependency 추가
2-2 webmvc 설정 (web.xml, spring-customer-validation.xml)
2-3 jsp 파일 생성
2-4 entity추가 및 validator 설정
2-5 controller 생성
2-6 customer validator 생성
3. dependency 추가
3-1 Web MVC + Hibernate Validation만 사용한다.
3-2 스프링 dependency Spring core, context, webmvc
3-3 하이버네이트 hibernate-validator
3-4 jsp, jstl, servlet 지원을 위한 javax.servlet-api, javax.servlet.jsp-api, jstl
3-5 개발 편의성을 위한 lombok
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
3. Web MVC 설정 (advanced 카테고리니 자세한 설명은 생략한다.)
3-1 web.xml 설정
3-1-1 서블릿 설정과 매핑 정보가 전부다. contextConfiguration에 spring 설정이 들어간다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
id="WebApp_ID" version="4.0">
<display-name>spring-mvc-demo</display-name>
<absolute-ordering />
<!-- Spring MVC Configs -->
<!-- Step 1: Configure Spring MVC Dispatcher Servlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-custom-validation.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Step 2: Set up URL mapping for Spring MVC Dispatcher Servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3-2 spring container 설정 (spring-customer-validation.xml)
3-2-1 많은 기능을 사용하지 않기 때문에 간단하다.
3-2-2 리소스 폴더를 webapp/resources로 설정
3-2-3 annotation설정 사용을 위해 component-scan, annotation-driven사용
3-2-4 jsp파일 사용을 위한 ViewResolver 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Add support for reading web resources -->
<mvc:resources location="/resources/"
mapping="/resources/**"></mvc:resources>
<!-- Step 3: Add support for component scanning -->
<context:component-scan
base-package="pe.pilseong.customer_validation" />
<!-- Step 4: Add support for conversion, formatting and validation support -->
<mvc:annotation-driven />
<!-- Step 5: Define Spring MVC view resolver -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
4. jsp 파일 작성
4-1 customer-form.jsp
4-2 Customer entity의 courseCode 속성에 @CourseCode라는 속성을 사용할 예정이다.
4-3 form:errors element는 에러가 발생 시에 해당 속성에 저장된 error 메시지를 표출한다.
4-4 cssClass error은 styles.css파일에 정의된 .error 를 사용한다. 그냥 .error { color: red; } 가 전부다.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous">
<link rel="stylesheet" href="${ pageContext.request.contextPath }/resources/css/style.css">
<title>Customer Form</title>
</head>
<body>
<div class="container">
<h1 class="display-5 mb-3">Customer Form</h1>
<form:form method="GET" modelAttribute="customer"
action="processForm">
<p>Fill out the form. Asterisk (*) means required.</p>
<div class="form-group">
<label for="firstName">First Name</label>
<form:input class="form-control" path="firstName" id="firstName" />
</div>
<div class="form-group">
<label for="lastName">Last Name (*)</label>
<form:input class="form-control" path="lastName" id="lastName" />
<form:errors path="lastName" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="freePass">Free pass</label>
<form:input class="form-control" path="freePass" id="freePass" />
<form:errors path="freePass" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="postal">Postal Code</label>
<form:input class="form-control" path="postalCode" id="postalCode" />
<form:errors path="postalCode" cssClass="error"></form:errors>
</div>
<div class="form-group">
<label for="courseCode">Course Code</label>
<form:input class="form-control" path="courseCode" id="courseCode" />
<form:errors path="courseCode" cssClass="error"></form:errors>
</div>
<input class="btn btn-outline-primary" type="submit"
value="Submit"></input>
</form:form>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
</body>
</html>
4-3 customer-confirmation.jsp
4-3-1 결과를 표출하는 부분이다. 하나도 중요한 내용이 없다. Bootstrap 코드가 전부다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous">
<title>Customer Confirmation</title>
</head>
<body>
<div class="container">
<h1 class="display-5">Student Confirmation</h1>
<p class="text-success">
Your Name: ${ customer.lastName }, ${ customer.firstName }
</p>
<p class="text-success">
FreePass: ${ customer.freePass}
</p>
<p class="text-success">
Postal Code: ${ customer.postalCode}
</p>
<p class="text-success">
Course Code: ${ customer.courseCode}
</p>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
</body>
</html>
5. Customer entity를 추가하고 Validator를 정의한다.
5-0 @NotNull을 사용하려면 공백 처리를 위한 Controller에 trimming을 위한 InitBinder 설정이 필요하다.
5-1 제일 아래 courseCode라는 속성의 @CourseCode annotation이 custom validator이다.
package pe.pilseong.customer_validation;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Range;
import lombok.Data;
import pe.pilseong.customer_validation.validation.CourseCode;
@Data
public class Customer {
private String firstName;
@NotNull(message = "Empty string is not allowed")
@Min(value = 3, message = "At least 3 characters")
private String lastName;
@NotNull(message = "is required")
@Range(min = 0, max = 10, message = "Range is from 0 to 10")
private Integer freePass;
@NotNull(message = "is required")
@Pattern(regexp = "^[a-zA-Z0-9]{5}", message = "only 5 chars/digits" )
private String postalCode;
@NotNull(message = "is required")
@CourseCode(value = "HEO", message = "must start with HEO")
private String courseCode;
}
6. controller를 작성한다.
6-1 아주 단순하다. /customer/showForm으로 form화면을 표출하고
6-2 form 작성 후 /customer/processForm으로 GET 요청을 하면 검증을 거쳐
6-3 모든 validation이 통과할 경우 customer-confirmation.jsp로 넘어간다.
6-4 에러가 발생하면 다시 이전 페이지로 돌아간다.
6-5 당연한 사실이지만 @Valid @ModelAttribute, BindingResult 순서가 중요하다.
6-6 공백 입력의 null처리를 위해 InitBinder를 사용하였다.
package pe.pilseong.customer_validation.controller;
import javax.validation.Valid;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import pe.pilseong.customer_validation.Customer;
@Controller
@RequestMapping("/customer")
public class CustomerController {
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
StringTrimmerEditor editor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, editor);
}
@GetMapping("/showForm")
public String showForm(Model model) {
model.addAttribute("customer", new Customer());
return "customer-form";
}
@GetMapping("/processForm")
public String processForm(@Valid @ModelAttribute("customer") Customer customer, BindingResult result) {
if (result.hasErrors()) {
return "customer-form";
}
return "customer-confirmation";
}
}
7. CourseCode custome-validator를 작성한다.
7-1 @CourseCode annotation 작성
7-1-1 @Constraint는 어떤 클래스를 사용하여 @CourseCode annotation을 검증할 것인지 지정한다.
7-1-1-1 이 클래스는 ConstraintValidator 인터페이스를 구현한 별도의 클래스를 작성해야 한다.
7-1-1-2 CourseCodeConstraintValidator 클래스이다.
7-1-2 @Target은 annotation이 어디에 정의될 수 있는지 지정하는 것이고 메소드와 필드에 사용하도록 지정하였다.
7-1-3 @Retention은 언제 annotation이 사용할지에 대한 부분으로 실행 시에 실시간으로 사용된다는 의미다.
7-1-4 중요한 부분은 value(), message() 이 부분
7-1-4-1 이 두 함수는 @CourseCode(value = "", message ='")의 value와 message값을 가져온다.
7-1-4-2 default는 annotation의 속성이 설정되지 않았을 경우의 기본값이다.
7-1-5 나머지 groups, payload는 boilerplate이므로 특별한 경우가 아니면 그냥 복사해서 사용하면 된다.
package pe.pilseong.customer_validation.validation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Constraint(validatedBy = CourseCodeConstraintValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CourseCode {
public String value() default "PIL";
public String message() default "must start with PIL";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
7-2 @CourseCode 검증 로직인 CourseCodeContraintValidator 작성
7-2-0 ConstraintValidator<T, R> Generic의 첫 번째는 Annotation클래스 두 번째는 입력 form값의 타입이다.
7-2-1 initialize는 처음 이 객체가 초기화 될 때 사용된다.
7-2-1-1 여기서는 어떤 문자로 시작하는지 시작기호를 받아와 coursePrefix에 저장한다. 초기값은 PIL
7-2-1-2 Customer entity 설정에서 value = "HEO" 이므로 결과가 HEO로 필터 된다.
7-2-2 isValid가 중요한데, inputCode는 사용자가 form에 입력한 값이 들어온다.
7-2-2-1 입력된 데이터가 coursePrefix에 저장된 문자열로 시작되는지 체크해서 boolean값을 돌려준다.
package pe.pilseong.springmvcstudent.validation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CourseCodeConstraintValidator implements ConstraintValidator<CourseCode, String> {
private String coursePrefix;
public void initialize(CourseCode courseCode) {
this.coursePrefix = courseCode.value();
}
@Override
public boolean isValid(String inputCode, ConstraintValidatorContext context) {
boolean result;
if (inputCode!= null) {
result = inputCode.startsWith(coursePrefix);
} else {
result = false;
}
return result;
}
}
7-3 결과 화면
7-3-1 마지막 Course Code가 HEO로 시작하면 에러가 사라진다.
'Spring > Spring Advanced' 카테고리의 다른 글
- Total
- Today
- Yesterday
- 도커 개발환경 참고
- AWS ARN 구조
- Immuability에 관한 설명
- 자바스크립트 멀티 비동기 함수 호출 참고
- WSDL 참고
- SOAP 컨슈머 참고
- MySql dump 사용법
- AWS Lambda with Addon
- NFC 드라이버 linux 설치
- electron IPC
- mifare classic 강의
- go module 관련 상세한 정보
- C 메모리 찍어보기
- C++ Addon 마이그레이션
- JAX WS Header 관련 stackoverflow
- SOAP Custom Header 설정 참고
- SOAP Custom Header
- SOAP BindingProvider
- dispatcher 사용하여 설정
- vagrant kvm으로 사용하기
- git fork, pull request to the …
- vagrant libvirt bridge network
- python, js의 async, await의 차이
- go JSON struct 생성
- Netflix Kinesis 활용 분석
- docker credential problem
- private subnet에서 outbound IP 확…
- 안드로이드 coroutine
- kotlin with, apply, also 등
- 안드로이드 초기로딩이 안되는 경우
- navigation 데이터 보내기
- 레이스 컨디션 navController
- raylib
- spring boot
- crud
- 상속
- Rest
- 자바
- XML
- RestTemplate
- form
- jsp
- 설정
- 스프링
- login
- WebMvc
- mapping
- Angular
- 스프링부트
- 매핑
- Many-To-Many
- 외부파일
- 설정하기
- Validation
- MYSQL
- hibernate
- one-to-many
- one-to-one
- Spring
- 하이버네이트
- Security
- 로그인
- Spring Security