1장에서는 애플리케이션 홈페이지를 보여주기 위해 최초의 스프링 MVC 컨트롤러를 생성하였다.
그러나 스프링 MVC는 간단하게 정적인 콘텐츠를 보여주는 것보다 훨씬 더 많은 일을 할 수 있다.
이번 장에서는 스프링 MVC를 더 깊이 있게 알아볼 것이며,
모델 데이터를 보여주고 사용자 입력을 처리하는 방법을 알게 될 것이다.
ℓ 정보 보여주기
타코 클라우드의 목적 : 고객이 창의적으로 커스텀 타코를 디자인할 수 있게 하고자 한다.
그리고 한걸음 더 나아가서 타코 클라우드에서는 풍부한 식자재를 보여주는 팔레트를 사용해서 고객이 창의적으로 커스텀 타코를 디자인할 수 있게 하고자한다.
선택할 수 있는 식자재의 내역은 수시로 변경될 수 있다.
따라서 HTML 페이지에 하드코딩되면 안 되며, 이보다는 사용 가능한 식자재의 내역을 데이터베이스로부터 가져와서 고객이 볼 수 있도록 해당 페이지에 전달되어야 한다.
지금까지 얘기한 타코 디자인 페이지를 지원하기 위해 다음 컴포넌트를 생성 할 것이다.
- 타코 식자재의 속성을 정의하는 도메인 클래스
- 식자재 정보를 가져와서 뷰에 전달하는 스프링 MVC 컨트롤러 클래스
- 식자재의 내역을 사용자의 브라우저에 보여주는 뷰 템플릿
ℓ 도메인 설정하기
package tacos;
import lombok.Data; //Lombok은 좋은 라이브러리를 사용해서 메서드들을 런타임 시에 자동으로 생성한다.
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
public class Ingredient {
private final String id;
private final String name;
private final Type type;
public static enum Type {
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE;
}
}
<타코 식자재 정의하기>
식재재를 나타내는데 필요한 3개의 속성을 정의한다.
Ingredient 클래스에서 특이한 점은 final 속성들을 초기화하는 생성자는 물론이고 속성들의 게터와 세터 메서드가 없다는 것과 equals(), hashCode(), toString() 등의 유용한 메서드도 정의하지 않았다는 것이다.
그 이유는 Lombok이라는 좋은 라이브러리를 사용해서 그런 메서드들을 런타임 시에 자동으로 생성하기 때문이다.
클래스에 @Data 애노테이션을 지정하면 소스 코드에 누락된 final 속성들을 초기화하는 생성자는 물론이고, 속성들의 게터와 세터 등을 생성하라고 Lombok에 알려준다.
Lombok이 매우 유용하지만 스프링 애플리케이션 개발에 꼭 필요 한 것은 아니다. (만약에 사용하지 않는다면 앞으로 메서드들을 직접 작성해야 한다)
package tacos;
import java.util.List;
import lombok.Data;
@Data
public class Taco {
private String name;
private List<String> ingredients;
}
<타코 디자인을 정의하는 도메인 객체>
Taco 클래스에도 @Data 애노테이션이 지정되었다.
각 속성의 생성자와 게터 및 세터를 컴파일 시에 Lombok에서 자동 생성하여 런타임 시에 사용할 수 있도록 하기 위해서이다.
ℓ 컨트롤러 클래스 생성하기
컨트롤러는 스프링 MVC 프레임워크의 중심적인 역할을 수행한다.
컨트롤러는 HTTP 요청을 처리하고, 브라우저에 보여줄 HTML을 뷰에 요청하거나, 또는 REST 형태의 응답 몸체에 직접 데이터를 추가한다.
package tacos.web;
import tacos.Ingredient;
import tacos.Taco;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Controller
@RequestMapping("/design")
public class DesignTacoController {
@GetMapping
public String showDesignForm(Model model) {
List<Ingredient> ingredients = Arrays.asList(
new Ingredient("AA", "AA", Ingredient.Type.WRAP),
new Ingredient("BB", "BB", Ingredient.Type.WRAP),
new Ingredient("CC", "CC", Ingredient.Type.PROTEIN),
new Ingredient("DD", "DD", Ingredient.Type.PROTEIN),
new Ingredient("EE", "EE", Ingredient.Type.VEGGIES),
new Ingredient("FF", "FF", Ingredient.Type.VEGGIES),
new Ingredient("GG", "GG", Ingredient.Type.CHEESE),
new Ingredient("HH", "HH", Ingredient.Type.CHEESE),
new Ingredient("II", "II", Ingredient.Type.SAUCE),
new Ingredient("JJ", "JJ", Ingredient.Type.SAUCE)
);
Ingredient.Type[] types = Ingredient.Type.values();
for (Ingredient.Type type : types) {
model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type));
}
model.addAttribute("taco", new Taco());
return "design";
}
@PostMapping
public String processDesign(Taco design) {
log.info("Processing design: " + design);
return "redirect:/orders/current";
}
private Object filterByType(
List<Ingredient> ingredients, Ingredient.Type type) {
return ingredients
.stream()
.filter(x -> x.getType().equals(type))
.collect(Collectors.toList());
}
}
<스프링 컨트롤러 클래스>
위 클래스가 하는 일
- 요청 경로가 /design인 HTTP GET 요청을 처리한다.
- 식자재의 내역을 생성한다.
- 삭자재 데이터의 HTML 작성을 뷰 템플릿에 요청하고, 작성된 HTML을 웹 브라우저에 전송한다.
@Slf4는 컴파일 시에 Lombok에 제공되며, 이 클래스에 자동으로 SLF4J Logger를 생성한다.
이 애노테이션은 다음코드를 추가한 것과 같은 효과를 낸다.
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);
@Controller 애노테이션은 위 클래스가 컨트롤러로 식별되게 하며, 컴포넌트 검색을 해야 한다는 것을 나타낸다.
따라서 스프링이 DesignTacoController 클래스를 찾은 후 스프링 애플리케이션 컨텍스트의 빈(Bean)으로
이 클래스의 인스턴트를 자동 생성한다.
위 클래스에는 @RequestMapping 애노테이션도 지정되어 있다.
클래스 수준으로 적용될 때는 해당 컨트롤러가 처리하는 요청의 종류를 나타낸다.
여기서는 /design으로 시작하는 경로의 요청을 처리함을 나타낸다.
@GetMapping 애노테이션은 /desing의 HTTP GET요청이 수신될 때 그 요청을 처리하기 위해
showDesignForm() 메서드가 호출됨을 나타낸다.
showDesignForm()의 메서드를 살펴보자. 식자재를 나타내는 Ingredient 객체를 저장하는 List를 생성한다.
그 다음 코드에서는 식자재의 유형을 List에서 필터링한 후 showDesignForm()의 인자로 전달되는 Model 객체의 속성으로 추가한다.
Model은 컨트롤러와 데이터를 보여주는 뷰 사이에서 데이터를 운반하고,
객체의 속성에 있는 데이터는 뷰가 알 수 있는 서블릿 요청 속성들로 복사된다.
메서드의 제일 마지막에 "design"을 반환한다.
ℓ 뷰 디자인하기
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="EUC-KR">
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}"/>
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="@{/images/TacoCloud.png}"/>
<form method="POST" th:object="${taco}">
<span class="validationError"
th:if="${#fields.hasErrors('ingredients')}"
th:errors="*{ingredients}">Ingredient Error</span>
<div class="grid">
<div class="ingredient-group" id="wraps">
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="proteins">
<h3>Pick your protein:</h3>
<div th:each="ingredient : ${protein}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="cheeses">
<h3>Choose your cheese:</h3>
<div th:each="ingredient : ${cheese}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="veggies">
<h3>Determine your veggies:</h3>
<div th:each="ingredient : ${veggies}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="sauces">
<h3>Select your sauce:</h3>
<div th:each="ingredient : ${sauce}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
</div>
<div>
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}"/>
<span th:text="${#fields.hasErrors('name')}">XXX</span>
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name Error</span>
<br/>
<button>Submit your taco</button>
</div>
</form>
</body>
</html>
<전체 타코 디자인 페이지>
@charset "EUC-KR";
div.ingredient-group:nth-child(odd) {
float: left;
padding-right: 20px;
}
div.ingredient-group:nth-child(even) {
float: left;
padding-right: 0;
}
div.ingredient-group {
width: 50%;
}
.grid:after {
content: "";
display: table;
clear: both;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
span.validationError {
color: red;
}
<타코 디자인 페이지에서 참조하는 스타일시트>
ℓ 실행하기
- java -jar로 JAR로 실행하거나, 또는 mvnspring-boot:run을 사용해서 실행할 수도 있다.
- 그리고 각자 사용하는 웹 브라우저에서 http://localhost:8080//desing에 접속하면 접속 할 수 있다.
'개발 서적 > 스프링 인 액션' 카테고리의 다른 글
스프링 인 액션 Chapter 2.4 :: 뷰 컨트롤러로 작업하기 (0) | 2023.09.19 |
---|---|
스프링 인 액션 Chapter 2.3 :: 폼 입력 유효성 검사하기 (0) | 2023.09.12 |
스프링 인 액션 Chapter 2.2 :: 폼 제출 처리하기 (0) | 2023.09.11 |
스프링 인 액션 Chapter 1.1 :: 스프링 시작하기 (1) | 2023.08.23 |