본문 바로가기
개발/Spring

MVC 패턴(2) - API 설계

by BellOne4222 2024. 2. 16.

어노테이션 기반 설계

 

컨트롤러 클래스

  • MVC 패턴 중 핸들러 메소드를 포함하는 컨트롤러 빈을 만드는 과정
  • @Controller 
    • 컨트롤러 빈으로 스프링 컨테이너에 인식
    • View를 반환하기 위해 사용
    • 객체를 상황에 맞는 ResponseEntity로 감싸서 반환해주어야 한다.
    • Spring MVC Container View 반환 과정
      1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
      2. DispatcherServlet이 요청을 처리할 대상을 찾는다.
      3. HandlerAdapter을 통해 요청을 Controller로 위임한다.
      4. Controller는 요청을 처리한 후에 ViewName을 반환한다.
      5. DispatcherServlet은 ViewResolver를 통해 ViewName에 해당하는 View를 찾아 사용자에게 반환한다.
    • Controller가 반환환 뷰의 이름으로부터 View를 렌더링하기 위해서는 ViewResolver가 사용되며, ViewResolver 설정에 맞게 View를 찾아 렌더링한다.

  • @RestController = @ResponseBody(바디 부분을 출력) + @Controller
  •  Json 형태로 객체 데이터를 반환
  • REST API를 개발 할 때 주로 사용하며 객체를 ResponseEntity로 감싸서 반환
    • 반환 과정
      1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
      2. DispatcherServlet이 요청을 처리할 대상을 찾는다.
      3. HandlerAdapter을 통해 요청을 Controller로 위임한다.
      4. Controller는 요청을 처리한 후에 객체를 반환한다.
      5. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.

 

핸들러 메소드

  • 스프링 웹서비스가 받는 URI 요청을 컨트롤러 클래스의 특정 메소드에 매핑(비즈니스 로직으로 타고 들어갈 수 있도록)하는 과정
  • @RequestMapping
    1. Spring 개발 시 특정 URL로 요청(Request)을 보내면 Controller에서 어떠한 방식으로 처리할지 정의한다.
      이때 들어온 요청을 특정 method와 매핑하기 위해 사용하는 어노테이션
    2. 스프링부트 애플리케이션이 실행되면 애플리케이션에서 사용할 bean들을 담을 ApplicationContext를 생성하고 초기화합니다. refresh과정에서 Spring Application 구동을 위해 많은 Bean들이 생성되고,3) Bean으로 등록된 HandlerMapping이 변수들을 찾아서 Adapter를 거쳐 실행합니다.
    3.  그 중 하나가 RequestMappingHandlerMapping 입니다. 이 Bean은 우리가 @RequestMapping 으로 등록한 메서드들을 가지고 있다가 요청이 들어오면 Mapping해주는 역할을 합니다.
    4.  @RequestMapping 이 붙은 메서드들이 핸들러에 등록되는 것은 Application refresh되는 과정에서 일어납니다.

1) @PostMapping : HTTP Post Method에 해당하는 단축 표현으로 서버에 리소스를 등록(저장)할 때 사용합니다.

2) @GetMapping : HTTP Get Method에 해당하는 단축 표현으로 서버의 리소스를 조회할 때 사용합니다.

3) @DeleteMapping : 서버의 리소스를 삭제

4) @PutMapping : 서버의 리소스를 모두 수정

5) @PatchMapping : 서버의 리소스를 일부 수정

 

코로나 줄서기 Controller 구현

// adminController
package com.example.Get_In_Line.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/admin") // 공통적인 맵핑을 할때 사용 -> /admin/places에서 공통적으로 admin 경로를 거치므로 RequestMapping 처리
@Controller
public class AdminController {

	@GetMapping("/places")
	public String adminPlaces(){
		return "admin/places";
	}

	@GetMapping("/places/{placeId}")
	public String adminPlaceDetail(@PathVariable Integer placeId){ // @PathVariable : path안의 일부 요소를 변수로 사용 -> placeId를 안에서 사용가능
		return "admin/place-detail";
	}

	@GetMapping("/events")
	public String adminEvents(){
		return "admin/events";
	}

	@GetMapping("/events/{eventId")
	public String adminEventDetail(@PathVariable Integer eventId){
		return "admin/event-detail";
	}
}

// AuthController
package com.example.Get_In_Line.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class AuthController {
	
	// 핸들러 메서드
	
	// 로그인
	@GetMapping("/login")
	public String login(){
		return "auth/login";
	}
	
	// 회원가입
	@GetMapping("/sign-up")
	public String signUp(){
		return "auth/sign-up";
	}
	
}

// BaseController
package com.example.Get_In_Line.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class BaseController {

	// root 페이지 설정
	@GetMapping("/")
	public String root(){
		return "index"; // 타임리프를 통해 기본경로를 templates로 변경하여 안에 있는 index.html을 기본 페이지로 설정, static에 index.html을 생성하면 welcomepage로 인식해서 기본페이지 설정 가능
	}
}

// EventController
package com.example.Get_In_Line.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/events")
@Controller
public class EventController {

	@GetMapping("/")
	public String events(){
		return "event/index";
	}

	@GetMapping("/{eventId}")
	public String eventDetail(@PathVariable Integer eventId){
		return "event/detail";
	}
}

// placeController
package com.example.Get_In_Line.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/places")
@Controller
public class PlaceController {

	@GetMapping("/")
	public String places(){
		return "place/index";
	}

	@GetMapping("/{placeId}")
	public String placeDetail(@PathVariable Integer placeId){
		return "place/detail";
	}
}

 

APIController 구현

// APIAuthController
package com.example.Get_In_Line.Controller.api;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api")
@RestController
public class APIAuthController {

	@GetMapping("/sign-up")
	public String signUp(){
		return "done.";
	}

	@GetMapping("/login")
	public String login(){
		return "done.";
	}
}

// APIEventController
package com.example.Get_In_Line.Controller.api;

import java.util.List;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api")
@RestController
public class APIEventController {

	@GetMapping("/events")
	public List<String> getEvents(){
		return List.of("event1", "event2");
	}

	@PostMapping("/events")
	public Boolean createEvent(){
		return true;
	}

	@GetMapping("/events/{eventId}")
	public String getEvent(@PathVariable Integer eventId){
		return "event " + eventId;
	}

	@PutMapping("/events/{eventId}")
	public Boolean modifyEvent(@PathVariable Integer eventId){
		return true;
	}

	@DeleteMapping("/events/{eventId}")
	public Boolean removeEvent(@PathVariable Integer eventId){
		return true;
	}
}

// APIPlaceController
package com.example.Get_In_Line.Controller.api;

import java.util.List;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api")
@RestController
public class APIPlaceController {

	@GetMapping("/places")
	public List<String> getPlaces(){
		return List.of("place1", "place2");
	}

	@PostMapping("/places")
	public Boolean createPlace(){
		return true;
	}

	@GetMapping("/places/{placeId}")
	public String getPlace(@PathVariable Integer placeId){
		return "place " + placeId;
	}

	@PutMapping("/places/{placeId}")
	public Boolean modifyPlace(@PathVariable Integer placeId){
		return true;
	}

	@DeleteMapping("/places/{placeId}")
	public Boolean removePlace(@PathVariable Integer placeId){
		return true;
	}
}

 

Postman Test

 

Error Report

Fix this pattern in your application or switch to the legacy 
parser implementation with 'spring.mvc.pathmatch.
matching-strategy=ant_path_matcher'.

 

  • 원인
SpringBoot 를 2.6 이상 버전으로 업그레이드 되어있을 시 요청 경로를 
ControllerHandler 에 매칭시키기 위한 것이 작동된다.
조금 더 자세하게 설명해보면, 
spring.mvc.pathmatch.matching-strategy 기본 값이 ant_path_matcher 
에서 path_pattern_parser 로 변경이 된다.

 

  • 해결
application.properties 파일에 가서 

spring.mvc.pathmatch.matching-	strategy=ant_path_matcher
을 입력해서 해결

'개발 > Spring' 카테고리의 다른 글

MVC 패턴 - API 설계(2)  (0) 2024.02.17
함수형 프로그래밍  (0) 2024.02.16
MVC 패턴(1) - 요구사항 설계  (0) 2024.02.15
AOP(Aspect Oriented Programming)  (0) 2024.02.06
스프링 빈 이벤트 라이프 사이클  (0) 2024.02.04