단의 개발 블로그

간단한 웹 요청 만들기 본문

Web/Spring

간단한 웹 요청 만들기

danso 2024. 5. 8. 20:04

애플리케이션

고객이 상품을 구매할 때 선택할 수 있는 내역은 수시로 변경된다. 그렇기 때문에 HTML페이지에 단순히 하드 코딩해서는 안된다. 구매 가능한 상품을 데이터베이스에서 가져와서 고객이 볼 수 있도록 해야한다. 스프링에서는 데이터를 가져오고 보여주는 역할을 컨트롤러가 하고, 브라우저에 보여주는 데이터를 HTML로 나타내는건 뷰가 한다. 고객에게 상품을 보여주기 위해 아래 3개의 클래스를 생성한다.

  • 타코 식자재 속성을 정의하는 도메인(domain) 클래스
  • 식자재 정보를 가져와서 뷰에 전달하는 스프링 MVC 컨트롤러 클래스
  • 식자재 내역을 사용자의 브라우저에 보여주는 뷰 템플릿

 

도메인 생성

타코 식자재를 정의하는 Ingredient 클래스를 생성한다. 해당 클래스는 domain 패키지를 새로 생성해서 그 안에 만든다.

@Data
public class Ingredient {

    private final String id;
    private final String name;
    private final Type type;

    public static enum Type {
        WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
    }
}

@Data

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
	String staticConstructor() default "";
}
  • @Data는 @ToString, @EqualAndHashCode, @Getter, @Setter, @RequiredArgsConstructor 어노테이션이 포함된 어노테이션이다. 
  • 인텔리제이 사용 시 alt + 7 or 상단 탭 view - tool window - structure를 클릭하면 아래와 같이 볼 수 있다.

 

타코 도메인 클래스도 추가한다.

@Data
public class Taco {
    private String name;
    private List<String> ingredients;
}

 

 

컨트롤러 생성

컨트롤러는 MVC 프레임워크에서 중심 역할을 한다. HTTP 요청을 처리하고, 브라우저에 보여줄 HTML을 뷰에 요청하거나, REST 형태의 응답 본문에 데이터를 추가한다. 현재 생성하는 컨트롤러는 아래 역할을 수행한다.

  • 요청 경로가 /design인 HTTP GET 요청을 처리한다.
  • 식자재의 내역을 생성한다.
  • 식자재 데이터의 HTML 작성을 뷰 템플릿에 요청하고, 작성된 HTML을 웹 브라우저에 전송한다.

HomeController와 같은 경로에 DesignTacoController를 생성한다.

@Log4j2
@Controller
@RequestMapping("/design")
public class DesignTacoController {

    @GetMapping
    public String showDesignForm(Model model)
    {
        List<Ingredient> ingredients = Arrays.asList(
            new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
            new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
            new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
            new Ingredient("CARN", "Carnitas", Type.PROTEIN),
            new Ingredient("TMTO", "Diced Tomatos", Type.VEGGIES),
            new Ingredient("LETC", "Lettuce", Type.VEGGIES),
            new Ingredient("CHED", "Cheddar", Type.CHEESE),
            new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
            new Ingredient("SLSA", "Salsa", Type.SAUCE),
            new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
        );

        Type[] types = Ingredient.Type.values();
        for(Type type : types)
        {
            model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type));
        }
        model.addAttribute("taco", new Taco());

        return "design";
    }

    private List<Ingredient> filterByType(List<Ingredient> ingredients, Type type) {
        return ingredients.stream().filter(x -> x.getType().equals(type)).collect(Collectors.toList());

    }
}
  • 해당 클래스는 @Controller라는 컴포넌트 어노테이션을 단다.
  • RequestMapping 어노테이션은 해당 컨트롤러가 처리해야하는 요청을 나타낸다
    • Spring 4.3에서 새롭게 등장한 어노테이션들이 있다. 해당 어노테이션들은 요청 방식에 맞게 전달 받아 처리한다. 해당 메소드들이 없을 경우 생성, 수정, 삭제 등 하나의 도메인에 여러 동작이 요청 될 수 있는데, 해당 동작마다 URL을 지정해줘야 하기 때문에 관리하기 어려워진다. 요청방식은 아래와 같다.
어노테이션 설명
@RequestMapping 다목적 요청 처리, method에 요청 방식을 지정한다.
@GetMapping GET 요청을 처리한다.
@PostMapping POST 요청을 처리한다.
@PutMapping PUT 요청을 처리한다.
@DeleteMapping DELETE 요청을 처리한다.
@PatchMapping PATCH 요청을 처리한다.
  • showDesignForm() 메소드가 해당 요청을 처리한다. 보통 웹 애플리케이션리아면 식자재 목록은 나중에 DB에서 목록을 받아와서 처리하는 것으로 수정한다.
  • Model을 파라미터로 전달 받아서 함수 내에서 model.addAttribute에 추가한다. Model 객체의 속성에 있는 데이터는 뷰가 알 수 있는 Servlet 요청으로 만들어서 전달된다.

 

뷰 생성

스프링에서 뷰를 생성하는 방법은 JSP, Thymeleaf, FreeMarker, Mustache등 다양하다. 각자 취향에 맞는 템플릿 엔진을 사용하여 웹을 완성하면 된다. 여기서는 Thymeleaf를 사용한다. 간단히 모델로 전달한 속성을 ${속성명}을 사용하여 출력한다. 속성의 속성은 체이닝을 이용해서 불러온다.

design.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <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="wrap">
      <h3>Design your wrap:</h3>
      <div th:each="ingredient : ${wrap}" >
        <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
        <span th:text="${ingredient.name}">INGREDIENT</span>
      </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>
      </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>
      </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>
      </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>
      </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>

resources/static/styles.css 를 추가한다.

@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;
}

 

 

실행하기

작성한 웹 애플리케이션을 실행하는 방법은 여러가지다. 인텔리제이에 초록색 재생버튼을 누르거나, gradle로 빌드된 jar파일을 실행하는 방법이 있다. 툴에서 실행하는 방법은 이미 해봤으므로 gradle로 빌드해서 jar로 실행하는 방법을 써보자.

실제 운영에서 웹 애플리케이션을 실행하기 위해서는 이 방법으로 해야한다. 인텔리제이 하단에 Termianal을 클릭해서 터미널을 띄운다. 아마 기본이 프로젝트 경로이다. 해당 프로젝트 경로에서 아래 명령어를 입력한다.

gradlew build

우리의 앱이 빌드가 되고, build/libs 에 jar파일이 생성된다. 해당 파일을 java 명령어로 실행한다.

java -jar ./build/libs/파일명.jar

인텔리제이에서 실행한 것과 똑같이 실행된다. 앱을 종료하고 싶으면 ctrl + c를 누르거나 터미널을 종료하면 된다.

만약 다시 실행했을 때 이미 포트가 실행중이라고 나온다면 netstat -a -o 명령어 입력 후 taskkill /f /pid PID번호 를 입력한다.

localhost:8080으로 요청하면 아래와 같은 뷰를 볼 수 있다.

출처

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=239755024&start=slayer

'Web > Spring' 카테고리의 다른 글

데이터 베이스  (0) 2024.09.04
사용자 요청 처리하기  (0) 2024.09.03
스프링 요청 및 라이브러리 살펴보기  (1) 2024.05.02
프로젝트 생성 및 프로젝트 구조  (0) 2024.05.02
Spring 이란?  (0) 2024.04.18