본문 바로가기

더 나은 엔지니어가 되기 위해/지금은 안쓰는 자바

[스프링 프레임워크 핵심 기술] Resource / Validation 추상화

인프런에서 백기선님의 스프링 프레임워크 핵심 기술 을 공부하며 개인적으로 정리한 글입니다.

Resource 추상화

애플리케이션에서 사용하는 리소스 인터페이스.

Resource 라는 인터페이스로 추상화 되어있으며, 기본적으로 java.net.URL 을 스프링 프레임워크에 맞게 추상화 한 것이다.

1) 주요 메쏘드

기본적으로 다음과 같은 메쏘드가 구현되어 있다.

  • InputStream getInputStream()
  • boolean exists()
  • boolean isOpen()
  • String getDescription()

2) 구현체

Resource 를 구체적으로 구현한 클래스는 다음과 같다.

  • UrlResource
    • java.net.URL 을 참고.
    • http, https, ftp, file, jar 지원
  • ClassPathResource
    • 클래스패스를 통해 가져온 리소스
    • 지원하는 접두어가 classpath:
  • FileSystemResource
    • 파일 시스템 경로를 통해 가져온 리소스
  • ServletContextResource
    • 웹 애플리케이션 루트 경로를 통해 가져온 리소스

3) 리소스 읽어오기

실제로 리소스 구현체가 결정되는 것은 리소스 로더의 기능을 가지고 있는 ApplicationContext 의 타입에 따라 결정된다.

  • ClassPathXmlApplicationContext -> ClassPathResource
  • FileSystemXmlApplicationContext -> FileSystemResource
  • WebApplicationContext -> ServletContextResource

ApplicationContext 의 타입에 상관없이 리소스 타입을 결정하려면, java.net.URL 접두어 (+ classpath:) 중 하나를 사용할 수 있다.
예를 들면 다음과 같다.

  • classpath:me/whiteship/config.xml -> ClassPathResource
  • file:///some/resource/path/config.xml -> FileSystemResource

그리고 실제로 이렇게 접두어를 통해서 직접 리소스 타입을 명시해주는게 좋다고 한다.

Validation 추상화

1) 방법 1

애플리케이션에서 사용하는 객체 검증용 인터페이스

Validator 라는 인터페이스로 추상화 되어있으며, 객체 내 속성 값이 null 인지, 0 이상의 값을 가지는 지 등등을 확인한다.
Validator 를 구현하는 클래스는 직접 만들어야 하는데, 다음 예제를 보자.

먼저 검증 대상이 될 객체를 하나 만든다.

public class Event {

  Integer id;
  String title;

  ... getter and setter ...
}

검증을 수행할 객체를 Validator 로 구현한다.

import org.springframework.validation.Validator;

public class EventValidator implements Validator {

  @Override
  public boolean supports(Class<?> aClass) {
    return Event.class.equals(aClass);
  }

  @Override
  public void validate(Object o, Errors errors) {
    ValidationUtils.rejectIfEmptyOrWhitespace(
      errors,
      "title",
      "notEmpty",
      "Empty title is not allowed");
  }
}

validate 메쏘드안에서, ValidationUtils 의 메쏘드를 통해 어떤 검증할 지를 정한다.
위에서는 "공백인 경우, Reject" 하도록 검증했고, 검증할 속성은 title, 에러 코드는 "notEmpty", 기본 에러메시지는 "Empty title is not allowed" 로 주었다.
이러한 정보는 errors 에 담기게 된다.

이제 Runner 를 통해 실제 검증작업을 수행해보자.

@Component
public class AppRunner implements ApplicationRunner {

  @Override
  public void run(ApplicationArguments args) throws Exception {
    Event event = new Event();
    EventValidator eventValidator = new EventValidator();
    Errors errors = new BeanPropertyBindingResult(event, "event");

    // event 를 eventValidator 로 검증하고, 결과가 errors 에 담김.
    eventValidator.validate(event, errors);

    // 에러가 있는지 확인하여 출력.
    System.out.println(errors.hasErrors());

    errors.getAllErrors().forEach(e -> {
      // 각 에러에 대해 모든 에러 코드를 출력.
      System.out.println("===== error code =====");
      Arrays.stream(e.getCodes()).forEach(System.out::println);

      // default 메시지도 출력
      System.out.println(e.getDefaultMessage());
    });
  }
}

eventValidator.validate(event, errors) 와 같이 event 객체를 검증하면 된다. 이 과정 중에 생긴 에러코드들은 errors 에 담긴다.

Runner 를 실행하면 결과는 다음과 같다.

true
===== error code =====
notEmpty.event.title
notEmpty.title
notEmpty.java.lang.String
notEmpty
Empty title is not allowed

우리가 설정해준 에러 코드 notEmpty 말고도 기본적으로 제공되는 에러코드들도 같이 담겨져 있음을 알 수 있다.

2) 방법 2

스프링부트 2.0.5 이상 버전일 때 다음과 같은 어노테이션을 객체에 활용할 수 있다.

public class Event {

  Integer id;

  @NotEmpty
  String title;

  @Min(0)
  int limit;

  @Email
  String email;

  ... getter and setter ...
}

또한 스프링 부트가 자동설정으로 Validator 를 등록해주기 때문에 별도의 Validator 를 만들 필요 없이 @Autowired 로 주입받아 사용하면 된다.

@Component
public class AppRunner implements ApplicationRunner {

  // validator 는 스프링부트 통해 빈 등록이 되어져있고, 주입만 받으면 됨.
  @Autowired
  Validator validator;

  @Override
  public void run(ApplicationArguments args) throws Exception {
    // 일부러 에러를 내기 위해 검증에 어긋나는 값들로 설정.
    Event event = new Event();
    event.setLimit(-1);
    event.setEmail("aaaaa");

    Errors errors = new BeanPropertyBindingResult(event, "event");

    // event 를 validator 로 검증하고, 결과가 errors 에 담김.
    validator.validate(event, errors);

    // 에러가 있는지 확인하여 출력.
    System.out.println(errors.hasErrors());

    errors.getAllErrors().forEach(e -> {
      // 각 에러에 대해 모든 에러 코드를 출력.
      System.out.println("===== error code =====");
      Arrays.stream(e.getCodes()).forEach(System.out::println);

      // default 메시지도 출력
      System.out.println(e.getDefaultMessage());
    });
  }
}

Runner 를 실행하면 다음과 같은 결과가 나온다.

true
===== error code =====
NotEmpty.event.title
NotEmpty.title
NotEmpty.java.lang.String
NotEmpty
반드시 값이 존재하고 길이 혹은 크기가 0보다 커야 합니다.
===== error code =====
Email.event.email
Email.email
Email.java.lang.String
Email
이메일 주소가 유효하지 않습니다.
===== error code =====
Min.event.limit
Min.limit
Min.int
Min
반드시 0보다 같거나 커야 합니다.