MessageConfig
@Configuration
public class MessageConfig {
@Bean
public MessageSource messageSource(){
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setBasenames("messages.commons");
ms.setDefaultEncoding("UTF-8");
ms.setUseCodeAsDefaultMessage(true); //메세지 코드가 없는 경우 코드로 메세지 대체
return ms;
}
}
commons.properties
LOGIN_MSG={0}({1})님 로그인
EMAIL=이메일
commons_en.properties
LOGIN_MSG={0}({1}) logged in
EMAIL=email
@SpringJUnitWebConfig
@ContextConfiguration(classes = MvcConfig.class)
public class MessageSourceTest {
@Autowired
private MessageSource messageSource;
@Test
void test1(){
String message = messageSource.getMessage("LOGIN_MSG", new Object[] {"사용자01","user01"}, Locale.KOREAN);
System.out.println(message);
}
@Test
void test2(){
String message = messageSource.getMessage("EMAIL", null, Locale.KOREAN);
System.out.println(message);
}
@Test
void test3(){
String message = messageSource.getMessage("LOGIN_MSG", new Object[] {"사용자01","user01"}, Locale.ENGLISH);
System.out.println(message);
}
}
MemberController.java
@Slf4j
@Controller
@RequestMapping("/member")
@RequiredArgsConstructor
public class MemberController {
private final MessageSource messageSource;
private final HttpServletRequest request;
@GetMapping("/join")
public String join(@ModelAttribute RequestJoin form) {
Locale locale = request.getLocale(); //요청 헤더 Accept-Language
String message = messageSource.getMessage("EMAIL", null, locale);
log.info(message);
return "member/join";
}
}
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<spring:message code="???" />
join.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<c:url var="actionUrl" value="/member/join" />
${commonValue}
<spring:message code="LOGIN_MSG" arguments="사용자01,user01" />
<spring:message code="LOGIN_MSG">
<spring:argument value="사용자01"/>
<spring:argument value="user01"/>
</spring:message>
<h1>
<spring:message code="회원가입" />
</h1>
<form:form method="POST" action="${actionUrl}" autocomplete="off" modelAttribute="requestJoin">
<dl>
<dt>
<spring:message code="이메일" />
</dt>
<dd>
<form:input path="email" cssClass="input-txt" cssStyle="border-color: red;"/>
</dd>
</dl>
<dl>
<dt>
<spring:message code="비밀번호"/>
</dt>
<dd>
<form:password path="password" />
</dd>
</dl>
<dl>
<dt>
<spring:message code="비밀번호_확인"/>
</dt>
<dd>
<form:password path="confirmPassword" />
</dd>
</dl>
<dl>
<dt>
<spring:message code="회원명"/>
</dt>
<dd>
<form:input path="userName" />
</dd>
</dl>
<dl>
<dt>
<spring:message code="약관동의" />
</dt>
<dd>
<form:checkbox path="agree" value="true" label="회원가입 약관에 동의합니다." />
</dd>
</dl>
<button type="submit"><spring:message code="가입하기" /></button>
</form:form>
supports(...) : 검증하는 커맨드 객체 한정 설정validate(Object target, Erros errors) : 커맨드 객체 검증Object target : 커맨드 객체 👉 형변환Errors errors : 검증 실패시 전달할 메세지 등록...JoinValidator
@Component
@RequiredArgsConstructor
public class JoinValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) { //RequestJoin 커맨드 객체만 검증하도록 제한
return clazz.isAssignableFrom(RequestJoin.class);
}
@Override
public void validate(Object target, Errors errors) {
}
}
reject("에러코드");
reject("에러코드", "기본 메세지")
rejectValue("필드명", "에러코드");
rejectValue("필드명", "에러코드", "기본메세지");
hasErrors() : 한개라도 reject 또는 rejectValue가 호출되면 true
MemberController
@Controller
@RequestMapping("/member")
@RequiredArgsConstructor
public class MemberController {
@Override
public boolean supports(Class<?> clazz) { //RequestJoin 커맨드 객체만 검증하도록 제한
return clazz.isAssignableFrom(RequestJoin.class);
}
@Override
public void validate(Object target, Errors errors) {
//1. 필수 항목 검증(email, password, confirmPassword, userName, agree)
//2. 이메일 중복 여부 검증(회원이 가입되어 있는지 체크)
//3. 비밀번호 자리수 체크 ( 8자리 이상 )
//4. 비밀번호 + 비밀번호확인 일치 여부
RequestJoin form = (RequestJoin) target;
String email = form.getEmail();
String password = form.getPassword();
String confirmPassword = form.getConfirmPassword();
String userNAme = form.getUserName();
boolean agree = form.isAgree();
// 필수 항목 검증
if(!StringUtils.hasText(email)) { //null값, isBlank() 대신해줌.
errors.rejectValue("email", "Required", "이메일을 입력하세요.");
}
if(!StringUtils.hasText(password)) {
errors.rejectValue("password", "Required", "비밀번호를 입력하세요");
}
}
}
fields.errors("필드명") : -> errors 객체 담긴 메세지를 필드명으로 조회 -> 배열
<form:erros path="팰드명" />
<br>구분 (delimiter)element="태그명"delimiter="에러와 에러 메세지 사이에 추가될 태그"join.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<c:url var="actionUrl" value="/member/join" />
<form:form method="POST" action="${actionUrl}" autocomplete="off" modelAttribute="requestJoin">
<dl>
<dt>
<spring:message code="이메일" />
</dt>
<dd>
<form:input path="email"/>
<form:errors path="email" element="div" delimiter=""/>
</dd>
</dl>
<dl>
<dt>
<spring:message code="비밀번호"/>
</dt>
<dd>
<form:password path="password" />
<form:errors path="password"/>
</dd>
</dl>
<dl>
<dt>
<spring:message code="비밀번호_확인"/>
</dt>
<dd>
<form:password path="confirmPassword" />
</dd>
</dl>
<dl>
<dt>
<spring:message code="회원명"/>
</dt>
<dd>
<form:input path="userName" />
</dd>
</dl>
<dl>
<dt>
<spring:message code="약관동의" />
</dt>
<dd>
<form:checkbox path="agree" value="true" label="회원가입 약관에 동의합니다." />
</dd>
</dl>
<button type="submit"><spring:message code="가입하기" /></button>
</form:form>
필수 항목 검증에 편의 메서드
rejectIfEmpty(...) : null 또는 "" rejectIfEmptyOrWhitespace(...) : 공백 포함 체크JoinValidator
@Component
@RequiredArgsConstructor
public class JoinValidator implements Validator {
private final MemberMapper mapper;
@Override
public boolean supports(Class<?> clazz) { //RequestJoin 커맨드 객체만 검증하도록 제한
return clazz.isAssignableFrom(RequestJoin.class);
}
@Override
public void validate(Object target, Errors errors) {
//1. 필수 항목 검증(email, password, confirmPassword, userName, agree)
//2. 이메일 중복 여부 검증(회원이 가입되어 있는지 체크)
//3. 비밀번호 자리수 체크 ( 8자리 이상 )
//4. 비밀번호 + 비밀번호확인 일치 여부
RequestJoin form = (RequestJoin) target;
String email = form.getEmail();
String password = form.getPassword();
String confirmPassword = form.getConfirmPassword();
String userNAme = form.getUserName();
boolean agree = form.isAgree();
//1. 필수 항목 검증(email, password, confirmPassword, userName, agree)
ValidationUtils.rejectIfEmptyOrWhitespace(errors,"email","Required", "이메일을 입력하세요");
ValidationUtils.rejectIfEmptyOrWhitespace(errors,"password","Required", "비밀번호를 입력하세요");
ValidationUtils.rejectIfEmptyOrWhitespace(errors,"confirmPassword","Required", "비밀번호를 확인하세요");
ValidationUtils.rejectIfEmptyOrWhitespace(errors,"userName","Required", "회원명을 입력하세요");
if(!agree){
errors.rejectValue("agree", "Requered", "회원가입 약관에 동의하세요");
}
//2. 이메일 중복 여부 검증(회원이 가입되어 있는지 체크)
if(StringUtils.hasText(email) && mapper.exists(email) != 0L){
errors.rejectValue("email", "Duplicated");
}
//3. 비밀번호 자리수 체크 ( 8자리 이상 )
if(StringUtils.hasText(password) && password.length() < 8){
errors.rejectValue("password", "Length");
}
//4. 비밀번호 + 비밀번호확인 일치 여부
if(StringUtils.hasText(password) && StringUtils.hasText(confirmPassword) && !password.equals(confirmPassword)){
errors.rejectValue("confirmPassword", "Mismatch");
}
}
}
join.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<c:url var="actionUrl" value="/member/join" />
<form:form method="POST" action="${actionUrl}" autocomplete="off" modelAttribute="requestJoin">
<dl>
<dt>
<spring:message code="이메일" />
</dt>
<dd>
<form:input path="email"/>
<form:errors path="email"/>
</dd>
</dl>
<dl>
<dt>
<spring:message code="비밀번호"/>
</dt>
<dd>
<form:password path="password" />
<form:errors path="password"/>
</dd>
</dl>
<dl>
<dt>
<spring:message code="비밀번호_확인"/>
</dt>
<dd>
<form:password path="confirmPassword" />
<form:errors path="cofirmPassword"/>
</dd>
</dl>
<dl>
<dt>
<spring:message code="회원명"/>
</dt>
<dd>
<form:input path="userName" />
<form:errors path="userName"/>
</dd>
</dl>
<dl>
<dt>
<spring:message code="약관동의" />
</dt>
<dd>
<form:checkbox path="agree" value="true" label="회원가입 약관에 동의합니다." />
<form:errors path="agree" element="div" delimiter=""/>
</dd>
</dl>
<button type="submit"><spring:message code="가입하기" /></button>
</form:form>
에러코드 + "." + 커맨드객체이름 + "." + 필드명에러코드 + "." + 필드명에러코드 + "." + 필드타입(자료형)에러코드MessageConfig
@Configuration
public class MessageConfig {
@Bean
public MessageSource messageSource(){
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setBasenames("messages.commons", "messages.validations", "messages.errors");
ms.setDefaultEncoding("UTF-8");
ms.setUseCodeAsDefaultMessage(true); //메세지 코드가 없는 경우 코드로 메세지 대체
return ms;
}
}
validations.properties
Required=필수 입력항목 입니다.
Required.email =이메일을 입력하세요.
Required.password=비밀번호를 입력하세요.
Required.confirmPassword=비밀번호를 확인하세요.
Required.userName=회원명을 입력하세요.
#name명이 중복될수있기 때문에 범위를 좀더 좁혀서 커맨드객체로 좁힘 = 회원 가입시 검증 메세지로 한정
Required.requestJoin.agree=회원가입 약관에 동의하세요.
Duplicated.requestJoin.email=이미 가입된 회원입니다.
Length.requestJoin.password=비밀 번호는 8자리 이상 입력하세요.
Mismatch.requestJoin.confirmPassword=비밀번호가 일치하지 않습니다.

에러코드 + "." + 커맨드객체이름 + "." + 필드명[인덱스].중첩필드명
에러코드 + "." + 커맨드객체이름 + "." + 필드명.중첩필드명
에러코드 + "." + 필드명[인덱스].중첩필드명
에러코드 + "." + 필드명.중첩필드명
에러코드 + "." + 중첩필드명
에러코드 + "." + 필드타입
에러코드
MvcConfig
@Configuration
@EnableWebMvc
@ComponentScan("org.choongang")
@Import({DBConfig.class, MessageConfig.class})
@RequiredArgsConstructor
public class MvcConfig implements WebMvcConfigurer {
private final JoinValidator joinValidator;
@Override
public Validator getValidator() {
return joinValidator;
}
}
@InitBinder
protected void InitBinder(WebDataBinder binder) {
binder.setValidator(new RegisterRequestValidator());
}
Bean Validation API
hibernate Validator : 구현체
1) Bean Validation Api - 에노테이션으로 기본 검증처리
2) 기본 애노테이션으로 안되는 검정 Validator를 정의 후 검증처리
implementation 'jakarta.validation:jakarta.validation-api:3.0.2' //Jakarta Validation API » 3.0.2
implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' //Hibernate Validator Engine » 8.0.1.Final
| 애노테이션 | 주요 속성 | 설명 | 지원 타입 |
|---|---|---|---|
| @AssertTrue @AssertFalse | 값이 true인지 또는 false인지 검사한다. null은 유효하다고 판단한다. | boolean Boolean | |
| @DecimalMax @DecimalMin | String value - 최대값 또는 최소값 boolean inclusive - 지정값 포함 여부 - 기본값 : true | 지정한 값보다 작거나 같은지 또는 크기가 같은지 검사한다. inclusive가 false이면 value로 지정한 값은 포함하지 않는다. null은 유효하다고 판단한다. | BigDecimal BigInteger CharSequence 정수타입 |
| @Max @Min | long value | 지정한 값보다 작거나 같은지 또는 크거나 같은지 검사한다. null은 유효하다고 판단한다. | BigDecimal BigInteger 정수타입 |
| @Digits | int integer - 최대 정수 자릿수 int fraction - 최대 소수점 자릿수 | 자릿수가 지정한 크기를 넘지않는지 검사한다. null은 유효하다고 판단한다. | BigDecimal BigInteger CharSequence 정수타입 |
| @Size | int min - 최소 크기 - 기본값 : 0 int max - 최대 크기 - 기본값 : 정수 최대값 | 길이나 크기가 지정한 값 범위에 있는지 검사한다. null은 유효하다고 판단한다. | CharSequence Collection Map 배열 |
| @Null @NonNull | 값이 null인지 또는 null이 아닌지 검사한다. | ||
| @Pattern | String regexp - 정규표현식 | 값이 정규표현식에 일치하는지 검사한다. null은 유효하다고 판단한다. | CharSequence |
| 애노테이션 | 설명 | 지원타입 |
|---|---|---|
| @NotEmpty | 문자열이나 배열의 경우 null이 아니고 길이가 0이 아닌지 검사한다. 콜렉션의 경우 null이 아니고 크기가 0이 아닌지 검사한다. | CharSequence Collection Map 배열 |
| @NotBlank | null이 아니고 최소한 한 개 이상의 공백아닌 문자를 포함하는지 검사한다. | CharSequence |
| @Positive @PositiveOrZero | 양수인지 검사한다. OrZero가 붙은 것은 0 또는 양수인지 검사한다. null은 유효하다고 판단한다. | BigDecimal BigInteger 정수타입 |
| 이메일 주소가 유효한지 검사한다. null은 유효하다고 판단한다. | CharSequence | |
| @Future @FutureOrPresent | 해당 시간이 미래 시간인지 검사한다. OrPresent가 붙은 것은 현재 또는 미래 시간인지 검사한다. null은 유효하다고 판단한다. | 시간 관련 타입 |
| @Past @PastOrPresent | 해당 시간이 과거 시간인지 검사한다. OrPresent가 붙은 것은 현재 또는 과거 시간인지 검사한다. null은 유효하다고 판단한다. | 시간 관련 타입 |
RequestJoin
@Data
public class RequestJoin {
@NotBlank(message = "이메일을 입력하세요")
@Email(message="이메일 형식이 아닙니다.")
private String email;
@NotBlank
@Size(min=8)
private String password;
@NotBlank
private String confirmPassword;
@NotBlank
private String userName;
@AssertTrue
private boolean agree;
}
validations.properties
Duplicated.requestJoin.email=이미 가입된 회원입니다.
Mismatch.requestJoin.confirmPassword=비밀번호가 일치하지 않습니다.
Email=이메일 형식이 아닙니다.
NotBlank=필수 입력항목 입니다.
NotBlank.email=이메일을 입력하세요.
NotBlank.password=비밀번호를 입력하세요.
NotBlank.confirmPassword=비밀번호를 확인하세요.
NotBlank.userName=회원명을 입력하세요.
AssertTrue.requestJoin.agree=회원가입 약관에 동의합니다.
Size.requestJoin.password=비밀번호는 8자리 이상 입력하세요.
MemberController
@Valid 애노테이션 추가
@Controller
@RequestMapping("/member")
@RequiredArgsConstructor
public class MemberController {
private final JoinValidator joinValidator;
@GetMapping("/join")
public String join(@ModelAttribute RequestJoin form){
return "member/join";
}
@PostMapping("/join")
public String joinPs(@Valid RequestJoin form, Errors errors){
//회원 가입 데이터 검증(유효성 검사)
joinValidator.validate(form, errors);
if(errors.hasErrors()){ //reject, rejectValue가 한번이라도 호출되면 treu = 에러가 한개라도 존재할경우
return "member/join";
}
return "redirect:/member/login";
}
}