@ResponseBody

핸들러 메소드에서 @ResponseBody 애노테이션이 적용된 경우 반환 객체를 HTTP 응답으로 전송한다.

- 메소드에서 반환하는 자바 객체를 HTTP 응답 몸체로 변환한다.

- 자바 객체를 HTTP 요청의 body 내용으로 매핑하는 역할

HttpMessageConverter를 통해 HTTP 응답 스트림으로 변환

<mvc:annotation-driven/>를 통해 HttpMessageConverter 구현 클래스를 모두 등록할 수 있다.

주요 HttpMessageConverter 구현 클래스

-  StringHttpMessageConverter : 요청 몸체를 문자열로 변환하거나 문자열을 응답 몸체로 변환

(text/plain;charset=ISO-8859-1)

- Jaxb2RootElementHttpMessageConverter : XML 용청 몸체를 자바 객체로 변환하거나 자바 객체를 XML 응답 몸체로 변환(text/xml, application/xml)

- MappingJackson2HttpMessageConverter : JSON 요청 몸체를 자바 객체로 변환하거나 자바 객체를 JSON 응답 몸체로 변환(text/json, application/json)

- ByteArrayHttpMessageConverter : HTTP 메시지와 byte 배열 사이의 변환을 처리(application/octet-stream)

- 스프링 4.0부터는 MappingJackson2HttpMessageConverter를 이용ㅇ하여 자바 객체를 JSON으로 변환하거나 JSON을 자바 객체로 변환하며, 다음의 의존성을 추가해야 한다.

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.3</version>
</dependency>

- 스프링 3.x 에서는 MappingJacksonHttpMessageConverter를 이용하여 변환 한다.

MappingJacksonHttpMessageConverter는 jackson-mapper-asl, jackson-core-asl의 의존성을 추가해야 한다.

- 스프링 4.0에 추가된 @RestController 애노테이션으로 컨트롤러를 설정하면, @RestController에는 기본적으로 @ResponseBody 애노테이션이 적용되어 메소드 레벨에서 추가하지 않아도 된다.

 

사용 예

1) String을 JSON으로 변환하여 반환

- 문자열을 JSON 반환할 때, 한글을 반환하는 경우 반드시 produces 옵션을 통해 charset을 명시 해야 한다.

@RequestMapping(value = "/user", produces="application/json;charset=utf-8")
@ResponseBody
public String process(@RequestParam String id) {
	String name = service.getName(id);
    return "{\"name\":\"+name+"\"}";
}

 

2) Map<String, Object>을 JSON으로 변환하여 반환

@RequestMapping(value = "/user")
@ResponseBody
public Map<String, Object> process(@RequestParam int idx) {
	Map<String, Object> model = service.readMap(idx);
    return model;
}

@RequestBody

HTTP 요청 몸체를 자바 객체로 전달받음

HTTP 요청의 body 내용을 자바 객체로 매핑하는 역할

POST 형식으로 응답 받는 경우에만 사용할 수 있다.

사용 예 - JSON 형식으로 전송한 요청 파라미터를 전달 받아 JSON 형식으로 응답

- JSON 형식의 요청 파라미터를 member 객체에 전달받아 처리 후, 처리 결과를 가지고 있는 loginMember 객체를 JSON으로 변환하여 응답한다.

@RequestMapping(value = "/member/login", method=RequestMethod.POST)
@ResponseBody
public Member loginSubmit(@RequestBody Member member) throws Exception {
	Member loginMember = memberService.login(member);
    return loginMember;
}

@RestController

@RestController는 @Controller에 @ResponseBody가 추가된 애노테이션이다.

스프링 4.0부터 지원한다.

REST 방식의 데이터 처리를 위해 사용하는 애노테이션

- @RestController는 자바 객체를 JSON/XML 타입으로 반환하는 REST 서비스에 최적화된 컨트롤러

 

@Controller와 @RestController 차이점

- @Controller 

API와 뷰를 동시에 서비스하는 경우에 사용하며, API 서비스는 @ResponseBody를 붙여줘야 한다.

 

- @RestController

뷰가 필요 없는 API를 서비스하는 경우에 사용. @ResponseBody를 포함하고 있다.

 

- @RestController 에서 뷰를 반환해야 하는 경우에는 ModelAndView를 반환한다.

AJAX 를 왜 쓰느냐 ?

-> Guess : 전체 페이지를 서버로 보내는게 아니라 일부분만 업데이트 할 때 그 부분만 보낼 수 있어서 , 부분적으로 업데이트 하거나 그럴때 쓰임 (맞는지 확인 필수) 요새는 많이 쓰기 때문에 꼭 알아두어야한다!

 

AJAX를 위한 함수 설정

function ajaxFun(url, method, query, dataType, fn) {
	$.ajax({
		type:method,
		url:url,
		data:query,
		dataType:dataType,
		success:function(data) {
			fn(data);
		},
		beforeSend:function(jqXHR) {
		},
		error:function(jqXHR) {
			console.log(jqXHR.responseText);
		}
	});
}

type은 GET/POST 형식인지 

url은 서버 주소 어디로 가는건지

data는 말그대로 어떤 데이터를 보내는지

dataType JSON인지 HTML인지 

 

 

처음 page 로드할 때 list 불러오기

$(function () {
	listPage(1);
});

function listPage(page) {
	var url = "${pageContext.request.contextPath}/nscore/list";
	var query = "pageNo=" + page;
	var fn = function(data) {
		printJSON(data);
	};
	
	ajaxFun(url, "get", query, "json", fn);
}

데이터를 불러옴

 

AJAX - JSON으로 리스트 찍기

function printJSON(data) {
	$(".score-list").empty(); // 쌓지않고 페이징 처리를 할 것이므로
	
	var dataCount = data.dataCount;
	var total_page = data.total_page;
	var pageNo = data.pageNo;
	var paging = data.paging;
	
	var str;
	$(data.list).each(function(index, item) {
		var hak = item.hak;
		var name = item.name;
		var birth = item.birth;
		var kor = item.kor;
		var eng = item.eng;
		var mat = item.mat;
		var tot = item.tot;
		var ave = item.ave;
		
		str = "<tr align='center' height='33'></tr>";
		$(str).append("<td>"+hak+"</td>")
			.append("<td>"+name+"</td>")
			.append("<td>"+birth+"</td>")
			.append("<td>"+kor+"</td>")
			.append("<td>"+eng+"</td>")
			.append("<td>"+mat+"</td>")
			.append("<td>"+tot+"</td>")
			.append("<td>"+ave+"</td>")
			.append("<td><span class='btn-update'>수정</span> | <span class='btn-delete'>삭제</span></td>")
			.appendTo(".score-list");
	});
	
}

 

AJAX - JSON 자료 등록

hak=1111&name=유니코드&EE3343

$(function() {
	$("form[name=scoreForm]").submit(function() {
		var query = $(this).serialize();
		var url = "${pageContext.request.contextPath}/nscore/insert";
		
		var fn = function(data) {
			var state = data.state;
			if(state === "true") {
				$(".score-input input").each(function() {
					$(this).val("");
				});
				listPage(1);
				$("#hak").focus();
				
			} else if(state === "notUnique") {
				alert("등록된 학번입니다.");
				return false;
			} else if(state ==="false") {
				alert("추가가 실패했습니다.");
				return false;
			}
		};
		ajaxFun(url, "post", query, "json", fn);
		
		return false; // 서버로 전송하지 못하도록
	});
});

 

AJAX - JSON 자료 삭제

$(function() {
	$("body").on("click", ".btn-delete", function() {
		if(! confirm("자료를 삭제하시겠습니까 ? ") ) {
			return false;
		}
		
		// var hak = $(this).closest("tr").children().first().text();
		var hak = $(this).closest("tr").find("td:first").text();
		var url = "${pageContext.request.contextPath}/nscore/delete";
		var query = "hak="+hak;
		
		var fn = function(data) {
			var state = data.state;
			if(state === "true") {
				listPage(1);
			} else {
				alert("자료를 삭제하지 못했습니다.");
			}
		};
		ajaxFun(url, "post", query, "json", fn);
	});
});

 

AJAX - JSON 자료 수정

$(function() {
	var arr = [];
	
	$("body").on("click", ".btn-update", function() {
		var $tds = $(this).closest("tr").children("td");
		var names = ["hak", "name", "birth", "kor", "eng", "mat"];
		
		var s1, s2;
		$($tds).each(function(idx) {
			if( idx != $tds.length-1 ) {
				arr[idx] = $(this).text(); // 배열에 정보를 다 담음
				
				$(this).empty();
				if(idx < 3 || idx > 5) {
					
					s1 = "";
					if(idx <= 5) {
						s1 = " name='"+names[idx]+"' ";
					}
					
					s2 = "";
					if(idx == 0 || idx >= 6 ){
						s2 = " readonly = 'readonly' ";	
					}
					
					$(this).append("<input type='text' "+ s1 + s2 + " value='"+arr[idx]+"'>");
				} else {
					$(this).append("<input type='number' name='"+names[idx]+"' value='"+arr[idx]+"' min='0' max='100'>");
				}
				
			} else {
				$(this).empty();
				$(this).append("<span class='btn-updateOk'>완료</span> | <span class='btn-updateCancel'>취소</span>")
			}
		});
		
		$($tds[1]).find("input").focus();
		
		// 등록하기 줄의 모든 input과 버튼 비활성화 및 숨기기 
		$(".score-input input").prop("disabled", true);
		$(".score-input button").prop("disabled", true);
		$(".score-input").hide(100);
		
	});
	
	// 수정 완료
	$("body").on("click", ".btn-updateOk", function() {
		var query = $("form[name=scoreForm]").serialize();
		var url = "${pageContext.request.contextPath}/nscore/update";
		
		var fn = function(data) {
			var state = data.state;
			if(state === "true") {
				listPage(1);
				
				$(".score-input input").prop("disabled", false);
				$(".score-input button").prop("disabled", false);
				$(".score-input").show(100);
			} else {
				alert("자료를 수정하지 못했습니다.");
				
				$(".score-input input").prop("disabled", false);
				$(".score-input button").prop("disabled", false);
				$(".score-input").show(100);
			}
		};
		ajaxFun(url, "post", query, "json", fn);
	
	});
	
	// 수정 취소
	$("body").on("click", ".btn-updateCancel", function() {
		var $tds = $(this).closest("tr").children("td");
		$($tds).each(function(idx) {
			if(idx != $tds.length-1) {
				$(this).empty();
				$(this).text(arr[idx]);
			} else {
				$(this).empty();
				$(this).append("<span class='btn-update'>수정</span> | <span class='btn-delete'>삭제</span>")
			}
		});
		
		$(".score-input input").prop("disabled", false);
		$(".score-input button").prop("disabled", false);
		$(".score-input").show(100);
	});
});

 

jsp

<div class="container">

	<div class="title">
	   <h3><span>|</span> 성적 처리</h3>
	</div>
	
	<form name="scoreForm">
		<table class="score-table">
			<thead>
				<tr>
					<th width="80">학번</th>
					<th width="100">이름</th>
					<th width="100">생년월일</th>
					<th width="80">국어</th>
					<th width="80">영어</th>
					<th width="80">수학</th>
					<th width="80">총점</th>
					<th width="80">평균</th>
					<th>변경</th>
				</tr>
			</thead>
			<tbody class="score-input">
				<tr align="center" height="33">
					<td><input type="text" name="hak" id="hak" required="required"></td>
					<td><input type="text" name="name" id="name" required="required"></td>
					<td><input type="text" name="birth" id="birth" required="required"></td>
					<td><input type="number" name="kor" id="kor" min="0" max="100" required="required"></td>
					<td><input type="number" name="eng" id="eng" min="0" max="100" required="required"></td>
					<td><input type="number" name="mat" id="mat" min="0" max="100" required="required"></td>
					<td><input type="text" id="tot" readonly="readonly"></td>
					<td><input type="text" id="ave" readonly="readonly"></td>
					<td>
						<button type="submit" id="btnAdd">등록하기</button>
					</td>
				</tr>
			</tbody>
			<tfoot class="score-list"></tfoot>
		</table>
	</form>
	
</div>

 

Spring - Controller

package com.sp.app.nscore;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.sp.app.common.MyUtil;

@Controller("nscore.scoreController")
@RequestMapping("/nscore/*")
public class ScoreController {
	@Autowired
	private ScoreService service;

	@Autowired
	private MyUtil myUtil;
	
	@RequestMapping("main")
	public String main() throws Exception {
		
		return "nscore/main";
	}
	
	// 성적 리스트 : AJAX - JSON으로 결과 전송
	@RequestMapping("list")
	@ResponseBody
	public Map<String, Object> scoreList(
			@RequestParam(value = "pageNo", defaultValue = "1") int current_page,
			@RequestParam(defaultValue = "hak") String condition,
			@RequestParam(defaultValue = "") String keyword) throws Exception {
		
		int rows = 10;
		int dataCount, total_page;
		
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("condition", condition);
		map.put("keyword", keyword);
		
		dataCount = service.dataCount(map);
		total_page = myUtil.pageCount(rows, dataCount);
		if(current_page > total_page) {
			current_page = total_page;
		}
		
		int start = (current_page -1) * rows + 1;
		int end = (current_page * rows);
		map.put("start", start);
		map.put("end", end);
		
		List<Score> list = service.listScore(map);
		
		String paging = myUtil.pagingMethod(current_page, total_page, "listPage");
		
		Map<String, Object> model = new HashMap<String, Object>();
		
		model.put("list", list);
		model.put("dataCount", dataCount);
		model.put("total_page", total_page);
		model.put("pageNo", current_page);
		model.put("paging", paging);
		
		return model;
	}
	
	// 성적추가 : AJAX - JSON으로 결과 전송
	@RequestMapping(value = "insert", method = RequestMethod.POST)
	@ResponseBody // 메소드에서 반환하는 자바객체를 HTTP응답으로 변환하여 전송
	public Map<String, Object> scoreSubmit(
			Score dto
			) throws Exception {
		String state = "false";
		
		try {
			service.insertScore(dto);
			state = "true";
		} catch (DuplicateKeyException e) { // 중복학번일 경우
			state = "notUnique";
		} catch (Exception e) {
		}
		
		// @ResponseBody 애노테이션으로 인하여 Map 객체는 JSON으로 변환되어 전송된다.
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("state", state);
		return model;
	}
	
	// 자료 수정 : AJAX - JSON
	@RequestMapping(value = "update", method = RequestMethod.POST)
	@ResponseBody
	public Map<String, Object> updateScore(
			Score dto) throws Exception {
		String state = "false";
		
		try {
			service.updateScore(dto);
			state = "true";
		} catch (Exception e) {
		}
		
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("state", state);
		return model;
	}
	
	// 자료 삭제 : AJAX -JSON
	@RequestMapping(value = "delete", method = RequestMethod.POST)
	@ResponseBody
	public Map<String, Object> deleteScore(
			@RequestParam String hak) throws Exception {
		String state = "false";
		
		try {
			service.deleteScore(hak);
			state = "true";
		} catch (Exception e) {
		}
		
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("state", state);
		return model;

	}
	
}
package com.sp.app.test1;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller("test1.testController")
@RequestMapping("/test1/*")
public class TestController {
	@RequestMapping("main")
	public String main() throws Exception {	
		return "test1/main";
	}
	
	// Map을 리턴하면 모델을 설정
	@RequestMapping("hello")
	public Map<String, Object> execute() throws Exception {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("msg", "Map 인터페이스를 리턴 타입으로 포워딩 JSP에 값 전달");
		return map;
	}
	
	// void를 리턴하는 경우는 뷰가 필요 없는 경우(다운로드 등)
	@RequestMapping("hello2")
	public void execute2(
			HttpServletRequest req,
			HttpServletResponse resp) throws Exception {
		// HttpServletResponse가 있으면 뷰를 자동 설정 하지 않음.
		try {
			String a = req.getParameter("name");
			
			resp.setContentType("text/html; charset=utf-8");
			PrintWriter out = resp.getWriter();
			out.print("<script>alert('" + a + "님 반가워요');history.back();</script>");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@RequestMapping("calc")
	public String calcForm(
			@RequestParam String num,
			Model model) throws Exception {
		
		try {
			int n = Integer.parseInt(num);
			int s = 0;
			for(int i=1; i<=n; i++) {
				s+=i;
			}
			model.addAttribute("msg", "결과:"+s);
		} catch (Exception e) {
			return "redirect:/test1/error"; // 리다이렉트
		}
		return "test1/hello";
	}
	
	@RequestMapping("error")
	public String errorForm() throws Exception {
		return "test1/error";
	}
}
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<h3> 리턴 타입 </h3>

<p>
	<a href="${pageContext.request.contextPath}/test1/hello">확인</a>
</p>

<p>
	<a href="${pageContext.request.contextPath}/test1/hello2?name=kim">확인</a>
</p>

<p>
	<a href="${pageContext.request.contextPath}/test1/calc?num=a">계산</a>
</p>


<!-- 

  - @RequestMapping 메소드의 리턴 타입
    String : 뷰의 이름 -> ModelAndView 로 변환하여 처리
	ModelAndView : 모델과 뷰의 이름 설정 
	req.setAttribute("이름", 값); <-- 모델
	Map, Model, ModelMap : 모델을 설정, 뷰는 viewResolver가 등록된 경우 뷰의 이름은 자동으로 uri를 이용하여 설정한다.
		uri가 test1/hello 이면 JSP이면 /WEB-INF/views/test1/hello.jsp 가 됨
	void : HttpServletResponse 파라미터가 존재하지 않으면 뷰가 자동으로 설정
 -->


</body>
</html>
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

	<p> 시스템 점검 중입니다. </p>

</body>
</html>
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<p> ${msg} </p>


</body>
</html>

 

package com.sp.app.test4;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller("test4.testController")
@RequestMapping("/test4/*")
public class TestController {

	@GetMapping("write")
	public String form() throws Exception {
		return "test4/write";
	}
	
	@PostMapping("write")
	public String submit(User dto,
			RedirectAttributes rAttr
			) throws Exception {
		String s = "회원가입을 축하합니다.";
		
		// DB 작업
		
		// 리다이렉트한 페이지에 값 넘기기(내부적으로 세션을 활용함)
		rAttr.addFlashAttribute("dto", dto);  // 한번만 dto가 나옴
		rAttr.addFlashAttribute("msg", s);
		
		return "redirect:/test4/complete";
	}
	
	@GetMapping("complete")
	public String complete(@ModelAttribute("dto") User dto) throws Exception {
		// @ModelAttribute("dto") 가 없으면 아래에서는 null이 된다.
		// F5를 눌러 새로 고침을 하면 초기화 되어 아래 이름은 null이 된다.
		// jsp에서 출력되는 내용은 위 메소드의 rAttr.addFlashAttribute("dto", dto); 내용이다. 
		System.out.println(dto.getName() + " : " + dto.getId());
		 
		return "test4/result";
	}
}
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<h3> 리다이렉트한 페이지에 값 넘기기 </h3>

<form action="${pageContext.request.contextPath}/test4/write"
	method="post">
	<p>이름 : <input type="text" name="name"> </p>
	<p>아이디 : <input type="text" name="id"> </p>
	<p>비밀번호 : <input type="text" name="pwd"> </p>
	<p>생년월일 : <input type="text" name="birth"> </p>
	<p> <button type="submit">확인</button> </p>
</form>

</body>
</html>
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<h3> 결 과 </h3>

<p> 이름 : ${dto.name} </p>
<p> 아이디 : ${dto.id} </p>
<p> 비밀번호 : ${dto.pwd} </p>
<p> 생일 : ${dto.birth} </p>

<p> ${msg} </p>

</body>
</html>
package com.sp.app.test2;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller("test2.testController")
@RequestMapping("/test2/*")
public class TestController {

	@RequestMapping("main")
	public String execute() throws Exception {
		return "test2/main";
	}
	
	@GetMapping("header")
	public String headerInfo(
			@RequestHeader("Accept-Language") String lang,
			@RequestHeader("User-Agent") String agent,
			HttpServletRequest req,
			Model model
			) throws Exception {
		
		String referer = req.getHeader("Referer"); // 이전 주소
		if(referer == null) {
			referer = "";
		}
		
		String s = "헤더 정보...<br>";
		s += "클라이언트 언어 정보 : " + lang + "<br>";
		s += "클라이언트 브라우저 및 OS : " + agent + "<br>";
		s += "이전 클라이언트 URL : "+ referer;
		
		model.addAttribute("msg", s);
		
		return "test2/result";
	}
	
	@RequestMapping("setCookie")
	public String cookieSet(
			HttpServletResponse resp
			) throws Exception {
		// 쿠키 설정하기(유효시간 : 브라우저를 닫으면 쿠키가 제거됨. 기본)
		Cookie ck = new Cookie("subject", "spring");
		resp.addCookie(ck);
		
		return "redirect:/test2/main";
	}
	
	// @CookieValue : 쿠키 가져오기. 기본 required는 true로 쿠키가 없으면 400에러
	// 	defaultValue 속성으로 쿠키값이 없는 경우 초기값 부여 가능
	@RequestMapping("getCookie")
	public String cookieGet(
			@CookieValue(name="subject", defaultValue = "") String subject,
			Model model
			) throws Exception {
		
		String s = "쿠키내용 : " + subject;
		
		model.addAttribute("msg", s);
		return "test2/result";
	}
	
}
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<p>
	<a href="${pageContext.request.contextPath}/test2/header"> 헤더정보확인 </a>
</p>

<p>
	<a href="${pageContext.request.contextPath}/test2/setCookie"> 쿠키설정 </a>
</p>

<p>
	<a href="${pageContext.request.contextPath}/test2/getCookie"> 쿠키확인 </a>
</p>


</body>
</html>
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<p>
 ${msg}
</p>

</body>
</html>
package com.sp.app.join;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

/*
 @SessionAttributes
 - 모델의 객체를 세션에 저장하여 뷰(jsp)에서 공유
 - Controller에서 사용
 - 용도
 	스프링 form태그 라이브러리를 이용할 경우
 	여러 단계에 걸쳐 입력된 값을 처리할 경우(지속적으로 값을 유지)
 		double submit 방지 - 브라우저의 뒤로가기 안됨
 
 */

@SessionAttributes("user") // 클래스명과같아야함
@Controller("join.joinController")
@RequestMapping("/join/*")
public class JoinController {
	
	@ModelAttribute("user") // user 이름으로 모든 페이지가 공유할 수 있는 객체를 만듦
	public User command() {
		return new User(); // 세션에 저장할 객체 메모리 할당
	}
	
	@RequestMapping(value = "main", method = RequestMethod.GET)
	public String joinForm(@ModelAttribute("user") User user) throws Exception {
		// 회원가입 처음화면(step1.jsp) 출력
		return "join/step1";
	}
	
	// @ModelAttribute("user")는 @SessionAttributes("user")에서 설정한
	// 설정이름이 동일하므로 세션에 저장된 user을 사용함
	// User 클래스명 첫글자가 소문자인 이름과 동일한 경우 생략가능
	@RequestMapping(value = "step1", method = RequestMethod.POST)
	public String step1Submit(@ModelAttribute("user") User user) throws Exception {
		
		// 회원가입 두번째 화면 출력
		return "join/step2";
	}
	
	@RequestMapping(value = "step2", method = RequestMethod.POST)
	public String step2Submit(@ModelAttribute("user") User user,
			SessionStatus sessionStatus,
			Model model
			) throws Exception {
		// 회원가입 정보를 DB에 저장
		
		String s = "아이디:" +user.getId() + "<br>";
		s += "이름:"+user.getName() + "<br>";
		s += "이메일:"+user.getEmail() + "<br>";
		s += "패스워드:"+user.getPwd() + "<br>";
		s += "전화번호:"+user.getTel() + "<br>";
		
		// 세션에 저장된 내용 지우기
		sessionStatus.setComplete();
		model.addAttribute("msg", s);
		return "join/complete";
	}
	
}
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<form method="post"
	action="${pageContext.request.contextPath}/join/step1">
	
	<p> 이름 : <input type="text" name="name" value="${user.name}"> </p>
	<p> 이메일 : <input type="text" name="email" value="${user.email}"> </p>
	<p>
		<button type="submit">다음단계</button>
	</p>

</form>

</body>
</html>
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<form method="post" 
	action="${pageContext.request.contextPath}/join/step2">
	
	<p> 아이디 : <input type="text" name="id" value="${user.id}"> </p>
	<p> 패스워드 : <input type="password" name="pwd"> </p>
	<p> 전화번호: <input type="text" name="tel" value="${user.tel}"> </p>
	<p>
		<button type="button"
			onclick="location.href='${pageContext.request.contextPath}/join/main';" >이전단계</button>
		<button type="submit">회원가입</button>
	</p>

</form>

</body>
</html>
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<h3> 환영합니다. </h3>
<p>
	${msg}
</p>

</body>
</html>
package com.sp.app.note;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller("note.noteController")
@RequestMapping("/note/*")
public class NoteController {
	@Autowired
	private NoteService service;
	
	@RequestMapping(value="request", method=RequestMethod.GET)
	public String form(Model model) {
		
		List<Note> listFriend = service.listFriend();
		
		model.addAttribute("friends", listFriend);
		
		return "note/write";
	}
	
	/*
	@RequestMapping(value = "request", method = RequestMethod.POST)
	public String formSubmit(Note dto, Model model) throws Exception {
		
		model.addAttribute("msg", dto.getMsg());
		model.addAttribute("list", dto.getRecipient());
		return "note/result";
	}
	*/
	@RequestMapping(value = "request", method = RequestMethod.POST)
	public String formSubmit(Note dto, Model model) throws Exception {
		
		model.addAttribute("dto", dto);
		return "note/result";
	}
	
}
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<style type="text/css">
* {
	margin: 0; padding: 0;
    box-sizing: border-box;
}

body {
	font-size: 14px;
	font-family: "맑은 고딕", 나눔고딕, 돋움, sans-serif;
}

a {
  color: #000;
  text-decoration: none;
  cursor: pointer;
}
a:active, a:hover {
	text-decoration: underline;
	color: #F28011;
}

.btn {
	color: #333;
	border: 1px solid #333;
	background-color: #fff;
	padding: 4px 10px;
	border-radius: 4px;
	font-weight: 500;
	cursor:pointer;
	font-size: 14px;
	font-family: "맑은 고딕", 나눔고딕, 돋움, sans-serif;
	vertical-align: baseline;
}
.btn:hover, .btn:active, .btn:focus {
	background-color: #e6e6e6;
	border-color: #adadad;
	color:#333;
}
.boxTF {
	border: 1px solid #999;
	padding: 5px 5px;
	background-color: #fff;
	border-radius: 4px;
	font-family: "맑은 고딕", 나눔고딕, 돋움, sans-serif;
	vertical-align: baseline;
}
.selectField {
	border: 1px solid #999;
	padding: 4px 5px;
	border-radius: 4px;
	font-family: "맑은 고딕", 나눔고딕, 돋움, sans-serif;
	vertical-align: baseline;
}

.boxTA {
    border:1px solid #999;
    height:150px;
    padding:3px 5px;
    border-radius:4px;
    background-color:#fff;
	font-family: "맑은 고딕", 나눔고딕, 돋움, sans-serif;
	resize : none;
	vertical-align: baseline;
}

textarea:focus, input:focus {
	outline: none;
}

.title {
	width:100%;
	font-size: 16px;
	font-weight: bold;
	padding: 13px 0;
}

.container {
    width: 400px;
    margin: 30px auto;
}

.note-table {
	width: 100%;
	border-spacing: 0;
	border-collapse: collapse;
}

.note-table th, .note-table td {
	padding: 5px 0;
}

.left {
	text-align: left;
	padding-left: 7px;
}
.center {
	text-align: center;
}
.right {
	text-align: right;
	padding-right: 7px;
}
</style>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
$(function() {
	$("#btnRight").click(function() {
		$("#friends option:selected").each(function() {
			$(this).appendTo("#recipient");
		});
	});
	
	$("#btnAllRight").click(function() {
		$("#friends option").each(function() {
			$(this).appendTo("#recipient");
		});
	});
	
	$("#btnLeft").click(function() {
		$("#recipient option:selected").each(function() {
			$(this).appendTo("#friends");
		});
	});
	
	$("#btnAllLeft").click(function() {
		$("#recipient option").each(function() {
			$(this).appendTo("#friends");
		});
	});$
});


function sendOk() {
	if($("#recipient option").length === 0) {
		alert("받는 사람을 먼저 추가하세요...");
		return;
	}
	
	$("#recipient option").prop("selected", true);
	
	if(! $("#msg").val().trim() ) {
		alert("내용을 입력하세요...");
		$("#msg").focus();
		return;
	}
	
	var f = document.noteForm;
	
	f.action = "${pageContext.request.contextPath}/note/request";
	f.submit();
}

</script>
</head>
<body>

<div class="container">
	<div class="title">
	   <h3><span>|</span> 쪽지 보내기</h3>
	</div>

	<form name="noteForm" method="post">
	<table class="note-table">
	<tr>
	    <td width="150"><span>친구목록</span></td>
	    <td width="100">&nbsp;</td>
	    <td width="150"><span>받는사람</span></td>
	</tr>
	
	<tr>
	    <td class="left">
	        <select name="friends" id="friends" multiple="multiple" class="selectField" style="width:130px; height:120px;">
	        	<c:forEach var="vo" items="${friends}">
	        		<option value="${vo.userId}">${vo.userName}</option>
	        	</c:forEach>
	        </select>
	    </td>
	    <td class="center">
		    <button type="button" class="btn" id="btnRight" style="display:block; width:80px;"> &gt; </button>
		    <button type="button" class="btn" id="btnAllRight" style="display:block;width:80px;"> &gt;&gt; </button>
		    <button type="button" class="btn" id="btnLeft" style="display:block;width:80px;"> &lt; </button>
		    <button type="button" class="btn" id="btnAllLeft" style="display:block;width:80px;"> &lt;&lt; </button>
	    </td>
	    <td class="left">
	        <select name="recipient" id="recipient" multiple="multiple" class="selectField" style="width:130px; height:120px;">
	        </select>
	    </td>
	</tr>
	<tr>
	    <td colspan="3">
	       <span>메시지</span>
	    </td>
	</tr>
	<tr>
	    <td colspan="3" class="left">
	        <textarea name="msg" id="msg" class="boxTA" style="height:60px; width: 98%;"></textarea>
	    </td>
	</tr>
	</table>
	
	<table class="table">
	<tr>
	    <td class="right">
	        <button type="button" class="btn" onclick="sendOk();"> 쪽지보내기 </button>
	    </td>
	</tr>
	</table>
	</form> 
</div>

</body>
</html>
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<style type="text/css">
* {
	margin: 0; padding: 0;
    box-sizing: border-box;
}

body {
	font-size: 14px;
	font-family: "맑은 고딕", 나눔고딕, 돋움, sans-serif;
}

a {
  color: #000;
  text-decoration: none;
  cursor: pointer;
}
a:active, a:hover {
	text-decoration: underline;
	color: #F28011;
}

.title {
	width:100%;
	font-size: 16px;
	font-weight: bold;
	padding: 13px 0;
}

.container {
    width: 400px;
    margin: 30px auto;
}
</style>

</head>
<body>

<div class="container">

    <div style="title">
       <h3><span>|</span> 쪽지 보내기 결과</h3>
    </div>

    <table style="width: 100%; margin: 10px auto 0px;">
    <tr height="30">
        <td width="100">받는사람</td>
        <!-- 
        <c:forEach var="vo" items="${list}" varStatus="status">
	        <td>${vo}</td>        
        </c:forEach>
         -->        
        <td>
	        <c:forEach var="vo" items="${dto.recipient}" varStatus="status">
		        ${vo}&nbsp;&nbsp;
	        </c:forEach>
        </td>                
    </tr>
    
    <tr>
        <td valign="top" style="margin-top: 5px;">메시지</td>
        <td>
        	<span style="white-space:pre;">${dto.msg}</span>
        </td>
    </tr>
    </table>
</div>

</body>
</html>
package com.sp.app.blog;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller("blog.blogController")
@RequestMapping("/blog/*")
public class BlogController {
	@Autowired
	private BlogService service;
	
	@RequestMapping(value = "main", method = RequestMethod.GET)
	public String main(Model model) {
		List<Blog> list = service.listBlog();
		
		model.addAttribute("listBlog", list);
		
		return "blog/main";
	}

/*
 	@PathVariable 애노테이션을 이용한 URI 템플릿
 	- URI 템플릿을 이용하여 REST 방식의 URL 매칭처리를 위한 애노테이션
 	- 블로그, 카페 등을 만들 때 유용
 	- 방법
 	  @RequestMapping 애노테이션의 값으로 {템플릿변수}를 사용
 	  @PathVariable를 이용하여  {템플릿변수}와 동일한 이름을 갖는 파라미터를 추가한다.
 		
 */
	@RequestMapping(value = "{blogIdx}/home")
	public String execute(
			@PathVariable long blogIdx,
			Model model) throws Exception {
		
		Blog dto = null;
		for(Blog vo : service.listBlog()) {
			if(vo.getBlogIdx() == blogIdx) {
				dto = vo;
				break;
			}
		}
		
		model.addAttribute("dto", dto);
		
		return "blog/home";
	}
	
}
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">

<style type="text/css">
* {
	margin: 0; padding: 0;
}

body {
	font-size: 13px; font-family: 맑은 고딕, 돋움;
}

div h3 {
	padding: 5px 5px;
}

div ul>li {
	list-style: none;
	padding: 5px 5px;
}

</style>

</head>
<body>

<div style="width: 500px; margin: 30px auto;">
	<h3> 블로그 리스트 </h3>
	
	<ul>
		<c:forEach var="dto" items="${listBlog}">
			<li>
				<a href="${pageContext.request.contextPath}/blog/${dto.blogIdx}/home">${dto.blogName}</a>
			</li>
		</c:forEach>
	</ul>
	
</div>

</body>
</html>​
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
</head>
<body>

<h3>${dto.nickName} 블로그</h3>

<p> 블로그 주제 : ${dto.blogName} </p>
<p> ${dto.nickName} 블로그입니다. 방문하셨으면 방명록을 써주세요. </p>
<p>
  <a href="${pageContext.request.contextPath}/blog/main">돌아가기</a>
</p>

</body>
</html>

+ Recent posts