🔷 Spring in Action - 2.웹 애플리케이션 개발하기_ 2.1 정보 보여주기, 2.2 폼 제출 처리하기
✔ 2.1 정보 보여주기
- Controller : 데이터를 가져오고 처리한다
- view : 브러우저에 보여주는 데이터를 HTML로 나타낸다
🎈 디자인 페이지를 지원하기 위해 생성할 컴포넌트
- 식자재의 속성을 정의하는 도메인(domain) 클래스
- 식자재 정보를 가져와 뷰에 전달하는 스프링 MVC 컨트롤러 클래스
- 식자재의 내역을 사용자의 브라우저에 보여주는 뷰 템플릿
✔ 2.1.1 도메인 설정하기
-
타코 식자재 정의 코드
package tacos; import lombok.Data; import lombok.RequiredArgsConstructor; // @data, @RequiredArgsConstructor : lombok // @Data : final 속성들을 초기화하는 생성자, 게터,세터 자동 생성 @Data // @RequiredArgsConstructor : 생성자를 만들어준다 @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 } }
❓ @Data, @RequiredArgsConstructor 를 왜 같이쓸까? Data 만 쓰면 안되는지? => @RequiredArgsConstructor 는 final 이나 @NonNull이 붙은 변수들을 가진 생성자를 생성, @Data 는 옵션값이 없을때 생성자, 게터,세터 등을 생성해준다 : 왜 같이 쓴지는 아직 모르겠다
▶ lombok 생성 출력
-
타코 디자인 정의하는 도매인 객체 코드
package tacos; import lombok.Data; import java.util.List; @Data public class Taco { private String name; private List<Ingredient> ingredients; }
✔ 2.1.2 컨트롤러 클래스 생성하기
- Controller : 스프링 MVC 프레임워크의 중심적인 역할 수행
=> HTTP 요청을 처리, 브라우저에 보여줄 HTML을 뷰에 요청, REST 형태의 응답 몸체에 직접 데이터 추가
🎈컨트롤러가 수행할 기능
- 요청 경로가 /design인 HTTP GET 요청 처리
- 식자재 내역 생성
- 식자재 데이터의 HTML 작성을 뷰 텐플릿에 요청, 작성된 HTML을 웹 브라우저에 전송
-
스프링 컨트롤러 클래스 코드
package tacos.web; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import lombok.extern.slf4j.Slf4j; import tacos.Taco; import tacos.Ingredient; import tacos.Ingredient.Type; // @Slf4j : Logger 생성 @Slf4j // @Controller : 해당 클래스가 컨트롤러로 식별되게 하며 컴포넌트 검색을 해야한다는 것을 나타냄 // => 스프링이 해당클래스를 찾은후 스프링 APP컴텍스트의 빈(Bean)으로 해당 클래스의 인스턴스를 자동 생성한다 @Controller // @RequestMapping : 해당 클래스가 처리하는 요청의 종류 // 요청 경로가 disign 인 HTTP GET 요청 처리 @RequestMapping("/design") public class DesignTacoController { // @GetMapping : /disign의 HTTP GET 요청이 수신될 때 그 요청을 처리하기 위해 showDesignForm() 메서드가 호출 되는것을 나타낸다 @GetMapping // 식자재 내역 생성 => 식자재의 내역(유형)을 List 에서 필터링(filterByType 메서드) 한 후 // showDesignForm()의 인자로 전달되는 Model 객체의 속성으로 추가한다 // Model : 컨트롤러와 데이터를 보여주는 뷰 사이에서 데이터를 운반하는 객체 public String showDesignForm(Model model){ // Ingredient 객체 저장하는 List 생성 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 Tomatoes", 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) ); // types 변수에 타코 식자재 정의 코드의 Type를 대입 Type[] types = Type.values(); for(Type type : types){ model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type)); } model.addAttribute("taco",new Taco()); // design 을 리턴 한다 => 모델 데이터를 브라우저에 나타나는 데 사용될 뷰의 논리적인 이름 return "design"; } private List<Ingredient> filterByType(List<Ingredient> ingredients, Type type) { // ingredients를 stream 으로 치환 -> type과 리스트의 타입이 같으면 -> 같은 타입끼리 뭉쳐놓는다 return ingredients.stream().filter(x -> x.getType().equals(type)) .collect(Collectors.toList()); } }
❓ @RequestMapping, @GetMapping 꼭 같이써야하는지 ? => @RequestMapping 은 클래스 개념(/design의 전체적인것) @GetMapping 는 세부적인 개념이다(/design 의 세부사항) @RequestMapping 을 안해주면 @GetMapping 할때 앞에 매번 /design 경로까지 붙혀줘야한다 ❓ @Slf4j : Logger를 사용하는 이유는? => 구간체크 하려고 -> System.out.print 사용 하면 되는거아닌가 ? -> Logger 는 debug, info, warn, error, fatal 로 나누어 어떤곳에 문제가 있는지 표시된다 sout 는 어떤문제인지 표시가 되지 않기때문에 Logger를 사용한다
✔ 2.1.3 뷰 디자인 하기
- 스프링에서 뷰를 정의하는 방법 : JSP,Thymeleaf, FreeMarker, Mustache, 그루비 기반의 템플릿 등
=> Thymeleaf 사용 !
✔ 2.2 폼 제출 처리하기
- /design 경로의 POST 요청을 처리하는 컨트롤러의 새로운 메서드 작성
-
@PostMapping을 사용해 POST 요청 처리 추가 코드
package tacos.web; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import lombok.extern.slf4j.Slf4j; import tacos.Taco; import tacos.Ingredient; import tacos.Ingredient.Type; // @RequestMapping : 해당 클래스가 처리하는 요청의 종류 // 요청 경로가 disign 인 HTTP GET 요청 처리 @RequestMapping("/design") public class DesignTacoController { // /design 경로의 POST 요청을 처리 => taco 를 디자인 하는 사용자가 제출한 것을 여기에서 처리한다 @PostMapping // processDesign() 메서드에서는 taco 객체를 사용해서 어떤 것이든 원하는 처리를 할 수 있다 public String processDesign(Taco design){ log.info("Processing design: " + design); // redirect : processDesign()의 실행이 끝난 후 사용자의 브라우저가 /orders/current 상대 경로로 재접속되어야 한다는 것을 나타낸다 return "redirect:/orders/current"; } }
❓ redirect 대신 forward를 써도 되는지? => 된다!-> redirect 와 forward의 차이점 -> redirect : URL 주소가 바뀐다 (세션 정보 유지가 안된다) -> forward : IRL 주소가 바뀌지 않는다 (세션 정보 유지가 된다)
-
타코 주문 폼을 나타내는 컨트롤러 코드
package tacos.web; 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.RequestMapping; import tacos.Order; @Slf4j @Controller // @RequestMapping : /orders 로 시작되는 경로의 요청을 이 컨트롤러의 요청 처리 메서드가 처리한다는 것을 알려주는 애노테이션 @RequestMapping("/orders") public class OrderController { @GetMapping("/current") public String orderForm(Model model) { model.addAttribute("order", new Order()); return "orderForm"; } }
-
타코 주문 제출 처리 추가 코드
package tacos.web; 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 tacos.Order; @Slf4j @Controller // @RequestMapping : /orders 로 시작되는 경로의 요청을 이 컨트롤러의 요청 처리 메서드가 처리한다는 것을 알려주는 애노테이션 @RequestMapping("/orders") public class OrderController { @PostMapping // processOrder() 메서드는 SessionStatus를 인자로 전달받아 // setComplete() 메서드를 호출하여 세션을 재설정 한다 public String processOrder(Order order) { log.info("Order submitted: " + order); // orderForm 을 리턴한다 return "orderForm"; } }
-
타코 주문 정보를 갖는 도매인 객체 코드
package tacos; import lombok.Data; @Data public class Order { private String deliveryName; private String deliveryStreet; private String deliveryCity; private String deliveryState; private String deliveryZip; private String ccNumber; private String ccExpiration; private String ccCVV; }