스프링 프레임워크 핵심 기술 - Resource, Validation, 데이터 바인딩, SpEL

 

스프링 프레임워크 Resource, Validation, 데이터 바인딩, SpEL

지난 강의 정리

ApplicationContext는 단순히 BeanFactory의 기능만 하는 것이 아니고 ResourceLoader, ApplicationEventPublisher,MessageSource, Environment 등 여러 기능들이 있습니다.

Resource 추상화

특징
  • java.net.URL을 추상화 한 것
  • 스프링 내부에서 많이 사용하는 인터페이스
추상화 한 이유
  • 클래스패스 기준으로 리소스 읽어오는 기능 부재
  • ServletContext를 기준으로 상대 경로로 읽어오는 기능 부재
  • 새로운 핸들러를 등록하여 특별한 URL 접미사를 만들어 사용할 수는 있지만 구현이 복잡하고 편의성 메소드가 부족함
인터페이스
  • 상속 받은 인터페이스
  • 주요 메소드
    • getInputStream()
    • exists();
    • isOpen();
    • getDescription(); 전체 경로 포함한 파일 이름 또는 실제 URL
구현체
  • UrlResource: http, https, ftp, file, jar
  • ClassPathResource: 접두어 classpath:
  • FileSystemResource
  • ServletContextResource: 웹 애플리케이션 루트에서 상대 경로로 리소스를 찾는다.
리소스 읽어오기
  • Resource의 타입은 locaion 문자열과 ApplicationContext의 타입에 따라 결정 된다.

    • ClassPathXmlApplicationContext -> ClassPathResource

    • FileSystemXmlApplicationContext -> FileSystemResource

    • WebApplicationContext -> ServletContextResource

      리소스 이름만 적으면 ServletConntextResource로 리저빙이 된다.

  • **ApplicationContext의 타입에 상관없이 리소스 타입을 강제하려면 **java.net.URL 접두어(+ classpath:) 중 하나를 사용할 수 있다.

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

Validation 추상화

org.sprignframework.validation.Validator

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

스프링 부트 2.0.5 이상 버전을 사용할 때 스프링에서 빈으로 등록되어 있습니다.

  • LocalValidationFactoryBean

스프링 부트 2.3 이상 버전에서는 validator 의존성이 포함이 되어있지 않습니다. 의존성을 추가 해야 사용이 가능하도록 바뀌었습니다.

도메인 객체에 @NotNull, @Min, @Max 등의 어노테이션을 붙이고 Validator를 사용하면 error를 잡아 냅니다.

검증 방법
검증할 도메인 객체
public class Event {
	@Min(0)
	Integer id;
	
	@NotNull
	String title;

	public Integer getId() {
		return id;
	}

	public void setId(Integer 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("=== error code ===");
			System.out.println(Arrays.toString(e.getCodes()));
		});
	}
}

데이터 바인딩 추상화: PropertyEditor

DataBinder

기술적인 관점: 프로퍼티 값을 타겟 객체에 설정하는 기능

사용자 관점: 사용자 입력값을 애플리케이션 도메인 모델에 동적으로 변환해 넣어주는 기능

입력값은 대부분 문자열인데 그 값을 객체가 가지고 있는 int, long, boolean, date 등 Event, Book 같은 도메인 타입으로 변환해서 넣어주는 기능

PropertyEditor
  • 스프링 3.0 이전까지 DataBinder가 변환 작업을 사용하던 인터페이스
  • 쓰레드 세이프하지가 않습니다. ( 상태 정보를 저장하고 있어 싱글톤 빈으로 등록해서 사용하면 안됨)
  • Object와 String 간의 변환만 가능하여 제한적입니다.

스트링으로 들어온 것을 Event로 변환하는 과정입니다.

public class EventEditor extends PropertyEditorSupport {

    @Override
    public String getAsText() {
        Event event = (Event)getValue();
        return event.getId().toString();
    }

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(new Event(Integer.parseInt(text)));
    }
}

setValue는 상태정보를 가지고 있어 쓰레드 세이프티 하지 않으므로 빈으로 등록하여 사용하면 안됩니다. 다른 쓰레드와 공유해서 잘 못사용될 가능성이 있습니다. 위의 방식은 사용하기 번거로우며 스트링과 오브젝트간의 변환만 가능합니다.

@RestController
public class EventController {

    @InitBinder
    public void init(WebDataBinder webDataBinder){
        webDataBinder.registerCustomEditor(Event.class , new EventEditor());
    }

    @GetMapping("/event/{event}")
    public String getEvent(@PathVariable Event event) {
        System.out.println(event);
        return event.getId().toString();
    }
}

controller에서 InitBinder를 통해서 사용하는 방법을 추천합니다.

데이터 바인딩 추상화: Convertor와 Fomatter

public class EventConverter {

    public static class StringToEventConverter implements Converter<String, Event>{

        @Override
        public Event convert(String source) {
            return new Event(Integer.parseInt(source));
        }
    }

    public static class EventToStringConverter implements Converter<Event, String>{

        @Override
        public String convert(Event source) {
            return source.getId().toString();
        }
    }
}
Converter
  • S타입을 T 타입으로 변환할 수 있는 매우 일반적인 변환기
  • 상태 정보가 없으므로 쓰레드 세이프 합니다.
  • ConverterRegistry에 등록해 사용
implements Converter<S,T>
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new EventConverter.StringToEventConverter());
    }
}

WebConfig 환경 설정으로 registry 하여 사용합니다.

기본적인 Integer 등 컨버터로 등록되어 있으므로 모든 것을 만들지 않고 필요에 따라 만들어 사용합니다.

Formatter
  • PropertyEditor의 대체
  • Object 와 String간 변환을 담당
  • 문자열을 Locale에 따라 다국화하는 기능(optional)
  • 웹 설정으로 포매터 등록후 사용
public class EventFormatter implements Formatter<Event> {
    @Override
    public Event parse(String text, Locale locale) throws ParseException {
        return new Event(Integer.parseInt(text));
    }

    @Override
    public String print(Event object, Locale locale) {
        return object.getId().toString();
    }
}
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new EventFormatter());
    }
}
ConversionService

DefaultFormattingConversionService

실제 변환 작업을 이 인터페이스를 통해서 쓰레드-세이프하게 사용할 수 있습니다.

스프링 MVC, 빈 설정, SpEL에서 사용

스프링 부트에서는 WebConversionService가 @Component로 Converter, Formatter를 빈으로 등록을 하면 웹 설정을 하지 않더라도 자동으로 등록을 해줍니다.

Fomatter 사용을 추천…

SpEL (스프링 Expression Language)

스프링 EL이란?
  • 객체 그래프를 조회하고 조작하는 기능을 제공
  • Unified EL과 비슷하지만, 메소드 호출 지원, 문자열 템플릿 기능 제공
  • 스프링 프로젝트 전반에 걸쳐 사용할 수 있는 EL
문법
  • #{“표현식”}
  • ${“프로퍼티”}
  • #{${프로퍼티} + 1} 가능 / 반대는 X
사용하는 곳
  • @Value
  • @ConditionalOnExpression
  • 스프링 시큐리티
  • 스프링 데이터 @Query
  • Thymeleaf