
핸들러 메서드는 사용자 요청을 받아서 응답을 만드는 메서드.
스프링이 이 메서드를 호출할 때, 메서드 파라미터 타입을 보고 자동으로 (인자)값을 넣어준다.
예:
각 방식 마다 어느 시점에 사용하면 좋을지 파악해보자
흐름부터 보면:
/first/regist로 GET 요청을 보냄regist.html 화면을 보여줌/first/regist로 POST 요청이 들어감WebRequest로 파라미터를 직접 꺼냄컨트롤러:
@Controller
@RequestMapping("/first/*")
public class FirstController {
/* 반환타입이 void면
* 요청 주소(/first/regist)가 그대로 뷰 이름이 된다.
* => templates/first/regist.html 을 찾아감
*/
@GetMapping("regist")
public void regist() {}
}
void 이면 요청 주소가 그대로 뷰 이름이 된다.뷰(regist.html):
<form action="regist" method="post">
등록할 메뉴의 이름 : <input type="text" name="name"><br>
등록할 메뉴의 가격 : <input type="number" name="price"><br>
등록할 메뉴의 카테고리 :
<select name="categoryCode">
<option value="1">식사</option>
<option value="2">음료</option>
<option value="3">디저트</option>
</select><br>
<button type="submit">등록하기</button>
</form>
POST 요청 처리:
@PostMapping("regist")
public String registMenu(Model model, WebRequest request) {
// WebRequest로 파라미터 직접 꺼냄
String name = request.getParameter("name");
int price = Integer.parseInt(request.getParameter("price"));
int categoryCode = Integer.parseInt(request.getParameter("categoryCode"));
String message =
name + "을(를) 신규 메뉴 목록의 " + categoryCode +
"번 카테고리에 " + price + "원으로 등록 하셨습니다!";
System.out.println(message);
model.addAttribute("message", message);
return "first/messagePrinter";
}
정리: WebRequest로 요청 파라미터 전달 받기
request.getParameter("name") 이런 식으로 직접 꺼내서 사용getParameter() 호출해야 해서 조금 번거로울 수 있음→ Http Request , Response 과 동일하지만 스프링에서는 WebRequest 를 사용하면 된다.
스프링에서 제공하는, 변수를 하나씩 불러오는 어노테이션이다.
required = true 가 기본 값이기 때문이다.흐름:
/first/modify GET 요청 → modify.html 화면 보여줌/first/modify POST컨트롤러 GET:
@GetMapping("modify")
public void modify() {}
뷰(modify.html):
<h3>메뉴 수정하기</h3>
<form action="modify" method="post">
수정할 메뉴의 이름 : <input type="text" name="modifyName"><br>
수정할 메뉴의 가격 : <input type="number" name="modifyPrice"><br>
<button type="submit">수정하기</button>
</form>
POST 처리:
@PostMapping("modify")
public String modifyMenuPrice(
Model model,
@RequestParam(value="name", required = false) String modifyName,
@RequestParam(value="price", defaultValue = "0") int modifyPrice) {
String message = modifyName + "메뉴의 가격을 " + modifyPrice + "로 가격을 변경하였습니다.";
System.out.println(message);
model.addAttribute("message", message);
return "first/messagePrinter";
}
⭐여기서 중요한 속성들:
@RequestParam("modifyName") String name → name 속성이 modifyName인 값을 해당 변수로 매핑 → 변수 이름이 폼의 name과 다르면 "이름"으로 명시@RequestParam("name") String modifyNamerequired = true (기본값) → 요청에 이 파라미터가 없으면 400 에러. 반드시 값을 넣어주어야 한다.required = false → 없어도 null로 처리. 값이 안들어와도 괜찮다.defaultValue = "0" → 값이 아예 안 넘어와도 0으로 세팅됨 (파싱 에러 방지)@RequestParm 을 생략할 수도 있다.
뷰의 두 번째 form:
<h3>메뉴 수정하기2</h3>
<form action="modifyAll" method="post">
수정할 메뉴의 이름 : <input type="text" name="modifyName2"><br>
수정할 메뉴의 가격 : <input type="number" name="modifyPrice2"><br>
<button type="submit">수정하기</button>
</form>
컨트롤러:
@PostMapping("modifyAll")
public String modifyMenu(Model model,
@RequestParam Map<String, String> parameters) {
String modifyMenu = parameters.get("modifyName2");
int modifyPrice = Integer.parseInt(parameters.get("modifyPrice2"));
String message = "메뉴의 이름을 " + modifyMenu +
"(으)로, 가격을 " + modifyPrice + "원 으로 변경하였습니다.";
System.out.println(message);
model.addAttribute("message", message);
return "first/messagePrinter";
}
포인트:
이번에는 폼 입력 전체를 DTO로 한 번에 받는 방식이다.
흐름:
/first/search GET → search.html 보여줌/first/searchGET:
@GetMapping("search")
public void search() {}
뷰(search.html):
<form action="search" method="post">
검색할 메뉴 이름 : <input type="text" name="name"><br>
검색할 메뉴 가격 : <input type="number" name="price"><br>
검색할 메뉴의 카테고리 :
<select name="categoryCode">
<option value="1">식사</option>
<option value="2">음료</option>
<option value="3">디저트</option>
</select><br>
검색할 판매 상태 : <input type="text" name="orderableStatus"><br>
<input type="submit" value="검색하기">
</form>
커맨드 객체 DTO:
public class MenuDTO {
private String name;
private int price;
private int categoryCode;
private String orderableStatus;
public MenuDTO() {}
public MenuDTO(String name, int price, int categoryCode, String orderableStatus) {
this.name = name;
this.price = price;
this.categoryCode = categoryCode;
this.orderableStatus = orderableStatus;
}
// getter/setter 전부 존재해야 함 (스프링이 자동으로 호출)
// ...
}
핸들러 메소드:
@PostMapping("search")
public String searchMenu(@ModelAttribute("menu") MenuDTO menu) {
System.out.println(menu);
return "first/searchResult";
}
뷰(searchResult.html):
<h1>Model에 담긴 커맨드 객체의 정보 출력</h1>
<h3 th:text="|메뉴의 이름 : ${ menu.name }|"></h3>
<h3 th:text="|메뉴의 가격 : ${ menu.price }|"></h3>
<h3 th:text="|메뉴의 카테고리 : ${ menu.categoryCode }|"></h3>
<h3 th:text="|메뉴의 판매상태 : ${ menu.orderableStatus }|"></h3>
포인트:
@ModelAttribute가 붙은 객체는"menu"라는 이름으로 모델에 들어감 (지정 안 하면 타입 이름의 앞글자 소문자: MenuDTO → menuDTO)→ 폼 데이터가 많아질수록 @ModelAttribute + DTO 방식이 훨씬 깔끔하고 유지보수하기 좋다.
로그인 정보 같은 걸 세션에 어떻게 담고 빼는지를 다룬다. 이전에 Session을 다룰 때 객체를 직접 생성하는 것이 아니라 request 객체 안에 getSession() 을 통해서 뽑아서 사용했었다.
세션에 있는 정보를 가져다 쓰거나 값을 추가해서 사용하려면 servlet에서는
HttpSession session = request.getSession() 처럼 사용했다.
HttpSession을 전달받는 것도 가능하지만 Servlet에 종속적이므로 Spring에서 제공하는 기능을 사용하는 것을 권장한다.
흐름:
/first/login GET → login.html/first/login1 POSTsession.id 출력/first/logout1 요청 시 session.invalidate() 로 세션 전체 제거login.html:
<h3>HttpSession을 매개변수로 선언하기</h3>
<form action="login1" method="post">
ID : <input type="text" name="id"><br>
PWD : <input type="password" name="pwd"><br>
<input type="submit" value="로그인">
</form>
컨트롤러:
@PostMapping("login1")
public String sessionTest(HttpSession session, @RequestParam String id) {
session.setAttribute("id", id);
return "first/loginResult";
}
@GetMapping("logout1")
public String logoutTest(HttpSession session) {
session.invalidate(); // 세션 전체 종료
return "first/loginResult";
}
뷰(loginResult.html):
<h1>Session에 담긴 값 확인하기</h1>
<h3 th:text="|${ session.id }님 환영합니다.|"></h3>
<div>
<button onclick="location.href='logout1'">로그아웃</button>
</div>
포인트:
setAttribute/getAttribute로 직접 다루는 전통적인 방식invalidate()는 세션 자체를 날리는 것 (모든 값 삭제)@SwessionAttribute("key") 와 같이 지정하면 Model에 해당 key가 추가될 경우 Session에도 자동 등록된다.이번에는 “모델에 넣었다가 자동으로 세션에 올리기” 방식.
login.html에 두 번째 form 추가:
<h3>@SessionAttributes를 사용하기</h3>
<form action="login2" method="post">
ID : <input type="text" name="id"><br>
PWD : <input type="password" name="pwd"><br>
<input type="submit" value="로그인">
</form>
컨트롤러 상단에:
@Controller
@RequestMapping("/first/*")
@SessionAttributes("id")
public class FirstController {
...
}
그리고 핸들러:
@PostMapping("login2")
public String sessionTest2(Model model, @RequestParam String id) {
model.addAttribute("id", id); // Model 에 추가
return "first/loginResult";
}
여기서 중요한 동작:
@SessionAttributes("id") 때문에 Model에 "id"라는 이름의 값이 들어오면 → 세션에도 자동으로 저장됨session.setAttribute 안 해도 됨로그아웃2 버튼 추가:
<button onclick="location.href='logout2'">로그아웃2</button>
컨트롤러:
@GetMapping("logout2")
public String logoutTest2(SessionStatus sessionStatus) {
// @SessionAttributes로 관리되는 세션 값들을 사용 끝 처리
sessionStatus.setComplete();
return "first/loginResult";
}
⭐포인트:
@SessionAttributes는 “이 컨트롤러에서 Model에 이 이름으로 담는 건 세션에도 저장해라”라는 의미SessionStatus.setComplete()를 호출해야 그 세션 값이 “만료”됨invalidate()처럼 세션 전체를 날리는 게 아니라 이 컨트롤러에서 관리하던 세션 속성만 끝내는 개념invalidate() 를 사용해도 Session이 소멸되지 않는다.@SessionAttributes는 컨트롤러 단위로 동작HTTP body를 그대로 읽어오는 것.
흐름:
/first/body GET → body.html/first/body로 전송body.html:
<form action="body" method="post">
메뉴 이름 : <input type="text" name="name"><br>
메뉴 가격 : <input type="number" name="price"><br>
카테고리 :
<select name="categoryCode">
<option value="1">식사</option>
<option value="2">음료</option>
<option value="3">디저트</option>
</select><br>
판매 상태 : <input type="text" name="orderableStatus"><br>
<input type="submit" value="검색하기">
</form>
컨트롤러:
@PostMapping("body")
public void bodyTest(@RequestBody String body,
@RequestHeader("content-type") String contentType,
@CookieValue(value="JSESSIONID", required = false) String sessionId) {
System.out.println(contentType); // 요청의 Content-Type
System.out.println(sessionId); // 세션 ID 쿠키 값
System.out.println(body); // raw body (쿼리스트링 형식)
System.out.println(URLDecoder.decode(body));
}
여기서 body는 이런 문자열로 들어온다:
name=%ED%83%9C%EA%B5%AD%EB%8B%AD&price=10000&categoryCode=1&orderableStatus=Y
URLDecoder로 디코딩하면 사람이 읽을 수 있는 한글/숫자로 변환 가능.
실제로 REST API에서 JSON을 보낼 때:
Content-Type: application/json@RequestBody MenuDTO menu 이런 식으로 DTO로 직접 받을 수 있음 (Jackson이 JSON → 객체로 자동 변환)포인트: