책읽기

[스프링 인 액션][Part-1 스프링 기초][Ch-2] 웹 애플리케이션 개발하기

pythaac 2021. 7. 20. 17:08
이 글은 "스프링 인 액션 제5판 (크레이그 윌즈 지음)"을 읽고 주관적으로 작성된 글입니다.

출처 : https://jpub.tistory.com/1040

※ 배우는 내용
1) 모델 데이터를 브라우저에서 보여주기
2) 폼 입력 처리하고 검사하기
3) 뷰 템플릿 라이브러리 선택하기

 

※ 요약

  • 스프링 MVC란?
    • 강력한 웹 프레임워크
    • 스프링 애플리케이션의 웹 프론트엔드 개발에 사용
  • 스프링 MVC 어노테이션
    • 스프링 MVC는 어노테이션 기반
      - @RequestMapping, @GetMapping, @PostMapping 등
      - 어노테이션으로 요청 처리 메서드를 선언
  • 요청 메서드의 뷰 이름 반환
    • 대부분 요청 처리 메서드는 마지막에 Thymeleaf 템플릿과 같은 논리 뷰 이름을 반환
    • 모델 데이터와 함께 해당 요청을 전달
  • 유효성 검사 컴포넌트 지원
    • 스프링 MVC는 유효성 검사 API 구현 컴포넌트로 유효성 검사 지원
    • 자바 빈 유효성 검사 API, Hibernate Validator 등
  • 뷰 컨트롤러 사용
    • 모델 데이터가 없거나 처리가 필요없는 HTTP GET 요청 처리시 사용 가능
  • 다양한 뷰 템플릿
    • Thymeleaf에 추가하여 다양한 뷰 템플릿 지원
    • FreMarker, Groovy Templates, Mustache 등

 

1. 정보 보여주기

  • 컨트롤러
    • 데이터를 가져오고 처리하는 역할

    • 브라우저에 보여주는 데이터를 HTML로 나타내는 것
  • 생성할 컴포넌트의 기낭
    1. 타코 식자재의 속성을 정의하는 도메인 클래스
    2. 식자재 정보를 뷰에 전달하는 스프링 MVC 컨트롤러 클래스
    3. 식자재의 내역을 사용자의 브라우저에 보여주는 뷰 템플릿

1) 도메인 설정하기

  • 어플리케이션의 도메인
    • 해당 어플리케이션의 이해에 필요한 개념을 다루는 영역
    • 이 어플리케이션의 도메인은 다음 객체를 포함
      1. 타코 디자인
      2. 식자재
      3. 고객
      4. 고객의 타코 주문
  • Lombok
    • 런타임에 getter, setter 외에 equals(), toString(), hashCode() 등 유용한 메서드를 자동으로 생성
      - 소스코드의 분량을 줄일 수 있음
      - 메서드들이 코드에 추가되지는 않음
      - Intellij 기준, ctrl + f12로 확인 가능
    • 예제의 클래스 Ingredient
      - final 속성들을 초기화하는 생성자가 없음
      - getter, setter 등등의 메서드가 없음
    • @Data
      - 속성들이 getter, setter 생성
    • @RequiredArgsConstructor
      - final 속성 초기화 생성자 생성

2) 컨트롤러 클래스 생성하기

  • 컨트롤러의 역할
    • HTTP 요청을 처리
    • 브라우저에 보여줄 HTML을 뷰에 요청
    • REST 형태의 응답 몸체에 직접 데이터를 추가
  • 이 어플리케이션에서 정의하는 컨트롤러의 기능
    • /design 요청 경로의 HTTP GET 요청을 처리
    • 식자재 내역 생성
    • 식자제 데이터의 HTML 작성을 뷰 템플릿에 요청 및 작성된 HTML을 웹 브라우저에 전송
  • @Slf4j
    • 자바에서 사용하는 SLF4J Logger 생성
    • Simple Loggin Facade
  • @Controller
    • 해당 클래스를 컨트롤러로 식별하게하여, 컴포넌트 검색이 필요함을 나타냄
    • 즉, 스프링 어플리케이션 컨텍스트에 해당 클래스의 인스턴스를 bean으로 추가
  • @RequestMapping
    • 클래스 수준에서, 해당 컨트롤러가 처리하는 요청의 종류를 나타냄
      - @RequestMapping("/design")
  • @GetMapping
    • 메서드 수준에서, HTTP GET 요청이 수신되면 해당 메서드가 호출됨을 나타냄
    • @RequestMapping은 특정 HTTP 요청을 지정하는(PostMapping, PutMapping 등) 어노테이션을 모두 선언함
    • 따라서 가급적 특화된 것을 사용하는 것이 좋음
  • Model 객체
    • 컨트롤러와 뷰 사이에서 데이터를 운반하는 객체
    • Model 객체의 속성(attribute)에 있는 데이터는 뷰가 알 수 있는 servlet 요청 속성들로 복사됨

1) 뷰 디자인하기

  • Thymeleaf
    • 빌드 명세에 dependency로 추가하면
      - 스프링 부트의 autoconfiguration에서 런타임에 classpath의 Thymeleaf를 찾아 bean을 자동 생성
    • 요청 데이터를 나타내는 요소 속성을 추가로 갖는 HTML
  • 뷰 라이브러리
    • 어떤 웹 프레임워크와도 사용 가능하도록 설계
    • 따라서, 스프링의 추상화 모델을 모르고, 컨트롤러가 데이터를 넣는 Model 대신, servlet 요청 속성을 사용
  • th:text
    • 교체를 수행
  • ${ }
    • 요청 속성의 값을 사용
  • th:each
    • 컬렉션을 반복 처리
    • 해당 컬렉션의 각 요소를 하나씩 HTML로 나타냄
  • @{ }
    • 참조되는 정적 콘텐츠
      - 로고 이미지, 스타일시트 위치

 

2. 폼 제출 처리하기

  • 예제의 <form>에 action 속성이 없음 -> button을 누를 시
    • <form method="POST" th:object="${taco}">
    • 브라우저가 form에 데이터를 모음
    • GET 요청과 같은 경로(/design)로 HTTP POST 요청을 전송
  • @PostMapping 메서드 (processDesign())
    • 즉, /design 경로의 controller에 HTTP POST를 처리하는 메서드를 추가해야함
    • 이 때, processDesign 메서드의 인자로 전달되는 Taco 객체의 속성과 바인딩
      <input name="ingredients" type="checkbox"
      th:value="${ingredient.id}" />
      <span th:text="${ingredient.name}">INGREDIENT</span><br />
    • checkbox 여러 요소들은 모두 ingredients라는 이름을 가지고, 텍스트 입력 요소의 이름은 name
    • 이 필드들은 Taco 클래스의 ingredients / name 속성 값과 바인딩
  • 리디렉션(Redirection) 
    • 그런데, 이 메서드의 반환값에 redirect가 붙음
      - "redirect:/orders/current"
    • 이는 해당 메서드의 실행이 끝난 후 사용자의 브라우저가 /orders/current 상대 경로로 재접속됨을 나타냄
    • 즉, /orders/current 경로의 요청을 처리할 컨트롤러가 필요
  • 리디렉션시 GET/POST
    • [???] POST요청에 대한 처리 후, redirect의 경로에 대한 처리는 GET으로 받는 이유?
  • <form>에 action이 있는 경우
    • /orders 경로로 제출하도록 지정
      <form method="POST" th:action="@{/orders}" th:object="${order}">

 

3. 폼 입력 유효성 검사하기

1) 유효성 검사 규칙 선언하기

  • Hibernate 컴포넌트
    • 입력 데이터에 대한 유효성을 검사해야 함
    • 스프링은 자바의 빈 유효성 검사(bean validation) API를 지원(구현)
      - 유효성 검사 규칙을 쉽게 선언
    • 이 API를 구현한 Hibernate 컴포넌트는 스프링 부트 웹 스타터 의존성으로 자동 추가
  • 유효성 검사 적용 방법
    1. 검사할 클래스에 검사 규칙 선언
    2. 검사를 하는 컨트롤러 메서드에 검사 구행 지정
    3. 검사 에러를 보여주도록 form 뷰 수정
  • 검사 클래스에 유효성 검사 규칙 선언
    • 예제에서는 Taco, Order 클래스
    • @NotNull : null인지 확인
    • @Size : min으로 최소 개수 지정 (String, List 상관없이 사용)
    • @NotBlank : 입력을 하지 않은 필드만 확인
    • @CreaditCardNumber : Luhn(룬) 알고리즘 검사로 신용카드 번호인지만 확인
    • @Pattern : 정규 표현식 확인
    • @Digits : 숫자 검사
      @NotNull
      @Size(min=5, message="Name must be at least 5 characters long")
      private String name;

      @Size(min=1, message="You must choose at least 1 ingredient")
      private List<String> ingredients;
  • 유효성 검사 결과
    • message
      - 유효성 규칙이 충족하지 않으면 보여줄 메세지 정의

2) 폼과 바인딩될 때 유효성 검사 수행하기

  • 검사 컨트롤러 메서드에서 검사 수행
    • 예제에서는 DesignTacoController의 processDesign(), OrderController의 processOrder()
    • @Valid : 해당 객체의 유효성 검사를 수행
      public String processDesign(@Valid Taco design, Errors errors) {
      if (errors.hasErrors()) {
      return "design";
      }
    • 제출된 form 데이터와 Taco 객체가 바인딩 된 후, 해당 메서드의 코드가 실행되기 전에 검사
    • hasErrors() 메서드로 검사
    • error가 있는 경우, design을 반환하여 다시 form을 보이게 함

3) 유효성 검사 에러 보여주기

  • Thymeleaf
    • fields와 th:errors 속성으로 Errors 객체의 편리한 사용 방법 제공
      <input type="text" th:field="*{name}" />
      <span
      class="validationError" th:if="${#fields.hasErrors('name')}"
      th:errors="*{name}">Name Error</span>
      <br />
      <button>Submit your taco</button>

    • class
      - 사용자의 주의를 끌기 위한 에러의 명칭을 지정
    • th:if
      - <span>을 보여줄지 말지 결정
      - fields.hasErros()를 통해 에러 확인
    • th:errors
      - 사전에 정의된 메시지(Name Error)를 검사 에러 메시지로 교체

 

4. 뷰 컨트롤러로 작업하기

  • 지금까지 프로그래밍 패턴
    • @Controller
      - 스프링 컴포넌트 검색으로 스프링 어플리케이션 컨텍스트에 bean을 생성하기 위해 등록
    • @RequestMapping
      - 클래스가 처리하는 요청의 패턴을 정의
    • @PostMapping
      - 메서드가 어떤 종류의 요청을 처리하는지 정의
  • 뷰 컨트롤러
    • 뷰에 요청을 전달하는 일만 하는 컨트롤러
    • 모델 데이터나 사용자 입력을 처리하지 않음
  • 예제
    @Configuration
    public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("home");
    }
    }
  • WebMvcConfigurer
    • 뷰 컨트롤러의 역할을 수행하는 구성 클래스가 구현해야할 인터페이스
    • 인터페이스지만 정의된 모든 메서드의 기본적인 구현을 제공
    • 따라서, 필요한 메서드만 오버라이딩
    • 어떤 구성 클래서에서도 해당 인터페이스를 구현할 수 있지만, 서로 다른 종류의 클래스(웹, 데이터, 보안 등)는 새로 생성하는 것을 선호
  • addViewControllers()
    • WebMvcConfigurer의 메서드
    • 하나 이상의 뷰 컨트롤러 등록
    • ViewControllerRegistry 객체를 인자로 받음
  • addViewController()
    • ViewControllerRegistry의 메서드
    • 뷰 컨트롤러가 GET 요청을 처리하는 "/"를 인자로 전달
    • ViewControllerRegistration 객체를 반환
  • setViewName()
    • ViewControllerRegistration의 메서드
    • 해당 경로의 요청이 전달되어야하는 뷰 지정 (home)

 

5. 뷰 템플릿 라이브러리 선택하기

  • 뷰 템플릿 라이브러리
    • 취향에 따라 뷰 템플릿 라이브러리 선택
    • 스프링의 유연성이 좋아 대부분 템플릿을 지원
  • 스프링에서 지원되는 템플릿
    • FreeMarker
    • Groovy 템플릿
    • JavaServer Page (JSP)
    • Mustache
    • Thymeleaf
  • 대게 템플릿의 사용 방법
    • 뷰 템플릿을 선택
    • 의존성 추가
    • /templates 디렉터리에 템플릿 작성
  • JSP
    • JSP는 의존성을 추가하지 않음
    • Servlet Container(Web Container)가 JSP 명세를 구현
    • Servlet Container는 /WEB-INF 밑에서 JSP 코드를 찾음
    • 만약 JAR 파일을 생성하면 이 요구사항을 충족시킬 수 없음
    • 따라서, WAR 파일로 생성

1) 템플릿 캐싱

  • 템플릿 캐싱
    • 템플릿은 최초 사용될 때 한 번만 파싱되고 캐시에 저장
    • 이는 불필요한 파싱을 하지 않아 성능을 향상시킴
    • 그러나 개발 시점에서는, 코드가 변할 때마다 어플리케이션을 다시 시작해야하는 불편함이 있음
  • 템플릿 캐싱 비활성화
    • 각 템플릿의 캐싱 속성을 false로 설정
    • Thymeleaf의 경우, application.properties에 다음을 추가
      - spring.thymeleaf.cache=false
    • 그러나 배포시 해당 설정을 true 혹은 삭제시켜줘야함
    • 스프링 부트의 DevTools를 사용하는 것이 더 쉬움 (어떻게 사용하는건지?)