ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링 인 액션] Chapter 5 - 구성 속성 사용하기 :: 우리의 구성 속성 생성하기
    개발서적읽기/Spring in Action 제 5판 2020. 7. 31. 01:22



    스프링 빈에서는 구성 속성들을 어떻게 사용할까?


    스프링 빈에 구성 속성을 주입하기 위해 


    스프링 부트는 @ConfigurationProperties 애노테이션을 제공한다.


    그리고 어떤 스프링 빈이건 이 애노테이션이 지정되면


    해당 빈의 속성들이 스프링 환경의 속성으로부터 주입될 수 있다.


    @ConfigurationProperties가 어떻게 동작하는지 알아보기 위해


    다음의 ordersForUser 메서드를 OrderController에 추가하자.

    @GetMapping
    public String ordersForUser(@AuthenticationPrincipal User user, Model model) {
    model.addAttribute("orders", orderRepo.findByUserOrderByPlacedAtDesc(user));

    return "orderList";
    }
    package tacos.data;
    /*
    * @USER JungHyun
    * @DATE 2020-07-29
    * @DESCRIPTION
    */

    import org.springframework.data.repository.CrudRepository;
    import tacos.Order;
    import tacos.User;

    import java.util.List;

    public interface OrderRepository extends CrudRepository<Order, Long> {
    List<Object> findByUserOrderByPlacedAtDesc(User user);
    }

    OrderByPlacedAtDesc에서 OrderBy는 결과를 정렬하는 기준이 되는 속성을 나타낸다.


    그리고 제일 끝의 Desc는 결과를 내림차순으로 정렬되게 한다.




    orderForUser()는 사용자가 여러 번 주문을 했을 때 유용하게 사용할 수 있다.


    그러나 최근 몇 개의 주문이 브라우저에 나타나는 것은 유용하지만,


    수백 개의 주문을 여러 페이지에 걸쳐 봐야 한다면 피곤할 것이다.


    예를 들어, 가장 최근의 20개 주문만 나타나도록 조회 주문 수를 제한하고 싶다고 해보자.

    @GetMapping
    public String ordersForUser(@AuthenticationPrincipal User user, Model model) {
    Pageable pageable = PageRequest.of(0, 20);

    model.addAttribute("orders", orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));

    return "orderList";
    }
    package tacos.data;
    /*
    * @USER JungHyun
    * @DATE 2020-07-29
    * @DESCRIPTION
    */

    import org.springframework.data.domain.Pageable;
    import org.springframework.data.repository.CrudRepository;
    import tacos.Order;
    import tacos.User;

    import java.util.List;

    public interface OrderRepository extends CrudRepository<Order, Long> {
    List<Object> findByUserOrderByPlacedAtDesc(User user, Pageable pageable);
    }

    Pageable 객체를 인자로 받기 위해 findByUserOrderByPlacedAtDesc()의 시크니처를 변경하였다.


    스프링 데이터의 Pageable 인터페이스를 사용하면 


    페이지 번호와 크기로 결과의 일부분을 선택할 수 있다.


    한편, 페이지 크기를 하드코딩했다는 점이 아쉽다.


    만일 한 페이지에 20개가 너무 많아서 향후에 10개로 줄인다면?


    현재는 페이지 크기가 하드코딩되어 있으므로 애플리케이션을 다시 빌드 및 배포해야 할 것이다.


    이때는 커스텀 구성 속성을 사용해서 페이지 크기를 설정할 수 있다.


    우선 pageSize라는 새로운 속성을 OrderController에 추가해야 한다.


    그다음에 @ConfigurationProperties 애노테이션을 OrderController에 지정하면 된다.

    package tacos.web;
    /*
    * @USER JungHyun
    * @DATE 2020-07-04
    * @DESCRIPTION
    */

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Pageable;
    import org.springframework.security.core.annotation.AuthenticationPrincipal;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.validation.Errors;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.bind.support.SessionStatus;
    import tacos.Order;
    import tacos.User;
    import tacos.data.OrderRepository;

    import javax.validation.Valid;

    @Slf4j
    @Controller
    @RequestMapping("/orders")
    @SessionAttributes("order")
    @ConfigurationProperties(prefix = "taco.orders")
    public class OrderController {

    private int pageSize = 20;

    private OrderRepository orderRepo;

    public void setPageSize(int pageSize) {
    this.pageSize = pageSize;
    }

    public OrderController(OrderRepository orderRepo) {
    this.orderRepo = orderRepo;
    }

    @GetMapping("/current")
    public String orderForm(@AuthenticationPrincipal User user, @ModelAttribute Order order) {
    if (order.getDeliveryName() == null) {
    order.setDeliveryName(user.getFullname());
    }
    if (order.getDeliveryStreet() == null) {
    order.setDeliveryStreet(user.getStreet());
    }
    if (order.getDeliveryCity() == null) {
    order.setDeliveryCity(user.getState());
    }
    if (order.getDeliveryZip() == null) {
    order.setDeliveryZip(user.getZip());
    }
    return "orderForm";
    }


    @PostMapping
    public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus, @AuthenticationPrincipal User user) {
    if (errors.hasErrors()) {
    return "orderForm";
    }
    order.setUser(user);
    orderRepo.save(order);
    sessionStatus.setComplete();
    return "redirect:/";
    }

    @GetMapping
    public String ordersForUser(@AuthenticationPrincipal User user, Model model) {
    Pageable pageable = PageRequest.of(0, pageSize);

    model.addAttribute("orders", orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));

    return "orderList";
    }
    }

    pageSize 구성 속성 값을 설정할 때는 taco.orders.pageSize라는 이름을 사용해야 한다.


    아래는 application.yml 파일에 taco.orders.pageSize값을 설정했다.

    spring:
    datasource:
    url: jdbc:mysql://localhost/tacocloud
    username: tacouser
    password: tacopassword

    greeting:
    welcome: You are using ${spring.application.name}

    taco:
    orders:
    pageSize: 10

    또는 애플리케이션을 프로덕션에서 사용 중에 빨리 변경해야 한다면


    다음과 같이 환경 변수에 설정해도 된다. 그러면 다시 빌드 및 배포하지 않아도 된다.


    export TACO_ORDERS_PAGESIZE = 10


    다음으로 구성 데이터를 속성 홀더에 설정하는 방법을 알아보자


    속성 홀더란?


    구성 속성 값을 관리하는 클래스




    ■구성 속성 홀더 정의하기


    @ConfigurationProperties가 반드시 컨트롤러나 특정 빈에만 사용될 수 있는 것은 아니다.


    @ConfigurationProperties는 구성 데이터의 홀더로 사용되는 빈에 지정되는 경우도 많다.


    이렇게 하면 컨트롤러와 이외의 다른 애플리케이션 클래스 외부에 구성 관련 정보를


    따로 유지할 수 있다. 또한 여러 빈에 공통적인 구성 속성을 쉽게 공유할 수 있다.


    OrderController의 pageSize 속성은 별개의 홀더 클래스로 추출할 수 있다.

    package tacos.web;
    /*
    * @USER JungHyun
    * @DATE 2020-07-31
    * @DESCRIPTION
    */

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;

    @Component
    @ConfigurationProperties(prefix = "taco.orders")
    @Data
    public class OrderProps {
    private int pageSize = 20;
    }

    @Component가 지정되었으므로, 스프링 컴포넌트 검색에서 OrderProps를


    자동으로 찾은 후 스프링 애플리케이션 컨텍스트의 빈으로 생성해 준다.


    이제 OrderController는 기존의 pageSize 속성을 제거하고 OrderProps 빈을 주입받는다.

    package tacos.web;
    /*
    * @USER JungHyun
    * @DATE 2020-07-04
    * @DESCRIPTION
    */

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Pageable;
    import org.springframework.security.core.annotation.AuthenticationPrincipal;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.validation.Errors;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.bind.support.SessionStatus;
    import tacos.Order;
    import tacos.User;
    import tacos.data.OrderRepository;

    import javax.validation.Valid;

    @Slf4j
    @Controller
    @RequestMapping("/orders")
    @SessionAttributes("order")
    public class OrderController {

    private OrderRepository orderRepo;

    private OrderProps props;

    public OrderController(OrderRepository orderRepo, OrderProps props) {
    this.orderRepo = orderRepo;
    this.props = props;
    }

    @GetMapping("/current")
    public String orderForm(@AuthenticationPrincipal User user, @ModelAttribute Order order) {
    if (order.getDeliveryName() == null) {
    order.setDeliveryName(user.getFullname());
    }
    if (order.getDeliveryStreet() == null) {
    order.setDeliveryStreet(user.getStreet());
    }
    if (order.getDeliveryCity() == null) {
    order.setDeliveryCity(user.getState());
    }
    if (order.getDeliveryZip() == null) {
    order.setDeliveryZip(user.getZip());
    }
    return "orderForm";
    }


    @PostMapping
    public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus, @AuthenticationPrincipal User user) {
    if (errors.hasErrors()) {
    return "orderForm";
    }
    order.setUser(user);
    orderRepo.save(order);
    sessionStatus.setComplete();
    return "redirect:/";
    }

    @GetMapping
    public String ordersForUser(@AuthenticationPrincipal User user, Model model) {
    Pageable pageable = PageRequest.of(0, props.getPageSize());

    model.addAttribute("orders", orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));

    return "orderList";
    }
    }

    이제는 OrderController가 직접 pageSize 구성 속성을 처리할 필요가 없다.


    OrderController는 더 깔끔해졌고 OrderProps 속성은 다른 빈에서 재사용될 수도 있다.


    OrderProps 속성을 다른 빈에서 사용하는 예를 생각해보자.


    여러 다른 빈에서 pageSize 속성을 사용하는데, 이 속성의 값이 5부터 25 사이인지


    검사하는 애노테이션을 적용하기로 해보자.


    OrderProps에 pageSize 속성을 추출했으므로 다음과 같이 OrderProps만 변경하면 된다.

    package tacos.web;
    /*
    * @USER JungHyun
    * @DATE 2020-07-31
    * @DESCRIPTION
    */

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    import org.springframework.validation.annotation.Validated;

    import javax.validation.constraints.Max;
    import javax.validation.constraints.Min;

    @Component
    @ConfigurationProperties(prefix = "taco.orders")
    @Data
    @Validated
    public class OrderProps {

    @Min(value = 5, message = "must be between 5 and 25")
    @Max(value = 25, message = "must be between 5 and 25")
    private int pageSize = 20;
    }


    댓글

Designed by Tistory.