시뻘건 개발 도전기

#11 : 서버의 필수 로직 [validation check] 본문

Framework/spring

#11 : 서버의 필수 로직 [validation check]

시뻘건볼때기 2020. 5. 8. 21:37
반응형

  나는 실제로 서버 개발자로서 한 직장에 다니고있다. 여러 서버를 만져보았지만 validation check(유효성 검사)를 하지 않는 서버는 단 하나도 없었다. client가 요청한 데이터를 server는 필히 유효성을 체크해야 하기 때문이다. 직장에서 가장 많이 들었던 이야기 중 하나는 이것이다.

 

"서버는 그 어떠한 것도 믿으면 안된다."

 

우린 서버에 요청이 들어오면 항상 하는 일 중 하나가 바로 유효성 검사라는 것이다. spring에서는 Controller에서 유효성 검사를 하게되는데, BindingResult라는 녀석을 제공해준다. 코드를 보자.

@RequestMapping("/")
public ModelAndView home(Member member, BindingResult bindingResult) { // validation 에러가 있는지 없는지 결과를 담아주는 녀석
	ModelAndView modelView = new ModelAndView();
	
	MemberValidation validation = new MemberValidation();
	validation.validationCheck(member, bindingResult);
	
	if(bindingResult.hasErrors()) {
		modelView.setViewName("error");
		modelView.addObject("error", bindingResult.getFieldError().getCode());
	} else {
		modelView.setViewName("home");
	}
	
	return modelView;
}

bindingResult는 유효성 검사에서 에러가 있었는지 없었는지 알 수 있는 녀석이다. Member 클래스의 유효성 검사를 할 memberValidation 클래스를 추가해준다.

 

public class MemberValidation {
	private static final Logger logger = LoggerFactory.getLogger(MemberValidation.class);
	
	public void validationCheck(Object object, Errors errors) {
		logger.debug("validationCheck start!!!");
		
		Member member = (Member) object;
		
		String id = member.getId();
		
		if(null == id || "".equals(id)) {
			logger.info("id field is required!!!");
			errors.rejectValue("id", "9999");
		}
		
	}
}

spring에서 제공해주는 Errors를 인자로 받고 있고 rejectValue를 호출하고 있다. 해당 메소드는 다음과 같은 녀석이다.

더보기

Register a field error for the specified field of the current object(respecting the current nested path, if any), using the given errordescription. 

The field name may be null or empty String to indicatethe current object itself rather than a field of it. This may resultin a corresponding field error within the nested object graph or aglobal error if the current object is the top object.

[ 참고 : https://docs.spring.io/spring/docs/ ] 본인이 사용하는 spring 버전에 맞는 docs 참고.

유효성 검사에서 오류 발생 시, 오류코드를 넣어주어 바인딩 해주면 상기 Controller코드와 같이 접근 할 수 있다.

modelView.addObject("error", bindingResult.getFieldError().getCode());

FieldError가 여러번 바인딩 될 수 있으므로, List로도 가지고 올 수 있으니 이 역시 docs를 참고하면 된다.

  각 클래스 별로 커스터마이징해서 validation check class를 생성하여 진행해도 되지만 한 가지 알아 두어야 할 점은, 가장 많이 사용되는 유효성 검사까지 spring에서 제공해준다는 사실!!! 그것이 궁금하면 ValidationUtils를 살펴보자. (참고로 spring 최신 버전 docs)

  간단해 보이지만 큰 프로젝트이거나 더 java답고, 실제 서비스가 가능할 정도로 코드를 작성하면 정말 머리가 아플정도로 힘들고 귀찮다...ㅎㅎㅎ

 

 

- 어노테이션으로 유효성 체크하기

  하이버네이트 라이브러리만 있으면 충분히 가능하다. pom.xml에 사용할 하이버네이트 버전을 dependency 걸어주고 우린 @Valid와 @InitBinder 어노테이션 만을 사용하면 된다. 코드를 보자.

@RequestMapping("/")
public ModelAndView home(@Valid Member member, BindingResult bindingResult) {
	ModelAndView modelView = new ModelAndView();
	
	if(bindingResult.hasErrors()) {
		modelView.setViewName("error");
		modelView.addObject("error", bindingResult.getFieldError().getCode());
	} else {
		modelView.setViewName("home");
	}
	
	return modelView;
}

@InitBinder
protected void initBinder(WebDataBinder webDataBinder) {
	webDataBinder.setValidator(new MemberValidation());
}

Controller의 커맨드 객체에 @Valid 어노테이션을 추가하면 spring은 @InitBinder 어노테이션으로 정의된 메소드를 찾아 실행한다.

 

public class MemberValidation implements Validator{
	private static final Logger logger = LoggerFactory.getLogger(MemberValidation.class);
	
	@Override
	public boolean supports(Class<?> clazz) {
		return Member.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		logger.debug("validationCheck start!!!");
		
		Member member = (Member) target;
		
		String id = member.getId();
		
		if(null == id || "".equals(id)) {
			logger.info("id field is required!!!");
			errors.rejectValue("id", "9999");
		}
		
	}
}

  우리가 만든 MemberValidation 클래스는 Validator 인터페이스를 상속받아 supports와 validate를 오버라이드해야한다. validate 메소드에 우리가 사용할 유효성 검사 로직을 추가하면 spring이 바인딩된 저 validate 메소드를 수행하게 된다.

반응형
Comments