HandlerMapping 와 HandlerAdapter

오늘 이야기할 내용은 spring의 HandlerMapping, HandlerAdapter 인터페이스에 대해 알아보도록 하자.
spring web, webflux 비슷한 아키텍처로 동작하기 때문에 예제는 그냥 web으로 설명하도록 하겠다.

먼저 이글을 읽고 오는 것을 추천하지만 굳이 보지 않아도 된다.

HandlerMapping

이 인터페이스는 해당 요청 정보를 기준으로 어떤 컨트롤러를 사용할 것 인가를 결정하는 인터페이스이다. 간단히 말해서 해당 url로 해당 컨트롤러(핸들러)를 선택하는 기준이 되는 인터페이스이다.
HandlerMapping 인터페이스는 여러 구현체를 가지고 있는데 한개씩 살펴보도록 하자.

BeanNameUrlHandlerMapping

이 구현체는 클래스명 그대로 빈명을 url로 사용한다는 매핑 전략이다. 아주 심플하게 사용할 수 있는 매핑전략이므로 설정 또한 간단하다.

@Bean("/accounts")
public AccountController accountController() {
    return new AccountController();
}

빈명에 슬러쉬(“/”) 가 존재하면 매핑 전략에 대상이 된다. 실제로 저 url이 요청할 url이랑 매핑된다는 이야기이다.
위와 같이 설정하면 /accounts는 저 controller로 매핑된다는 것을 의미한다.

@Component("/accounts")
@Controller("/accounts")
public class AccountController {

}

사실 위와 같이 @Component 이나 @Controller 어노테이션 둘중 하나를 이용해도 된다.

위와 같이 BeanNameUrlHandlerMapping 을 사용하려면 설정을 해줘야 한다. 직접적으로 설정을 해줘도 무방하지만 @EnableWebMvc 어노테이션을 선언해주면 자동으로 BeanNameUrlHandlerMapping 클래스가 자동으로 빈으로 등록된다. xml 경우엔 <mvc:annotation-driven/>을 사용하면 된다. (사실 xml은 정확하지 않다.)
참고로 Spring boot는 사실 딱히 설정할 필요는 없다.

SimpleUrlHandlerMapping

이것역시 간단한 설정만으로 사용할 수 있다. 이 매핑은 url과 controller를 map에 담아 매핑할 수 있는 전략이다.

@Bean
SimpleUrlHandlerMapping urlHandlerMapping() {
    SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
    simpleUrlHandlerMapping.setOrder(Ordered.LOWEST_PRECEDENCE - 2);

    Map<String, Object> mapping = new HashMap<>();
    mapping.put("/accounts", accountController());
    simpleUrlHandlerMapping.setUrlMap(mapping);
//or 
//  Properties properties = new Properties();
//  properties.put("/accounts", "accountController");
//  simpleUrlHandlerMapping.setMappings(properties);

    return simpleUrlHandlerMapping;
}

Map 과 Properties 아무거나 사용해도 상관없고 value에는 인스턴스를 넣어도 되고, 빈 명을 입력해도 무방하다.

여기에서 order를 준 이유는 resourceHandlerMapping(기본 값 : Ordered.LOWEST_PRECEDENCE – 1) 보다 먼저 urlHandlerMapping이 먼저 동작해야 된다. 만약 order를 설정 하지 않았다면 resourceHandlerMapping 에는 모든 요청이 걸리게 되어 있다. 그래서 원하는 매핑으로 동작하지 않는다.

RequestMappingHandlerMapping

사실 우리는 해당 매핑 전략을 대부분 이용하고 있다. @RequestMapping 어노테이션을 사용해서 개발한다면 해당매핑전략을 사용하고 있다는 뜻이다.

@Controller
@RequestMapping("/accounts")
public class AccountController {

}

위와 같이 손쉽게 @RequestMapping (@Get, @Post…) 어노테이션을 사용하면 된다. 설정방법은 BeanNameUrlHandlerMapping와 동일하게 EnableWebMvc 어노테이션을 선언하거나 xml 경우엔 <mvc:annotation-driven/>을 선언하면 된다. 이것 역시 spring boot 경우엔 아무 설정할 필요는 없다.

여기까진 매핑 전략에 대해서 알아봤다. 사실 여기까진 어떤 url에 어떤 컨트롤러를 결정하는 것까지 만이다. 실제 어떤 메서드를 호출하는지는 HandlerMapping 에선 알 수 없다.
그래서 우린 HandlerAdapter 인터페이스를 알아야 한다.

HandlerAdapter

이 인터페이스는 HandlerMapping에서 결정된 핸들러 정보로 해당 메서드를 직접 호출해 주는 스펙이다. 이 역시 여러개의 구현체가 존재한다. 한개씩 알아보도록 하자.

SimpleControllerHandlerAdapter

Controller 인터페이스를 사용하면 위의 어뎁터를 사용한다.

public class AccountController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ///... 
        return null;
    }
}

위와 같이 Controller 인터페이스를 구현하면 SimpleControllerHandlerAdapter 통해 해당 메서드가 호출 된다.

HttpRequestHandlerAdapter

이것 역시 HttpRequestHandler 인터페이스를 사용하면 해당 어뎁터를 사용한다.

public class AccountController implements HttpRequestHandler {

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response)  {
        ///...
    }
}

SimpleServletHandlerAdapter

클래스명 그대로 Servlet을 구현하면 된다. 아마도 기본적으로는 설정이 되어 있지 않다. 만약 사용을 원한다면 해당 어뎁터를 빈으로 등록해야 한다.

public class AccountController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
}

RequestMappingHandlerAdapter

이것은 아마도 우리가 가장많이 쓰는 어뎁터이다. 실제로는 RequestMappingHandlerMapping 과 대응되는 클래스이다. 만약 RequestMappingHandlerMapping 매핑 전략을 사용하고 싶다면 해당 어뎁터를 이용해야 한다.

해당 어뎁터에선 어떤 메서드를 호출해야 할지 결정해야하므로 이 클래스엔 여러 정보들이 담겨서 있다. 왜냐하면 위의처럼 특정한 인터페이스를 구현한게 아니라 리플렉션을 이용해서 메서드를 호출해야 하므로 해당 파라미터 타입과 리턴타입들의 정보를 알아야 하기 때문에 해당 정보들을 파싱해줄 HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler 인터페이스가 존재한다. 이외에도 더 많은 정보들을 가지고 있지만 여기선 중요한게 아니므로 생략 하겠다.

@Controller
@RequestMapping("/accounts")
public class AccountController {


    @GetMapping("/{id}")
    public String hello(@PathVariable Long id) {

       //...
    }
}

위의 클래스는 RequestMappingHandlerAdapter 통해 hello(Long) 이란 메서드가 호출된다.

HandlerMapping 와 HandlerAdapter 의 연관관계

위에서도 언급을 했지만 RequestMappingHandlerAdapterRequestMappingHandlerMapping은 연관관계가 있다. RequestMappingHandlerMapping 전략을 사용하고 싶다면 꼭 RequestMappingHandlerAdapter 어뎁터를 이용해야 한다.

하지만 다른 전략들은 연관이 있어도 되며 없어도 된다.

예를들어 다음과 같다.

@Bean("/accounts")
public AccountController accountController() {
    return new AccountController();
}

위와 같이 Mapping 전략은 BeanNameUrlHandlerMapping을 사용하면서 여러 어뎁터를 사용할 수 있다.

public class AccountController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //...   
        return ..;
    }
}


public class AccountController implements HttpRequestHandler {

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response)  {

        //...
    }
}


public class AccountController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

BeanNameUrlHandlerMapping 전략을 사용했지만 어뎁터는 아무 어뎁터나 사용할 수 있다. SimpleUrlHandlerMapping 전략도 마찬가지로 가능하다.

Spring에선 다양한 Mapping 전략과 다양한 Adapter를 제공함으로써 좀 더 유연하게 개발 할 수 있게 한다.

특별한 경우가 아니라면 직접적으로는 다른 전략들은 사용하지 않을 듯 싶다.
사실 간접적으로(?)는 우린 가끔 사용하고 있다.

class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

    }
}

ResourceHandlerRegistry, ViewControllerRegistry 는 내부적으로 SimpleUrlHandlerMapping 전략을 사용하고 있다.

오늘은 이렇게 HandlerMapping 와 HandlerAdapter 에 대해서 살펴봤다. spring web(서블릿)과 관련해서만 살펴봤지만 관심있으면 webflux도 살펴보는 것도 나쁘진 않아 보인다. 비슷한 아키텍처로 구현되어 있긴 하겠지만…

참고로 spring 5.2 부터는 RouterFunctionMapping 클래스가 추가 되었다. 리액티브에만 있던 매핑 전략이 서블릿에도 추가가 되었다.(아마 코틀린을 위한 거겠지?) 아직 릴리즈는 되지 않았지만 한번 살펴보는 것도 좋겠다.
관심있으면 여기를 스리슬쩍 참고하자.

Spring Data 여러 기능

오늘은 Spring Data의 몇가지 기능들을 살펴 볼 예정이다. 예전에 작성했던 Spring data common 기타 기능과는 별개로 유용하게 사용할 수 있는 것들을 정리해 보도록 하자. 물론 어떤 특정한 버전, 프로젝트들은 따로 명시를 해놓도록 하겠다.

query method

Spring data 프로젝트의 특징인 query method는 아주 유용한 기능이다. 물론 복잡한 쿼리에는 사용할 수 없지만 간단한 쿼리를 작성하는데는 더할 나위 없이 유용한 기능이다.
필자도 간단한 쿼리를 작성할 때는 자주 이용하고 있다. 잘 모르고 있을 수도 있는 기능들을 좀 더 살펴보자.

public interface PersonRepository extends CrudRepository<Person, String> {

    List<Person> findByName(String name);

}

만약 이름으로 select을 하고 있다면 대부분 위와 같이 작성을 할 것이다. find란 키워드와 by라는 키워드 spring data에서 정의한 키워드이다. 만약 두개의 키워드가 존재하지 않다면 쿼리메서드는 제대로 동작하지 않을 수 있다.

하지만 select 할때는 find 라는 키워드 말고도 여러 키워드들이 존재한다. 굳이 find 키워드를 사용할 필요 없이 다른 키워드를 사용해도 된다.

public interface PersonRepository extends CrudRepository<Person, String> {

    List<Person> findByName(String name);

    List<Person> queryByName(String name);

    List<Person> streamByName(String name);

    List<Person> getByName(String name);

    List<Person> readByName(String name);

}

find 라는 키워드 말고도 query, stream, get, read 등으로 대체 할 수 있다.
이외에도 카운터를 세는 count 존재 여부를 알려주는 exists 등이 있다.

또한 삭제쿼리 메서드도 존재하는데 remove, delete 두가지 키워드를 사용해서 삭제할 수 있다.

public interface PersonRepository extends CrudRepositor<Person, String> {

    void deleteByName(String name);

    void removeByName(String name);

}

위와 같이 사용해도 동일한 결과를 얻을 수 있다.

find 라는 키워드 말고도 여러 키워드가 있으니 다른 키워드도 사용해도 좋다.

사실 위와 같이 delete method 에는 @Modifying 어노테이션이 필요 없다. @Modifying 어노테이션은 @Query(update or delete) 어노테이션을 사용할 때만 작성하면 된다.

또한 한글, 중국어, 일어 등도 가능하다. 사실 그럴일은 거의거의 없지만 그냥 가능하다고 만 알고 있자.

//@Entity, //Document ...
public class Person {

  // ...

  private String 이름;

  //... getter setter

}

public interface PersonRepository extends CrudRepositor<Person, String> {

  List<Book> findBy이름(String name);

}

entityName (Data-JPA)

이것은 Jpa에 특화된 기능이다. 다른 data 하위 프로젝트엔 동작하지 않는다. @Query 어노테이션을 사용할 때 유용하게 사용될 수 있는 기능이다.

entityName 이라는 키워드를 통해 해당 엔티티를 조회, 저장, 삭제등을 할 수 있다.

@MappedSuperclass
public class Product {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    ....
}

// book, disc 

@NoRepositoryBean
public interface ProductRepository<T extends Product> extends JpaRepository<T, Long> {

    @Query("select p from #{#entityName} p where p.name = :name")
    List<T> findName(String name);
}


public interface BookRepository extends ProductRepository<Book> {

}

public interface DiscRepository extends ProductRepository<Disc> {

}

주로 사용하고 있는 곳으로는 위와 같이 공통적인 상위 인터페이스를 정의 한 후 공통된 쿼리들을 entityName을 이용하여 처리 할 수 있다.
@Query 어노테이션을 자주 사용하는 분들은 아주 유용한 기능인 듯 싶다.

Mongo (Data-Mongo)

Spring data mongo를 사용할 때 몇가지 유용한 기능이다.

Document(model) 를 작성할 때 굳이 @Document 어노테이션과 @Id를 선언하지 않아도 된다.

@Value
public class Person {

    ObjectId id;

    String name;

    int age;

}

public interface PersonRepository extends MongoRepository<Person, ObjectId> {

}

위와 같이 Person 모델에 @Document 를 붙이지 않아도 자동으로 Spring data가 만들어서 넣어 준다. 만약 @Document 없다면 클래스명(첫글자는 소문자)으로 collection이 만들어 진다.

@Id도 마찬가지로 id, _id 라는 필드가 존재하면 그것을 키로 잡아 저장을 한다.

만약 어노테이션이 많아 보기가 힘들다면 작성하지 않아도 되며 명시적인 어노테이션을 선호한다면 작성해도 무방하다.

단, 아래코드는 동작하지 않는다.


@Value public class Person { ObjectId id; String name; int age; } public interface PersonRepository extends CrudRepository<Person, String> { }

그 이유는 해당 엔티티가 어느 스토어를 사용하지는 알수 없어 빈으로 등록하지 못한다. 위의 경우엔 명시적으로 @Document 어노테이션을 사용하거나 직접적인 MongoRepository를 사용해야 한다.

다른 spring data 하위 프로젝트(JPA는 해당사항 없다.) 들은 어떻게 되어 있는지 확인해보지 않았다. 만약 다른 프로젝트도 이런 기능이 있을 수도, 없을 수도 있으니 잘 보고 적용해야 된다.

Spring data 쪽에는 엔티티를 위와 같이 불변으로 만들어도 된다. (사실.. 확인은 mongo, redis 만 했다. 이것 또한 JPA는 해당사항 없다.)

JPA는 명시된 스펙으로 작성을 해야 되기 때문에 @Entity, @Id, 기본생성자, 정의된 스펙에 맞게 작성해야 한다.

public class Person {

    private final ObjectId id;

    private final String name;

    private final int age;

    public Person(ObjectId id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
   // getter
}

or

@Value
public class Person {

    ObjectId id;

    String name;

    int age;

}

or

@Value(staticConstructor = "of")
public class Person {

    ObjectId id;

    String name;

    int age;

}

@Value 는 롬복에 있는 어노테이션이며 여기를 참고하며 되겠다. 또한 팩토리 메서드를 만들어 사용해도 된다. 다른 더 많은 기능이 있으나 주로 사용될만한 기능은 이정도 일듯 싶다.

Streamable

Spring data 2.0 에서 추가된 Streamable 인터페이스를 query method 를 사용할 때 리턴타입으로 사용할 수 있다.

public interface PersonRepository extends MongoRepository<Person, String> {

    Streamable<Person> findByName(String name);

}

해당 인터페이스를 사용하면 바로 map, filter를 사용할 수 있다.

 List<String> names = people.map(Person::getName)
                    .stream()
                    .collect(Collectors.toList());

사실 사용 Api를 보면 List를 사용하는 것과 동일하다. 어차피 Stream으로 다시 만들어 하기에 굳이 사용할 필요가 있나 싶기도 하다.

하지만 spring data 2.2 부터는 (현재기준으로 아직 릴리즈는 되지 않았다) 조금 더 간편하게 사용할 수 있다.

List<String> names = people.map(Person::getName)
                    .toList()

Set<String> names = people.map(Person::getName)
                    .toSet()

내부적인 코드는 동일하지만 사용하는 Api는 간단하게 사용할 수 있다.

또한 spring data 2.2 부터는 좀 더 커스텀한 Wrapper Type의 Streamable 만들어 사용할 수 있다.

//@RequiredArgsConstructor(staticName = "of")
@RequiredArgsConstructor
public class Persons implements Streamable<Person> {

    private final Streamable<Person> people;

    public int getAge() {
        return people.stream()
                .mapToInt(Person::getAge)
                .sum();
    }

    @Override
    public Iterator<Person> iterator() {
        return people.iterator();
    }
}


public interface PersonRepository extends MongoRepository<Person, String> {

    Persons readByName(String name);

}

Persons people = personRepository.readByName("wonwoo");
int totalAge = people.getTotalAge();

이렇게 Wrapper Type을 만들어 내부적으로 기능들을 추가할 수 있어 유용한 기능인 것 같다.

오늘은 이렇게 Spring data 쪽에 사용되는 유용한 기능들을 살펴봤다. 조금이라도 도움이 되었으면 좋겠다.

오랜만에 포스팅을 했다. 이런저런 일도 있고 사실 귀찮았던게 더 컷다. 일주일에 한번(적어도 이주일엔)은 꼭 쓰도록 노력해야겠다.

Spring 5.2 그리고 Spring boot 2.2

몇일전에 Spring 5.2.M1버전 과 Spring boot2.2.M2 버전이 릴리즈 되었다. 아직 해당 프로젝트들의 GA 버전이 나오기엔 시간이 많이 남아있지만 (대략 7월) 많이 변경되지 않을 것들만 모아서 살펴보도록 하자. (사실 필자가 아는 부분만이겠지만)

일단 Spring Framework 5.2 부터 알아보자.

Spring Framework 5.2

@Configuration

@Configuration 어노테이션의 속성이 추가 되었다. proxyBeanMethods 라는 속성이며 예전에 필자가 포스팅한 글중 Lite Mode라는 주제가 있었던 그 내용이 Spring 쪽에 들어갔다.
사용법은 아주 간단하다.

@Configuration(proxyBeanMethods = false)
public class TestConfiguration  {
  //... 
}

위와 같이 lite mode를 적용하고 싶으면 proxyBeanMethods 속성을 false로 설정하면 된다. 기본값은 true이다. 그럼 cglib을 사용하지 않는 상태에서 설정이 진행된다.
해당 설명은 위의 포스팅이 있기에 생략.

참고로 Spring boot 2.2의 AutoConfiguration들은 모두 @Configuration(proxyBeanMethods = false) 로 변경하였다.
또한 @SpringBootApplication 어노테이션에도 proxyBeanMethods 속성이 추가 되었다.

vavr의 Try와 @Transactional

io.vavr(구 javaslang) 의 Try를 @Transactional 어노테이션을 지원한다.

@Transactional
public Try<?> hello() {
    return Try.of(() -> accountRepository.save(new Account(1L, "test")))
        .filter(Account::isActive)
        .andThenTry((account) -> accountRepository.save(new Account(account.getId()))); // 예외
}

해당 라인에서 예외가 발생하면 롤백이 진행된다. 필자가 vavr를 잘 사용하지 못하는 관계로..
만약 vavr 프로젝트를 잘 사용한다면 유용하게 쓰일듯 싶다. Spring data 쪽에서는 vavr 프로젝트도 지원해주니 함께 사용하면 더 많은 지원을 받을 수 있을 듯 싶다.

import io.vavr.control.Try;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AccountRepository extends JpaRepository<Account, Long> {

    Try<Account> findByName(String name);

}

MergedAnnotations

Annotation을 처리하기 위해 새로운 API가 등장했다. 기존의 있던 AnnotationUtilsAnnotatedElementUtils 대신에 사용할 수 있는 API이다.
위의 AnnotationUtils, AnnotatedElementUtils API들도 내부적으로 MergedAnnotations 으로 변경하였다.
조금 더 살펴보고 해야겠지만 일단은 기본사용법만 알아두자. 언제 바뀔지 모르니..

MergedAnnotation<SpringBootApplication> springBootApplication = MergedAnnotations.from(Application.class)
    .get(SpringBootApplication.class);
Class<?>[] scanBasePackageClasses = springBootApplication.getClassArray("scanBasePackageClasses");
String[] scanBasePackages = springBootApplication.getStringArray("scanBasePackages");
boolean proxyBeanMethods = springBootApplication.getBoolean("proxyBeanMethods");

위와 같이 쉽게 사용할 수 있다. 더 많은 API 들과 속성들이 있긴 하지만 지금 당장은 살펴볼 필요가 없어서 이정도만 알아봤다. 릴리즈 되면 그때 다시 알아보도록 하자.
근데 사용할 일이 많이 없어서..

Spring boot 2.2

지금 부터는 Spring boot 쪽의 변화이다.

lazyInitialization

Spring boot 2.2 부터는 lazyInitialization를 모든 빈의 적용할 수 있게 되었다. 기존에는 @Lazy 어노테이션을 선언하여 게으른 빈은 설정하였다.

@Bean
@Lazy
public AccountService accountService() {
    return new AccountService();
}

위와 같이 설정하면 실제 사용될 시점에 빈이 생성된다.
조금 빠른 로딩을 하기 위해서 모든 빈에 게으르게 빈을 설정 할 수 있게 되었다.

spring.main.lazy-initialization=true

application.properties에 위와 같이 spring.main.lazy-initialization 속성을 true로 주면 모든 빈들이 Lazy 빈으로 생성 된다.
Spring boot 로딩 시간이 길다면 해당 속성을 이용해서 빠르게 로딩할 수 있다.

하지만 여기서 주의할점은 web 기준으로 http 첫 요청이 조금 길다는점. 첫 요청시 사용하는 빈들이 그 순간 만들어져 요청이 조금 느릴 수 있다.
두 번쨰로는 일반적으로는 빈을 찾을 수 없을 때 에러가 나지만 lazyInitialization을 설정하면 서버가 기동될때 에러를 찾을 수 없다. 빈을 사용할 때 그 때 빈을 찾으므로 런타임시 에러가 발생할 수 있으므로 조심해야 된다.

개발할 때만 설정하고 운영할 때는 설정 하지 않는것이 좋아 보인다.

만약 위의 설정을 해놓고 특정빈은 Lazy 로딩을 하고 싶지 않다면 다음과 같이 설정하면 된다.

@Bean
@Lazy(false)
public AccountService accountService() {
    return new AccountService();
}

이 기술(@Lazy)은 오래된 기술이다. 실제로는 대략 11년전에 개발된 기술이다. 그래서 우리도 간단한 구현만으로도 위와 비슷한 모든 빈에 Lazy 초기화를 할 수 있다.

@Bean
static BeanFactoryPostProcessor beanPostProcessor() {
    return beanFactory -> {
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        for(String beanName : beanDefinitionNames) {
            beanFactory.getBeanDefinition(beanName).setLazyInit(true);
        }
    };
}

Spring boot 2.2를 사용하지 않더라도 그 이하버전에서도 위와 같이 구현하면 게으른 초기화를 진행 할 수 있다.

@ConfigurationPropertiesScan

@ConfigurationPropertiesScan 어노테이션이 추가 되었다. 애노테이션명 그대로 ConfigurationProperties를 스캔하는 용도이다.

@ConfigurationProperties("foo")
public class FooProperties {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

위와 같은 Properties가 존재한다면 우리는 아래와 같이 설정하였다.

@Configuration
@EnableConfigurationProperties(FooProperties.class)
public class Application {

}

이제는 EnableConfigurationProperties을 사용하지 않고 @ConfigurationPropertiesScan 어노테이션을 사용하여 scan 할 수 있다.

@Configuration
@ConfigurationPropertiesScan("ml.wonwoo")
public class Application {

}

위와 같이 적용하면 ml.wonwoo 패키지 아래의 모든 프로퍼티들이 자동으로 등록이 된다.
Spring boot 2.2에서는 @SpringBootApplication 애노테이션에 메타 애노테이션으로 @ConfigurationPropertiesScan 애노테이션이 추가 되었으므로 추가적인 설정은 할필요 없다.

scan할 패지키를 지정하지 않는다면 선언된 애노테이션 이하의 패지키를 검색한다.

이제 @EnableConfigurationProperties의 사용처는 자동설정을 커스텀하게 만들때 사용하면 된다.

Immutable @ConfigurationProperties

불변의 Properties 를 지원한다. 기존에는 기본생성자와 setter가 존재 했어야 했지만 이제는 그럴 필요 없다.

@ConfigurationProperties("foo")
public class FooProperties {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

기존에는 위와 같이 javaBeans 스펙에 따라야 했지만 이제는 setter 또한 필요 없고 기본생성자 또한 필요 없다.

@ConfigurationProperties("foo")
public class FooProperties {

    private final String name;

    public FooProperties(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

만약 해당 프로퍼티에 디폴트값을 원한다면 @DefaultValue 어노테이션을 이용해서 기본값을 설정 할 수 있다.

@ConfigurationProperties("foo")
public class FooProperties {

    private final String name;

    public FooProperties(@DefaultValue("wonwoo") String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

이렇게 하면 프로퍼티의 값이 존재 하지 않아도 기본값으로 wonwoo가 설정이 된다.

오늘은 이렇게 Spring과 Spring boot에 변화에 대해서 알아봤다. 이 보다 많은 변화가 있었지만 개발자들이 직접적으로 사용하기에는 위의 내용으로도 충분해 보여 몇가지만 준비했다.
더 많은 내용은 해당 문서를 통해 알아보도록 하고, 위의 내용 또한 언제 바뀔지 모르니 현재는 그냥 참고만 하면 되겠다.
나중에 릴리즈 될때 다시 보자!