목표

  • UTC, GMT, Epoch Time에 대한 이해

UTC, GMT

그리니치 평균시(Greenwich Mean Time, GMT)는 런던을 기점, 웰링턴을 종점으로 하는 협정 세계시의 빠른시간이다.

세계협정시(Coordinated Universal Time, UTC)와 혼용해서 쓰이므로 같다고 생각하면 된다.

(세부적으로는 차이가 있지만 사용하는데 있어서는 같다고 이해하자.)

 

Epoch Time

1970년 01월 01일 자정(UTC/GMT) 이후로 초단위로 지난시간을 나타낸다.

Epoch Time과 같은 의미의 단어로 Unix Epoch, Unix Time, POSIX Time, Unix Timestamp 등이 있다.


보통 프로그래밍시에 시간 측정을 위해서 사용되는 System.currentTimeMillis()가 Epoch Time을 반환하며, 반환값은 밀리세컨드 단위로 반환되므로 1000을 나눠줘야 초 단위가 된다.

long epoch = System.currentTimeMillis()/1000;

참고 사이트 : https://www.epochconverter.com/

'개발 > 기타' 카테고리의 다른 글

LastModified 헤더를 이용한 파일변경 체크  (0) 2020.12.06
Maven 기본  (0) 2019.11.12
Ajax에 관하여  (0) 2018.12.13
META-INF 디렉터리에 대하여  (1) 2018.04.12
TCP/IP  (0) 2017.08.01

스프링 도큐먼트 읽고 정리해보기 / Version 5.1.9.RELEASE


3. Validation, Data Binding, and Type Conversion

스프링에서 제공하는 검증, 데이터 바인딩, 타입변환에 대해 알아본다.

 

3.1 Validation by Using Spring's Validator Interface

스프링은 객체에 대한 검증을 위해 추상화한 Validator 인터페이스를 제공한다.
org.springframework.validation.Validator 인터페이스는 다음 두 가지 메서드를 가진다.

  • supports(Class): Validator가 해당 Class를 처리할 수 있는지 boolean을 반환한다.
  • validate(Object, org.springframework.validation.Errors): Object에 대한 검증을 실시하며 결과를 Errors 객체에 담는다.

가장 Low-Level의 객체에 대한 Validator 사용법은 아래를 참고한다. 검증을 위한 helper 클래스로써 ValidationUtils을 사용한다. ValidationUtils에 대한 자세한 사용법은 문서를 참고하며 간단한 검증 및 결과를 Errors객체에 담는 용도로 사용한다.

// 검증대상 클래스
public class Person {
    private String name;
    private int age;
    // the usual getters and setters...
}
// Validator를 구현하는 클래스
public class PersonValidator implements Validator {
    /**
     * This Validator validates *only* Person instances
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }

    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too.darn.old");
        }
    }
}
// 검증 code snippet
TestPerson maruta = new TestPerson("검증대상", 200); // 검증 오류 대상
Errors errors = new BeanPropertyBindingResult(maruta, "testperson"); // Errors 객체 초기화
validator.validate(maruta, errors); // 검증 실행
if(errors.hasErrors()){ // 검증 오류가 있으면
    for(ObjectError error : errors.getAllErrors()){
        System.out.println("error : " + Arrays.toString(error.getCodes()));
    }
}

위의 예시처럼 커스텀 Validator를 만들어서 사용해도 되지만, 공통적으로 사용할 수 있는 검증을 모아서 제공되는 Validator가 있다. 예를들면 문자열의 길이 체크나, 숫자의 최대값/최소값 체크같은 기본적인 검증을 할 대상 클래스의 인스턴스 변수에 애노테이션(@MIN, @MAX, @SIZE 등)으로 마킹한 후 스프링부트 2.0.5 이상에서 자동등록되는 LocalValidatorFactoryBean에 의해 검증하는 것이다.

자세한 내용은 링크를 참고하자.

 

3.2. Resolving Codes to Error Messages

위 검증의 실행결과로 age에 대한 에러코드는 소스코드에서 설정한 'too.darn.old'를 포함하여 추가적으로 자동 설정된다.

too.darn.old.testperson.age, too.darn.old.age, too.darn.old.int, too.darn.old 

이는 MessageCodesResolver(DefaultMessageCodesResolver)가 객체명이나 프로퍼티명, 타입을 이용해 추가해준 것이며, 에러메세지 출력의 편의성을 위해서 동작한다. 개발자가 설정한 에러코드와 자동으로 추가된 에러코드가 프로퍼티 소스로부터 메세지를 가져오기 위해 사용된다.

 

3.3 Bean Manipulation and the BeanWrapper

  • BeanWrapper
    org.springframework.beans 패키지는 JavaBean 표준에 부합(adhere)하다. JavaBean은 인자가 없는 생성자와 naming convention을 따르는 클래스를 말한다.
    org.springframework.beans 패키지에서 가장 중요한 인터페이스 중 하나는 BeanWrapper이다. 이 인터페이스는 직접적으로 어플리케이션 코드에서 호출되지는 않지만 DataBinder와 BeanFactory가 빈에 프로퍼티를 설정할 때 사용된다.

  • PropertyEditor
    어플리케이션에서 대부분의 값을 문자열로 표현한다. 예를 들어, 빈 메타 설정에 대한 XML파일을 작성할 때 int 타입이나 빈의 타입 등을 모두 문자열로 표현한다. 이러한 문자열을 적절한 타입(Object)으로 변경하는 것이 PropertyEditor이다.
    PropertyEditor는 java.beans 패키지에 포함되어 오래전부터 존재했다.
    스프링에서는 여러 종류의 PropertyEditor의 구현체를 제공한다. org.springframework.beans.propertyeditors 패키지 이하에 위치하며, 대부분은 BeanWrapper의 구현체인 BeanWrapperImpl에 등록되어 사용된다. 자세한 리스트는 링크를 참고한다.
    커스터마이징한 PropertyEditor를 구현하여 사용할 수도 있지만 Thread-Safe하지 않으므로 싱글톤 빈으로 등록해서 사용하는 것은 지양해야 한다.(Scope를 Thread로 지정하여 사용하는 것도 가능하다.)

      Custom PropertyEditor구현 - ExoticType 클래스에 String 값을 바인딩한다.

public class ExoticTypeEditor extends PropertyEditorSupport {
    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}

     
      Controller에서 @InitBinder 메서드에 PropertyEditor를 등록하여 사용할 수 있다. 

@InitBinder
public void initBinder(WebDataBinder webDataBinder){
		webDataBinder.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
}

정리하면, 스프링은 문자열 값에서 객체로의 타입변경을 위해서 jdk에 존재하는 PropertyEditor를 구현한 클래스들을 제공하고 있다. 하지만, 스프링 3 이후 소개된 ConverterFormatterPropertyEditor의 단점을 보완하여 사용되고 있다.

 

3.4 Spring Type Conversion

스프링 3부터 소개된 core.convert 패키지는 타입변환 시스템을 지원한다. 이는 이전까지 사용되던 PropertyEditor의 단점을 보완하면서 대체할 수 있다.

PropertyEditor의 단점 : Stateful 하기 때문에 Thread-Safe 하지 않다. String - Object 사이의 변환만 지원한다.

 

  • Converter SPI
    한 객체를 다른 타입의 객체로 변환하기 위해 스프링에서 제공하는 인터페이스이다.
    Converter 생성을 위해서는 Converter<S, T> 인터페이스를 구현한다.
    S 타입의 객체를 인자로 입력받아서 T 타입으로 변환하여 반환하는 메서드 1개만 존재한다.
    PropertyEditor와 다르게 인자나 반환형에 대한 제한이 없고, Thread-Safe하다.(싱글톤 사용이 가능하다.)

package org.springframework.core.convert.converter;

public interface Converter<S, T> {
    T convert(S source);
}

      간단한 Converter들은 스프링이 이미 구현하여 제공하고 있다.

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {
    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}

경우에 따라 Converter 인터페이스에서 추가적인 설정이 가능한 인터페이스를 사용할 수 있다. 예를 들면, String 타입에서 Enum 타입의 객체로 변환하는 것처럼 클래스 체계에 대한 변환로직이 필요한 경우 ConverterFactory 인터페이스를 사용할 수 있고, 좀 더 유연한 변환을 원한다면 GenericConverter를 사용을 고려할 수 있다.

 

  • ConversionService API
    ConversionService는 런타임시에 타입변환 로직을 수행할 통합 API를 정의한다.

package org.springframework.core.convert;

public interface ConversionService {

    boolean canConvert(Class<?> sourceType, Class<?> targetType);

    <T> T convert(Object source, Class<T> targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

실제 타입변환은 등록된 Converter들에 의해 수행되며, 대부분의 ConversionService 구현체들은 Converter의 등록을 위해 ConverterRegistry 인터페이스를 구현하고있다. 즉, 용도에 맞춰 수동으로 컨버터를 선택하여 실행하지 않고 ConversionService 하나만 호출하여 변환한다.

ConversionService는 stateless하므로 초기화 된 후 Multi-Thread에서 공유가 가능하다. (PropertyEditor의 단점을 보완)

디폴트 ConversionService를 사용하기 위해서는 빈으로 등록하여 사용하고, 만일 추가적으로 커스터마이징한 컨버터를 등록하고 싶다면 property에 추가하여 사용한다. 타입 변환을 위해 자동으로 사용되지만 소스코드에서 주입받아 사용도 가능하다.

<bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

 

3.5 Spring Field Formatting

  • Formatter SPI
    Converter와 같이 타입변환을 위해 제공되는 인터페이스이다. 차이점은 문자열 타입변환만 제공한다는 점이다. Formatter는 Printer와 Parser 두 개의 인터페이스를 상속받고 있다. Printer와 Parser의 메서드는 인자로 Locale을 받아 국제화를 지원할 수 있다.
    메서드의 인자에서 알 수 있듯이 String 문자열을 Locale 정보에 맞게 객체화 시키거나 객체를 Locale 정보에 맞게 String 문자열로 변환하는 기능을 한다.

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}
public interface Printer<T> {
    String print(T fieldValue, Locale locale);
}
import java.text.ParseException;

public interface Parser<T> {
    T parse(String clientValue, Locale locale) throws ParseException;
}

주로 사용되는 용도는 org.springframework.format.number 패키지의 NumberStyleFormatter, CurrencyStyleFormatter 같이 숫자와 관련된 포맷팅이나 org.springframework.format.dateTime 패키지의 DateFormatter 같이 날짜시간에 관련된 포맷팅에 사용된다.

Formatter는 애노테이션에 바인딩되어 편리하게 사용할 수 있다.(Annotation-driven Fomatting)
바인딩하고자 하는 애노테이션으로 AnnotationFormattingFactory를 구현하고, 클래스의 프로퍼티에 애노테이션을 마킹하면 된다.

package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

    Set<Class<?>> getFieldTypes();

    Printer<?> getPrinter(A annotation, Class<?> fieldType);

    Parser<?> getParser(A annotation, Class<?> fieldType);
}
  • FormatterRegistry SPI
    FormatterRegistry 는 Formatter를 등록할 수 있도록 제공되는 인터페이스이면서 Converter를 등록할 수 있는 ConverterRegistry 인터페이스를 확장하고 있으므로 FormatterRegistry 하나로 Converter와 Formatter 등록이 가능하다.
    FormattingConversionService는 FormatterRegistry와 ConversionService 구현 및 확장하고 있는 클래스이다.

    즉 FormattingConversionService 빈 등록을 통해 Formatter와 Converter에 대한 등록과 사용이 가능하다.

    스프링 부트 웹 어플리케이션의 경우에는 DefualtFormattingConversionService을 상속하여 만든 WebConversionService를 빈으로 등록해주며, 이 때 Fomratter와 Converter빈을 찾아 자동으로 WebConversionService에 등록한다.

정리하면, 스프링에서는 타입변환을 위해 타입에 제한이 없는 Converter와 문자열, 국제화에 특화된 Formatter를 제공한다. 직접 호출보다는 자동으로 형변환이 수행될 떄 호출되며 DefualtFormattingConversionService을 통해 등록 및 접근할 수 있다. (하지만, Converter와 Formatter가 빈으로만 등록되어 있다면 자동등록 되므로 수동등록은 하지 않는다.)

 

3.6 Configuring a Global Date and Time Format

@DateTimeFormat을 사용하지 않고 전역적으로 날짜시간 타입의 변환을 위해 DefualtFormattingConversionService 설정할 수 있다.

@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}

3.7 Spring Validation

스프링3 에서부터 검증에 관한 다음 기능이 강화되었다.

1. JSR-303 Bean Validation API 가 지원된다.

  • JSR-303 Bean Validation API

빈 검증에 대해서 애노테이션으로 설정이 가능하다. JSR-303과 JSR-349의 정보가 필요하다면 Bean Validation website를 참고하고, Validator에 대한 정보는 Hibernate Validator를 참고한다.

public class PersonForm {

    @NotNull
    @Size(max=64)
    private String name;

    @Min(0)
    private int age;
}

 

  • LocalValidatorFactoryBean
    스프링은 javax.validation.ValidatorFactory 와 javax.validation.Validator 를 구현한 LocalValidatorFactoryBean을 제공하므로써 기본 Validator를 빈으로 등록할 수 있게 한다.
<bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

소스코드에서는 다음과 같이 주입받아서 사용하면 된다.

import org.springframework.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}

2. DataBinder 가 바인딩 뿐만 아니라 검증도 할 수 있다.

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();

binder.addValidatorsdataBinder.replaceValidators으로 validator를 추가하거나 변경이 가능하다.


3. Spring MVC 에서 @Controller 파라미터에 대한 선언적인 검증이 가능하다.

컨트롤러에서 @InitBinder 메서드에서 Validator를 추가하여 컨트롤러 인풋에 대한 검증이 가능하다.

스프링 도큐먼트 읽고 정리해보기 / Version 5.1.9.RELEASE


2. Resources

2.1 Introduction

리소스에 관한 자바의 표준은 java.net.URL 클래스이고 URL prefix에 따라서 다양한 핸들러들이 존재한다. 하지만 클래스패스로부터 리소스에 접근하는 표준화된 URL 구현체의 부재나 URL 인터페이스가 갖는 기능적인 부족함들이 존재한다.

 

2.2 The Resource Interface

스프링에서는 이러한 단점을 보완하여 Resource 접근에 대해 추상화한 org.springframework.core.io.Resource 인터페이스를 제공한다. (기존의 java.net.URL 을 감싼 형태이다.)

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isOpen();

    URL getURL() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();

}

InputStreamSource 인터페이스를 상속받아 리소스의 inputStream을 가져올 수 있는 InputStreamSoure.getInputStream() 메서드를 제공하며, exists() 등 추가 기능을 제공한다.

 

2.3 Built-in Resource Implementations

스프링은 추상화한 Resource 인터페이스의 구현체를 제공하고 있다.

  • UrlResource
    java.net.URL 을 감싼 형태로 URL을 이용하여 리소스에 접근한다. 파일시스템에 접근하기 위한 file: 이나 HTTP 프로토콜을 이용하여 접근하는 http: , FTP 프로토콜을 이용하는 ftp: 같이 접두어를 통해 접근방식을 정한다.
  • ClassPathResource
    classpath 상에 존재하는 리소스에 대한 접근을 위한 리소스 구현체이다. 명시적으로 ClassPathResource 생성자를 이용하여 생성할 수도 있고, 문자열에 classpath: 접두어로 생성될 수도 있다.
  • FileSystemResource
  • ServletContextResource 웹 어플리케이션 루트에서 상대경로로 리소스를 찾는 구현체이다.
  • InputStreamResource
  • ByteArrayResource

 

2.4 The ResourceLoader

ResourceLoader 인터페이스는 Resource 인스턴스를 리턴받기 위한 용도로 사용한다.

public interface ResourceLoader { 
    Resource getResource(String location);
}

ApplicationContext는 ResourceLoader를 구현하고 있으므로 ApplicationContext를 이용하여 리소스를 리턴받을 수 있다.

ApplicationContext extends ResourcePatternResolver
ResourcePatternResolver extends ResourceLoader

ApplicationContext도 인터페이스이기 때문에 어떤 ApplicationContext 구현체에게 getResource()를 호출하느냐에 따라 리턴되는 Resource의 구현체가 달라진다. 예를 들어, ClassPathXmlApplicationContext에서 getResource()를 호출한다면 ClassPathResource가 리턴된다. 이러한 식으로 ApplicationContext와 리턴되는 Resource의 타입은 맵핑된다.

 

FileSystemXmlApplicationContext- FileSystemResource

WebApplicationContext - ServletContextResource

 

만일 ApplicationContext 구현체의 타입에 상관없이 특정 타입의 Resource를 리턴받고 싶다면 getResource()에 대한 문자열 인자에 Prefix를 부여하면 된다.

// ClassPathResource 리턴
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

// FileSystemResource 리턴
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");

// UrlResource 리턴
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");

정리하자면, ResourceLoader로부터 리턴받는 Resource의 타입은 ApplicationContext의 구현체의 타입에 따라 다르며, 특정 타입의 Resource를 리턴받고자 할 때는 문자열 인자에 알맞은 prefix를 부여하면 된다.

 

2.5 The ResourceLoaderAware interface

ResourceLoaderAware는 콜백 인터페이스로 컨텍스트에 생성된 ResourceLoader 빈을 가져올 수 있다.

public interface ResourceLoaderAware {
    void setResourceLoader(ResourceLoader resourceLoader);
}

물론 ApplicationContext가 ResourceLoader를 구현하고 있기 때문에 ApplicationContextAware로 컨텍스트를 이용하여 리소스에 접근할 수 있지만, 추천되지는 않는다. 해당 빈을 가져오고자 하는 이유가 리소스의 접근이라면 그 용도로 설계된 ResourceLoader 타입의 빈을 가져오는 것이 맞다.

2.6 Resources as Dependencies

만일 특정 빈이 고정된(static) 리소스를 필요로 한다면 프로퍼티로써 ResourceLoader를 사용하지 않고 Resource 빈을 주입할 수 있다. 예시처럼 value속성을 문자열로 주지만 Resource로 빈 주입이 가능한 것은 ApplicationContext가 등록하고 사용하는 특별한 JavaBean인 PropertyEditor가 있기 때문이다.

<bean id="myBean" class="...">
    <property name="template1" value="some/resource/path/myTemplate.txt"/>
    <property name="template2" value="classpath:some/resource/path/myTemplate.txt">
    <property name="template3" value="file:///some/resource/path/myTemplate.txt"/>
</bean>

 

2.7 Application Contexts and Resource Paths

XML을 베이스로 한 ApplicationContext를 생성시 Resource를 활용하는 방법에 대해 알아본다.

  • ApplicationContext 생성시 Resource 인자

    ApplicationContext을 소스코드로 생성자를 이용하여 생성할 때 인자로 문자열(String)을 지정한다. 문자열이 해석되어 특정 타입의 Resource객체로 XML파일이 로드된다. 문자열에 prefix를 지정하지 않으면 ApplicationContext의 구현체 타입에 따라 Resource의 타입이 결정되어 로드한다.

// ClassPathResource로 빈 설정파일이 로드된다.
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

// FileSystemResource로 빈 설정파일이 로드된다.
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");

 

  • ApplicationContext 생성시 와일드카드(*) 사용

    와일드카드를 이용하여 Ant-Style Patterns을 활용할 수 있다.

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

      또는 classpath*: prefix를 활용할 수 있다.

// classpath에서 해당 이름의 모든 xml이 로드된다.
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

      Ant-Style과 classpath*: prefix를 혼용하여 사용할 수도 있다.

 

  • FileSystemResource를 사용할 때 주의점 FileSystemApplicationContext는 모든 FileSystemResource 인스턴스들에 대해서 슬래쉬(/)의 유무에 상관없이 상대경로를 사용하도록 강제한다. 즉 아래 예시는 동일하게 상대경로를 지정한다.

// 1번
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/context.xml");

// 2번
ApplicationContext ctx = new FileSystemXmlApplicationContext("/conf/context.xml");

      그러므로 만일 절대경로를 사용하고 싶으면 file:/// prefix를 활용하여 UrlResource를 사용하도록 한다.

// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");

// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");

+ Recent posts