Resource와 Validator를 사용해보자!
기존에 있는 URL 클래스는 http, https, ftp 등을 지원했지만, classpath로 가져오는 것을 지원하지 않았다. URL 클래스로 자원을 가져오는 것과 classpath로 자원을 가져오는 것은 동일하다고 판단했고, 하나로 통일하고자 Resource 클래스가 만들어지게 되었다.
Resource의 구현체로는 정말 많이 있으나 대표적으로 UrlResource, ClassPathResource, FileSystemResource, ServletContextResource
가 있다.
Resource를 읽어올 때 Resource의 타입은 ApplicationContext 타입에 따라 결정된다. 따라서 명시적으로 표현할 수 있도록 'classpath'나 'file' 접두어를 적어 강제하는 방법을 권장한다고 한다.
예를 들어, ApplicationContext가 ClassPathXmlApplicationContext라면, ClassPathResource로 Resolving하여 가져오게 된다는 의미다.
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext ctx;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(ctx.getClass()); // WebApplicationContext 중 하나가 출력
Resource resource = ctx.getResource("classpath:application.properties");
System.out.println(resource.getClass()); // classpath 접두어를 사용했으니 ClassPathResource가 출력
}
}
Validator는 애플리케이션에서 사용하는 객체 검증용 인터페이스다. 사용방법은 생각보다 간단하다. Validator 인터페이스에서 우리가 구현해야하는 메서드는 단 2개다.
public interface Validator {
boolean supports(Class<?> clazz); // 어떤 타입의 객체를 검증할 때 사용할 것인지 결정
void validate(Object target, Errors errors); // 실제 검증 로직을 이 안에서 구현 (ValidationUtils 사용하면 편리)
}
예시를 살펴보자.
public class Event {
int id;
String title;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
public class EventValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Event.class.equals(clazz); // Event 타입인지 확인
}
@Override
public void validate(Object target, Errors errors) {
// ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "notempty", "Empty title is not allowed");
Event event = (Event) target;
if(event.getTitle() == null) {
errors.rejectValue("title", "notempty", "Empty title is not allowed");
}
}
}
@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");
eventValidator.validate(event, errors);
System.out.println(errors.hasErrors());
errors.getAllErrors().forEach(e -> {
System.out.println("errors------------------------------");
Arrays.stream(e.getCodes()).forEach(System.out::println);
System.out.println(e.getDefaultMessage());
});
}
}
조금 더 개선하자면, 스프링 부트 2.0.5 이상 버전을 사용한다면 LocalValidatorFactoryBean 빈이 자동으로 등록되기 때문에 어노테이션 기반으로 더 깔끔하게 처리할 수 있다.
public class Event {
int id;
@NotEmpty
String title;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
Validator validator;
@Override
public void run(ApplicationArguments args) throws Exception {
Event event = new Event();
Errors errors = new BeanPropertyBindingResult(event, "event");
validator.validate(event, errors);
System.out.println(errors.hasErrors());
errors.getAllErrors().forEach(e -> {
System.out.println("errors------------------------------");
Arrays.stream(e.getCodes()).forEach(System.out::println);
System.out.println(e.getDefaultMessage());
});
}
}