Spring 개념정리

[Spring] MVC : 메시지, 커맨드 객체 검증

개발자 문문 2025. 7. 7. 20:36

안녕하세요 개발자 문문입니다.

오늘은 Spring MVC의 메시지, 커맨드 객체 검증에 대해 공부하겠습니다.

 

  • <spring:message> 태그

- 만약 어느 사이트의 언어를 다국어 처리해야 한다고 가정했을때, 사이트에 적혀있는 모든 글자를 다국어 처리하는건 쉽지 않습니다. (페이지를 여러개 만들거나, 다국어 로직을 만들어야 함)

- 위와 같은 상황이 왔을때 각 나라별 메시지를 미리 파일로 만들어 놓고 그 메시지를 불러오기만 한다면 한 페이지에서 간단하게 처리가 가능할 것 입니다.

- 스프링에서는 아래와 같은 작업을 진행하면 이런 기능을 구현할 수 있습니다.

 

1. 문자열을 담은 메시지 파일 생성

- src/main/resourece/message에 label.properties 생성

member.register = 회원 가입

term = 약관
term.agree = 약관동의
next.btn = 다음단계

 

2. MessageSource 빈 설정

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer{
...
@Bean
public MessageSource messageSource(){
	ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
    ms.setBasenames("message.label");
    ms.setDefaultEncoding("UTF-8");
    return ms;
}

- ResourceBundleMessageSource : Spring에서 국제화 메시지(properties 파일 기반)를 처리하기 위해 사용하는 메시지 소스

 

3. <spring:message> 태그를 사용해서 메시지 출력

~
<%@ taglib prefix="spring" url="http://www.springframework.org/tagss" %>
<!DOCTYPE html>
~
<head>
	<title><spring:message code"member.register"/><title>
</head>
~

- 이렇게 사용하면 title에 회원 가입이라고 출력됩니다.

 

  • 커맨드 객체의 값 검증과 에러 메시지 처리

- 스프링에서 커맨드 객체의 값이 올바른지 검사하려면 다음의 두 인터페이스를 사용합니다.

  • org.springframework.validation.Validator
  • org.springframework.validation.Errors
public class RegisterRequestValidator implements Validator {
	private static final String emailRegExp = 
			"^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" +
			"[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
	private Pattern pattern;

	public RegisterRequestValidator() {
		pattern = Pattern.compile(emailRegExp);
	}

	@Override
	public boolean supports(Class<?> clazz) {
		return RegisterRequest.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		System.out.println("RegisterRequestValidator#validate(): " + this);
		RegisterRequest regReq = (RegisterRequest) target;
		if (regReq.getEmail() == null || regReq.getEmail().trim().isEmpty()) {
			errors.rejectValue("email", "required");
		} else {
			Matcher matcher = pattern.matcher(regReq.getEmail());
			if (!matcher.matches()) {
				errors.rejectValue("email", "bad");
			}
		}
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
		ValidationUtils.rejectIfEmpty(errors, "password", "required");
		ValidationUtils.rejectIfEmpty(errors, "confirmPassword", "required");
		if (!regReq.getPassword().isEmpty()) {
			if (!regReq.isPasswordEqualToConfirmPassword()) {
				errors.rejectValue("confirmPassword", "nomatch");
			}
		}
	}

}

- supports (): 파라미터로 전달 받은 clazz 객체가 RegisterRequest 클래스로 타입 변환이 가능한지 확인하는 역할을 합니다.

- validate(Object target, Errors errors) 

  • target : 검사 대상 객체
  • errors : 검사 결과 에러 코드 설정 객체 (errors.rejectValue()로 에러코드 저장)
  • errors.rejectValue("프로퍼티명","에러코드") : 첫 번째 파라미터의 이름을 가진 값이 존재하지 않으면 해당 프로퍼티의 에러코를 "에러코드"로 지정합니다.
  • ValidateUtills.rejectIfEmptyOrWhitespace(Error errors,String field, String errorCode) : field에 해당하는 프로퍼티 값이 null이거나 빈 문자열이면 에러코드로 errorCode를 추가합니다.
  • 에러코드 추가 메서드에서 target을 사용하지 않고 프로퍼티 값을 검사할 수 있는 이유 : 커맨드 객체 파라미터 뒤에 Errors 타입 파라미터가 위치하면, 스프링 MVC는 메서드 호출시 커맨드 객체와 연결된 Errors 객체를 생성하기 때문입니다. 그렇기 때문에 Errors의 위치는 반드시 커맨드 객체 뒤에 있어야 합니다.
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq, Errors errors){
	new RegisterRequestValidator().validate(regReq, errors);
    if(errors.hasErrors())
    	return "register/step2";
    try{
    	memberRegisterService.regist(regReq);
        return "register/step2";
    } catch(DuplicationMemberException ex) {
    	errors.rejectValue("email","duplicate");
        return "register/step2";
    }
}

 

- errors.hasErrors() : validate()를 실행하는 과정에서 유효하지 않은 값이 존재하면 Errors의 rejectValue() 메서드를 실행하고 hasErrors() 메서드는 true를 반환합니다.

- 만약 프로퍼티 값이 아니라 커맨드 객체 자체가 잘못 되면 rejectValue() 대신 reject()를 사용하는데 이때 추가된 에러를 글로벌 에러라고 합니다.

errors.reject("notMatchingPassword");

 

- 에러 메시지 출력

<form:errors path="name"/>

- path : 에러 메시지를 출력할 프로퍼티명을 지정합니다.

  • name 프로퍼티에 에러 코드가 존재하면, 에러 코드에 해당하는 메시지를 출력합니다.

- 에러 코드에 맞는 메시지 지정 (label.properties)

member.register = 회원 가입

term = 약관
term.agree = 약관동의
next.btn = 다음단계

required=필수항목입니다.
bad.email=이메일이 올바르지 않습니다.
duplicate.email=중복된 이메일입니다.
nomatch.confirmPassword=비밀번호와 확이닝 일치하지 않습니다.

 

 

  • 글로벌 범위, 컨트롤러 범위 Validator

- 글로벌 범위 Validator 설정

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer{
    @Override
    public Validator getValidator() {
    	return new RegisterRequestValidator();
    }
}
@Controller
public class RegisterController {
	...
    @PostMapping("/register/step3")
    public String handleStep3(@Valid RegisterRequest regReq, Errors errors) {
    	if(errors.hasErrors())
        	...
     }
 }

- @Valid 애노테이션을 붙이면 메서드 실행 전 글로벌 범위의 Validator가 해당 타입을 검증할 수 있는지 확인하고, 검증 수행 결과를 Errors에 저장합니다.

- 그렇기 때문에 위에서 사용했던 검증 메서드 validate()는 사용하지 않아도 됩니다.

 

- 컨트롤러 범위 Validator 설정

@InitBinder
protected void initBinder(WebDataBinder binder) {
	binder.setValidator(new RegisterRequestValidator());
}

- @InitBinder 애노테이션을 적용한 메서드는 WebDataBinder 타입 파라미터를 갖는데, setValidator() 메서드를 통해 컨트롤러 범위에 적용할 Validator를 설정할 수 있습니다.

 

  • Bean Validation을 이용한 값 검증 처리

- @Size , @NotBlank, @Email, @NotEmpty와 같은 Bean Validation을 사용하려면 OptionalValidatorFactoryBean클래스를 빈으로 등록해야합니다.

- @EnableWebMvc가 OptionalValidatorFactoryBean을 글로벌 범위 Validator로 등록해주기 때문에 @EnableWebMvc만 설정 클래스에 붙여주면 됩니다.

- 단, 설정 클래스에 별도로 설정한 Validator가 없어야 사용가능합니다.

public class RegisterRequest{

    @NotBlank
    @Email
    private String email;
    ...

- label.properties

NotBlank=필수 항목입니다. 공백 문자는 허용하지 않습니다.
NotEmpty=필수 항목입니다.
Size.password=암호 길이는 6자 이상이어야 합니다.
Email=올바른 이메일 주소를 입력해야 합니다.