** @RestController
=> spring 4.0 부터 제공하는 Controller로 요청을 처리하는 메소드에서 String을 리턴하면 String을 그대로 클라이언트에게 전송을 하고 DTO 또는 Map 그리고 List 나 배열을 리턴하면 JSON 형식으로 변환해서 클라이언트에게 전송
=> String을 리턴하는 경우는 csv를 제공하고자 하는 경우이고 그 이외 자료형은 JSON 리턴을 위해서 사용
** csv 형식의 문자열을 출력
1. JSON 출력을 위한 Controller 클래스를 추가
=> kr.co.pk.item.controller
package kr.co.pk.item.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import kr.co.pk.item.service.ItemService;
@RestController
public class JSONController {
@Autowired
private ItemService itemService;
//문자열을 출력하는 요청 처리
@RequestMapping(value="dataformat.csv", method = RequestMethod.GET)
public String csv() {
String result = "dataformat:scv,xml,json";
return result;
}
}
2. home.jsp 파일에 위의 요청을 호출하는 link를 추가
<li>
<a href="dataformat.csv" class="menu">문자열 출력</a>
</li>
** JSON 출력
1. 앞에서 만든 Controller 클래스에 요청 처리 메소드를 추가
package kr.co.pk.item.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import kr.co.pk.item.service.ItemService;
@RestController
public class JSONController {
@Autowired
private ItemService itemService;
//문자열을 출력하는 요청 처리
@RequestMapping(value="dataformat.csv", method = RequestMethod.GET)
public String csv() {
String result = "dataformat:scv,xml,json";
return result;
}
//JSON 출력을 위한 메소드
@RequestMapping(value="dataformat.json", method = RequestMethod.GET)
public Map<String, Object> json() {
List<String> list = new ArrayList<String>();
list.add("csv:구분자로 구분된 텍스트");
list.add("xml:태그로 데이터를 표현");
list.add("json:자바스크릅트의 데이터 표현법을 이용해서 데이터를 표현");
Map<String, Object> result = new HashMap<String, Object>();
result.put("dataformat", list);
return result;
}
}
2. home.jsp 파일에 위의 요청을 호출하는 link를 추가
<li>
<a href="dataformat.json" class="menu">데이터 확인</a>
</li>
** ajax를 이용한 JSON 출력
ajax(Asynchronous JAvascript Xml)
=> 비동기적으로 서버의 데이터를 가져와서 사용
=> 전체 화면 갱신없이 데이터를 가져와서 화면의 일부분을 수정하기 위해서 주로 사용
모바일처럼 네트워크의 안정성을 담보할 수 없는 상황의 경우 전체 화면을 갱신을 하는 도중 네트워크가 연결해제되면 화면에 아무것도 출력을 하지 못한다.
ajax를 이용하면 서버의 데이터르 가져와서 출력하는 부분은 나중에 보여지고 다른 부분은 계속 출력
가져와야 하는 데이터가 많은 경우에도 전체 화면 갱신의 형태를 사용하게 되면 데이터를 전부 가져올 때까지 화면에 아무것도 출력되지 않는다.
=> 최근에는 SPA(하나의 페이지로 모든 콘텐츠를 제공 - Single Page Application)가 유행인데 ajax를 기반으로 생성
1.
package kr.co.pk.item.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import kr.co.pk.item.service.ItemService;
@RestController
public class JSONController {
@Autowired
private ItemService itemService;
//문자열을 출력하는 요청 처리
@RequestMapping(value="dataformat.csv", method = RequestMethod.GET)
public String csv() {
String result = "dataformat:scv,xml,json";
return result;
}
//JSON 출력을 위한 메소드
@RequestMapping(value="dataformat.json", method = RequestMethod.GET)
public Map<String, Object> json() {
List<String> list = new ArrayList<String>();
list.add("csv:구분자로 구분된 텍스트");
list.add("xml:태그로 데이터를 표현");
list.add("json:자바스크릅트의 데이터 표현법을 이용해서 데이터를 표현");
Map<String, Object> result = new HashMap<String, Object>();
result.put("dataformat", list);
return result;
}
//JSON 출력을 위한 메소드
@RequestMapping(value="ajax.json", method = RequestMethod.GET)
public Map<String, Object> ajaxjson() {
//테이블 형식의 데이터 구조
//Map 대신에 DTO를 사용해도 된다.
List<String> list = new ArrayList<String>();
Map<String, Object> map = new HashMap<String, Object>();
map.put("Encapsulation", "캡슐화 - 불필요한 부분은 숨기고 필요한 부분만 외부로 노출해서 사용하는 것으로 정보 은닉을 클래스를 만드는 것과 인스턴스를 만드는 것이 캡슐화");
list.add(map);
map = new HashMap<String, Object>();
map.put("Inheritance", "상속 - 하위 클래스가 상위 클래스의 모든 것을 물려받는 것으로 재사용성이 증가하고 유지보수가 편리해짐 "
+ "직접 만든 클래스의 경우는 대부분의 경우 클래스를 여러개 만들다가 동일한 부분이 잇어 상위 클래스를 생성 - "
+ "이런 이유 때문에 다이어그램에서는 하위 클래스에 상위 클래스로 화살표가 만들어짐 - is a 관계 또는 generalizationi이라고도 함");
list.add(map);
map = new HashMap<String, Object>();
map.put("Polymorphism", "다형성 - 동일한 메시지에 대하여 다르게 반응하는 성질로 프로그래밍에서는 동일한 코드가 "
+ "어떤 인스턴스가 대입되었느냐에 따라 다른 클래스의 메소드를 호출하는 것 - 인터페이스나 추상 클래스에 메소드를 선언하고 "
+ "다른 클래스들이 인터페이스나 추상클래스를 상속받아서 메소드를 재정의 하는 방식으로 작성"
+ " - Inheritance와 MethodOverriding을 이용해서 구현");
list.add(map);
Map<String, Object> result = new HashMap<String, Object>();
result.put("oop", list);
return result;
}
}
2. home.jsp 파일에 위의 요청을 ajax로 처리하는 코드를 작성
=> Map(List(Map)) : 객체(배열(객체))
1) ajax요청을 위한 DOM을 작성
<li><a href="#" class="menu" id="ajaxbtn">ajax 요청</a></li>
2) 스크립트 코드를 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- jsp를 이용해서 데이터를 출력할 때는 이 코드는 거의 필수 -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ITEM</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/css/home.css">
</head>
<body>
<div align="center" class="body">
<h2>상품 목록</h2>
<table border="1">
<tr class="header">
<th width="80">상품 ID</th>
<th width="300">상품 이름</th>
<th width="100">상품 가격</th>
</tr>
<c:forEach var="item" items="${list}">
<tr class="record">
<td width="80">${item.itemid}</td>
<td width="300">
<a href="detail/${item.itemid}">${item.itemname}</a>
</td>
<td width="100" align="right">
${item.price}원</td>
</tr>
</c:forEach>
</table>
<ul>
<li><a href="fileview" class="menu">파일 목록 보기</a></li>
<li><a href="item.xls" class="menu">엑셀 출력</a></li>
<li><a href="item.pdf" class="menu">PDF 출력</a></li>
<li><a href="item.json" class="menu">ITEM JSON 출력</a></li>
<li><a href="dataformat.csv" class="menu">문자열 출력</a></li>
<li><a href="dataformat.json" class="menu">데이터 확인</a></li>
<li><a href="#" class="menu" id="ajaxbtn">ajax 요청</a></li>
</ul>
</div>
<div id="disp"></div>
<script>
document.getElementById("ajaxbtn").addEventListener("click", function(event){
//ajax 객체를 생성
var request = new XMLHttpRequest();
//요청할 URL을 생성
request.open('get', 'ajax.json');
//전송
request.send('');
//데이터를 가져오면 호출될 함수를 설정
request.addEventListener('load', function(data){
//데이터 확인 - 404와 관련된 HTML에러가 발생하면 open에 작성한 URL과 Controller의 RequestMapping에 작성한 URL을 비교
//alert(data.target.responseText)//xml이면 responseXML
//JSONParsing
var result = JSON.parse(data.target.responseText);
var list = result.oop;
document.getElementById("disp").innerHTML += list[0].Encapsulation;
document.getElementById("disp").innerHTML += list[1].Inheritance;
document.getElementById("disp").innerHTML += list[2].Polymorphism;
});
});
</script>
</body>
</html>
** Marshalling
=> RPC(Remote Procedure Call) : 원격에 있는 프로시저를 호출
=> 대표적인 RPC가 Web Server
** XML 출력
=> MarshallingView 이용해서 XML 형식으로 가능
=> 아직도 RSS(Rich Site Summary - 뉴스나 블로그 처럼 실시간으로 변경되는 데이터를 제공하는 콘첸츠 표현방식) 에서는 xml 를 주로 이용
=> 직접 서버를 만든다면 JSON을 이용하는 것이 효율적
=> XML parsing은 반드시 해 두어야 한다.
=> BackEnd 개발자는 데이터를 생성하는 것과 파싱하는 것을 모두 할 수 있어야 한다.
CSV, XML, JSON 다 할줄 알아야한다.
=> FrontEnd 개발자는 데이터 파싱하는 것을 할 수 있어야 한다.
=> XML을 출력할 때는 DTO 클래스
** ItemServiceImpl의 allitem 메소드의 결과를 XML로 출력
1. XML pom.xml
2. home.jsp 파일에 XML 요청을 생성
<li><a href="item.xml" class="menu">XML 요청</a></li>
3. DTO 클래스를 수정 - Item
@XmlAccessorType(XmlAccessType.FIELD)
//출력할 속성과 순서를 설정
@XmlType(name ="", propOrder= {"itemid", "itemname", "price", "description", "pictureurl"})
public class Item {
package kr.co.pk.item.domain;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
@XmlAccessorType(XmlAccessType.FIELD)
//출력할 속성과 순서를 설정
@XmlType(name ="", propOrder= {"itemid", "itemname", "price", "description", "pictureurl"})
public class Item {
//숫자 데이터의 경우 null 이 될 가능성이 있는 경우는 Wrapper 클래스 사용
private Integer itemid;
private String itemname;
private Integer price;
private String description;
private String pictureurl;
public Integer getItemid() {
return itemid;
}
public void setItemid(Integer itemid) {
this.itemid = itemid;
}
public String getItemname() {
return itemname;
}
public void setItemname(String itemname) {
this.itemname = itemname;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPictureurl() {
return pictureurl;
}
public void setPictureurl(String pictureurl) {
this.pictureurl = pictureurl;
}
@Override
public String toString() {
return "Item [itemid=" + itemid + ", itemname=" + itemname + ", price=" + price + ", description=" + description
+ ", pictureurl=" + pictureurl + "]";
}
}
4. DTO 클래스의 List를 속성으로 갖는 클래스를 생성 - ItemReport
package kr.co.pk.item.domain;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlAccessorType(XmlAccessType.FIELD)
//전채 태그를 설정
@XmlRootElement(name="ITEMLIST")
public class ItemReport {
//DTO 1개만 출력될 태그 설정
@XmlElement(name="ITEM")
private List<Item> list;
//getter and setter
public List<Item> getList() {
return list;
}
public void setList(List<Item> list) {
this.list = list;
}
}
5. HomeCotroller에 xml 요청을 처리하는 메소드를 생성
package kr.co.pk;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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;
import kr.co.pk.item.domain.Item;
import kr.co.pk.item.domain.ItemReport;
import kr.co.pk.item.service.ItemService;
@Controller
public class HomeController {
@Autowired
private ItemService itemService;
@RequestMapping(value = "fileview", method = RequestMethod.GET)
public String fileview(HttpServletRequest request, HttpServletResponse response) {
itemService.fileview(request, response);
return "fileview";
}
// 파일 다운로드 뷰를 출력하는 요청
@RequestMapping(value = "download", method = RequestMethod.GET)
public String download(HttpServletRequest request, HttpServletResponse response, Model model) {
// 아래 문장이 출력되지 않으면 View에서 요청과 Controller 처리 메소드의
// RequestMapping이 맞지 않는 것입니다.
// System.out.println("Controller");
// 파라미터 읽기
String filename = request.getParameter("filename");
// 이 줄에서 null 이 리턴되면 ? 뒤에 이름과 위의 이름이 안맞는 것입니다.
// System.out.println("파라미터:" + filename);
// 데이터를 저장
model.addAttribute("filename", filename);
return "download";
}
// 엑셀로 출력하는 요청을 처리하는 메소드
@RequestMapping(value = "item.xls", method = RequestMethod.GET)
public String excel(
HttpServletRequest request,
HttpServletResponse response,
Model model) {
//ITEM 테이블의 데이터를 전부 읽어오는 서비스를 호출
itemService.allitem(request, response);
//Model에 데이터 저장
model.addAttribute("list", request.getAttribute("list"));
//출력할 뷰를 결정
return "excel";
}
// PDF로 출력하는 요청을 처리하는 메소드
@RequestMapping(value = "item.pdf", method = RequestMethod.GET)
public String pdf(
HttpServletRequest request,
HttpServletResponse response,
Model model) {
//ITEM 테이블의 데이터를 전부 읽어오는 서비스를 호출
itemService.allitem(request, response);
//Model에 데이터 저장
model.addAttribute("list", request.getAttribute("list"));
//출력할 뷰를 결정
return "pdf";
}
//json 출력을 위한 메소드
@RequestMapping(value = "item.json", method = RequestMethod.GET)
public String json(
HttpServletRequest request,
HttpServletResponse response,
Model model) {
//ITEM 테이블의 데이터를 전부 읽어오는 서비스를 호출
itemService.allitem(request, response);
//Model에 데이터 저장
model.addAttribute("list", request.getAttribute("list"));
//출력할 뷰를 결정
return "itemlist";
}
//xml 출력을 위한 메소드
@RequestMapping(value = "item.xml", method = RequestMethod.GET)
public String xml(
HttpServletRequest request, HttpServletResponse response, Model model) {
//ITEM 테이블의 데이터를 전부 읽어오는 서비스를 호출
itemService.allitem(request, response);
List<Item> list = (List<Item>)request.getAttribute("list");
ItemReport itemReport = new ItemReport();
itemReport.setList(list);
//Model에 데이터 저장
model.addAttribute("list", itemReport);
//출력할 뷰를 결정
return "itemxml";
}
}
6. servlet-context.xml 파일에 Controller에서 리턴한 뷰 이름을 XML로 출력하도록 설정
<!-- XML 출력 -->
<beans:bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<beans:property name="classesToBeBound">
<beans:list>
<beans:value>kr.co.pk.item.domain.ItemReport</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean id="itemxml" class="org.springframework.web.servlet.view.xml.MarshallingView">
<beans:property name="marshaller" ref="marshaller"/>
<beans:property name="modelkey" value="list"/>
</beans:bean>
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- Controller가 처리하지 못하는 URL은 WAS가 처리하도록 하는 설정 -->
<default-servlet-handler/>
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Controller가 리턴한 뷰 이름을 가지고 실제 출력할 뷰를 설정하는 뷰 리졸버
jstlview(jsp)로 출력 -->
<beans:bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
<beans:property name="order" value="1" />
</beans:bean>
<context:component-scan base-package="kr.co.pk" />
<!-- BeanNameViewResolver 설정 -->
<beans:bean
class="org.springframework.web.servlet.view.BeanNameViewResolver">
<beans:property name="order" value="0" />
</beans:bean>
<!-- download 출력 설정 -->
<beans:bean id="download" class="kr.co.pk.view.DownloadView" />
<!-- Excel 출력 -->
<beans:bean id="excel" class="kr.co.pk.view.ExcelView" />
<!-- PDF 출력 -->
<beans:bean id="pdf" class="kr.co.pk.view.PdfView" />
<!-- JSON 출력 -->
<beans:bean id="itemlist"
class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
<!-- XML 출력 -->
<beans:bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<beans:property name="classesToBeBound">
<beans:list>
<beans:value>kr.co.pk.item.domain.ItemReport</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean id="itemxml" class="org.springframework.web.servlet.view.xml.MarshallingView">
<beans:property name="marshaller" ref="marshaller"/>
<beans:property name="modelKey" value="list"/>
</beans:bean>
</beans:beans>
** Error
1. 물리적 에러 : 문법 오류로 인해서 애플리케이션이 실행이 안되는 것
=> 대다수의 IDE는 문법오류가 발생하면 에러 표시를 출력
=> 컴파일을 하고 실행되는 언어들은 IDE가 코드를 작성하면 그 때 그때 컴파일을 해보고 오류 표시를 해준다.
소스 파일을 만들었는데 ClassNotFoundException이 발생하면 IDE가 컴파일을 못한 것이므로 클래스를 지우고 다시 생성하는 것이 좋다.
=> 컴파일을 하지 않고 한 줄 씩 읽어가면서 실행하는 스크립트 언어들은 IDE에서 에러 표시를 하지 않는다.
=> 이 경우는 소스 코드를 읽어가면서 에러 나는 부분을 직접 수정해야 한다.
2. 논리적 에러 : 문법 상의 오류는 없어서 실행은 되지만 결과가 예상과 다르게 만들어지는 것
=> 로직을 잘못 설계해서 발생하는 에러이므로 디버깅(메모리의 값 확인)을 해서 잘못된 로직을 수정
3. 예외(Exception) : 문법상의 오류는 없어서 실행은 되지만 특수한 상황이 발생하면 프로그램이 중단되는 것
=> 예외 메시지를 읽고 수정을 해야 한다.
=> 소스 코드에서 이런 문제가 발생한다면 예외 처리를 해서 예외가 발생하다라도 애플리케이션을 계속 수행하도록 할지를 결정해야 하고 이런 예외는 별도의 파일에 기록을 해두는 것이 좋다.
예외 중에는 개발자가 해결할 수 있는 예외가 있고 개발자가 해결할 수 없는 예외도 있는데 해결할 수 없는 예외들은 나중에 다른 방법으로 해결을 해야하기 때문에 기록을 해두는 것이 중요
// if처럼 동작 - catch 는 여러개 생성 가능
try{
예외가 발생할 가능성이 있는 코드
}catch(예외변수) {
예외가 발생했을 때 수행할 내용
}finally{
예외 발생 여부에 상관없이 수행할 내용
}
4. 단언(Assertion) : 특수한 상황이 발생하면 강제로 예외를 발생시켜 애플리케이션을 중지 시키는 것
=> 조건을 만족하는 경우에만 애플리케이션이 수행되도록 하는 것
** Web 에서의 예외 처리
=> Web Page에서는 예외가 발생하면 WAS가 가지고 있는 예외 페이지가 출력되도록 되어 있다.
=> 되도록이면 예외가 발생했을 때 별도의 페이지를 출력해주는 것이 좋다.
** Server에서의 예외처리
=> 데이터를 넘겨주는 서버를 구축한다면 예외가 발생하거나 잘못된 파라미터를 대입했을 때 여기에 해당하는 데이터를 만들어 주는 것이 좋다.
** Web Programming에서의 예외 페이지를 출력
1. jsp 페이지나 web.xml 파일에 예외가 발생하면 보여질 페이지를 설정
2. Controller에서 예외가 발생했을 때 사용할 페이지를 직접 설정
@ExceptionHandler 어노테이션을 이용
**예외를 발생시키기
1. home.jsp 파일에 링크 추가
<li><a href="exception" class="menu">예외발생</a></li>
2. HomeController에 exception 요청이 왔을 때 처리할 메소드를 생성
// exception 요청이 get 방식으로 온 경우 처리하기 위한 메소드
@RequestMapping(value = "exception", method = RequestMethod.GET)
public String exception(HttpServletRequest request, HttpServletResponse response, Model model) {
// 출력할 뷰를 결정
return "exception";
}
3. exception.jsp 파일을 만들고 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>예외처리</title>
</head>
<body>
<form method="post">
나누어지는 수<input type="text" name="num1"/><br/>
나누는 수<input type="text" name="num2"/><br/>
<input type="submit" value="전송" />
</form>
</body>
</html>
4. HomeController에 위의 페이지에서 전송을 눌렸을 때 처리할 메소드를 생성
//exception 요청이 POST 방식으로 온 경우 처리하기 위한 메소드
@RequestMapping(value = "exception", method = RequestMethod.POST)
public String exceptionprocess(HttpServletRequest request, HttpServletResponse response, Model model) {
//파라미터 읽기
String num1 = request.getParameter("num1");
String num2 = request.getParameter("num2");
int result = Integer.parseInt(num1) / Integer.parseInt(num2);
//결과 저장
model.addAttribute("result", result);
//결과 출력 페이지 설정
return "result";
}
5. 결과를 출력할 result.jsp 파일을 만들고 내용을 출력
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
${result}
</body>
6. 숫자가 아닌 데이터나 두번째 입력란에 0을 대입하면 예외가 발생한다.
=> 실제 프로젝트라면 전송을 누를 때 유효성 검사를 수행해서 예외가 발생하지 않도록 해야 한다.
=> 예외가 발생했을 때 WAS는 예외 메시지와 printStackTrace를 출력하는데 유저는 이 메시지를 볼 필요가 없다.
개발자는 이 메시지를 보고 예외를 수정하지만 사용자에게는 다른 형태의 예외 페이지를 출력해줘야 한다.
** Error 페이지 작성
=> jsp 파일에서는 isErrorPage 속성을 true로 설정하면 exception 객체를 사용할 수 있는 페이지가 된다.
=> IE 하위 버전에서는 에러페이지의 내용이 512바이트가 안되면 IE 자체 에러 페이지를 출력한다.
edge는 다른 브라우저와 동일하게 처리
** 에러 페이지 출력 설정
1. views 디렉토리에 error라는 디렉토리를 생성하고 exception.jsp 파일
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- exception 객체를 사용할 수 있는 페이지 -->
<%@ page isErrorPage = "true" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>예외 페이지</title>
</head>
<body>
서버에 장애가 발생했습니다.<br/>
빠른 시간 내에 조치하겠습니다.<br/>
예외내용:${result}
</body>
</html>
2. HomeController 클래스에 예외가 발생하면 error/exception.jsp 파일을 출력하도록 하는 설정을 추가
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- exception 객체를 사용할 수 있는 페이지 -->
<%@ page isErrorPage = "true" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>예외 페이지</title>
</head>
<body>
서버에 장애가 발생했습니다.<br/>
빠른 시간 내에 조치하겠습니다.<br/>
예외내용:${result}
</body>
</html>
=> 별도의 클래스를 만들어서 클래스이름 상단에 @ControllerAdvice("패키지이름")을 추가하고 메소드를 만들면 패키지이름에서 발생한 예외만 처리하게 된다.
** Spring MVC View 설정
=> 단순한 페이지 이동은 Controller에 처리하는 메소드를 만들지 않고 servlet-contex.xml 파일에 아래 태그를 추가해도 된다.
<view-controller path="요청경로" view-name="뷰 이름"/>
=> Controller의 URl 패턴을 /* 이나 / 로 설정을 하게 되면 상대경로로 만들어진 모든 요청을 Controller 가 처리할려고 하고 이 때 Controller가 처리하지 못하면 404에러가 발생한다.
위의 경우 404 에러를 방지할려면 servlet-context.xml 파일에 <default-servlet-handler/> 를 추가해야 한다.
이 설정을 추가하면 Controller가 처리하지 못하는 요청은 WAS가 처리해달라고 요청을 한다.
** Spring 메시지 출력
=> Spring에서 properties 파일의 내용을 출력하고자 할 때는 아래 설정을 추가
<bean class="org.springframework.context.support.ResourceBundleMessageSource" id = "messageSource"?
<property name="basenames">
<list>
<value>메시지 파일 경로</value>
...
</list>
</property>
</bean>
=> 지역화(브라우저의 언어 설정에 따라 다른 문자열 출력) 설정
메세지 파일이름_언어코드.properties로 만들면 된다.
=> 프로퍼티 파일을 작성할 때는 key=value 형식으로 설정
=> value는 반드시 인코딩이 되어야 한다.
IDE의 Workspace 설정에서 text file encoding을 UTF-8로 설정했으면 자동으로 인코딩이 된다.
=> jsp 파일에서 메시지 출력
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>를 추가하고
<spring:message code="key" var="변수명" />
=> key의 값을 출력하고 변수에 저장 - var는 생략 가능
** 메세지 출력과 지역화
1. home.jsp 파일에 링크를 추가
<li><a href="validation" class="menu">유효성 검사</a></li>
2. 메시지를 저장할 파일을 src/main/resources 디렉토리에 message디렉토리를 만들고 label.properties로 생성
email=email
password=password
loginform.help=email/password root/user05
loginform.title=Login Form
3. 프로퍼티 파일을 사용하기 위한 설정을 servlet-context.xml 파일에 추가
<!-- 메시지 파일의 경로 설정 -->
<beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<beans:property name="basenames">
<beans:list>
<beans:value>message/label</beans:value>
</beans:list>
</beans:property>
</beans:bean>
4. HomeController에 validation 요청이 get 방식으로 오면 처리하는 메소드 생성
=> 위 메소드 대신에 servlet-context.xml 파일에 아래 설정을 추가해도 된다.
<!-- validation 요청이 오면 view이름을 validation으로 설정 -->
<view-controller path="validatoin" view-name="validation"/>
5. 메세지를 출력할 validation.jsp 파일을 views 디렉토리에
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- spring 태그를 사용하기 위헌 설정 -->
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><spring:message code="loginform.title"/></title>
</head>
<body>
<form method="post">
<spring:message code="email"/>
<input type="email" name="email" id="email"/><br/>
<spring:message code="password"/>
<input type="password" name="password" id="password"/><br/>
<p><spring:message code="loginform.help"/>
<input type="submit" value="로그인"/><br/>
</form>
</body>
</html>
** 변경 가능성이 있는 문자열 작성
1. Java에서 작성된 경우 : 문자열을 변경하면 compile. build, run을 전부 다시 수행
2. 일반 텍스트 파일에 작성된 경우 : 문자열을 변경하면 run만 다시 수행
3. 데이터베이스에 저장해서 읽어서 출력하는 경우 : 아무것도 할 필요 없음 - 대신에 읽어올 때 느림
** 지역화 코드
언어코드_국가코드
ko_kr - 대한민국
en_us - 미국
=> 대다수의 API에서는 지역화를 할 때는 기본이름에 _언어 코드나 국가코드를 설정해야 한다.
6. 기존에 만든 프로퍼티 파일의 이름을 label_en.properties로 변경
7. label_ko.properties 파일을 만들고 동일한 이름의 key에 value 를 설정
label_en.properties
email=email
password=password
loginform.help=email/password root/1234
loginform.title=Login Form
label_ko.properties
email=\uC774\uBA54\uC77C
password=\uD328\uC2A4\uC6CC\uB4DC
loginform.help=email/password root/1234
loginform.title=\uB85C\uADF8\uC778\uD3FC
** Spring Form 관련 Custom Tag
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 을 추가해서 사용
=> 유효성 검사를 서버에서 한 경우 dlwjs dlqfur rkqtdmf cnffurgksms qnqnsdlsk aptlwl cnffurdl tnldnjwlsek.
1. <form:form>
=> action을 설정하지 않으면 이 페이지에 오게 만든 요청
=> method를 생략하면 post
=> id는 입력 폼의 값을 저장하는 Command 객체의 이름이 할당
2. <form:input> : input 객체 생성
3. <form:password> : password 객체 생성
4. <form:hidden> : hidden 객체 생성
=> path 속성에 DTO의 프로퍼티 이름을 가제하면 name과 id를 프로퍼티 이름으로 설정
=> 데이터가 넘어왔다면 그 데이터를 value에 설정
<form:form commandName="member">
<form:input path="email" />
</form:form>
위처럼 작성하면 아래처럼 변경
<form method="post">
<input type="text" name="email" id="email" value="${member.email}" />
</form:form>
5. <form:select>
=> items 속성에 List 타입의 속성을 대입하면 자동으로 option을 생성
6. <form:checjboxex>, < form:radiobuttons>
=> items 속성에 List 타입의 속성을 대입하면 자동으로 생성
7. <form:textarea>
=> path만 설정
8. <form:errors path="프로퍼티 이름"> : 프로퍼티 이름으로 만들어진 에러 메시지를 출력
** Spring Form 관련 태그 사용 실습
1. email, password, loginType을 String으로 갖는 DTO 클래스를 생성
=> kr.co.pk.item.domain.Member
package kr.co.pk.item.domain;
public class Member {
private String email;
private String password;
private String loginform;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getLoginform() {
return loginform;
}
public void setLoginform(String loginform) {
this.loginform = loginform;
}
@Override
public String toString() {
return "Member [email=" + email + ", password=" + password + ", loginform=" + loginform + "]";
}
}
2. HomeController에 loginType에 관련된 데이터를 만들어주는 메소드를 생성
3. validation 요청을 처리하는 메소드를 수정
4. validation.jsp 수정
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!-- spring 태그를 사용하기 위한 설정 -->
<%@ taglib prefix="spring"
uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form"
uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><spring:message code="loginform.title" /></title>
</head>
<body>
<form:form modelAttribute="member">
<spring:message code="email" />
<form:input path="email"/><br/>
<spring:message code="password" />
<form:input path="password"/><br/>
<form:select path="loginType items="${loginTypes}"/>
<p><spring:message code="loginform.help"/></p>
<input type="submit" value="로그인"/>
</form:form>
</body>
</html>
** 유효성 검사 - Validation Check
1. 클라이언트 사이드
=> 서버에게 데이터를 전송하기 전에 파라미터의 유효성을 체크
=> 응답이 빠르다. 서버의 트래픽을 줄일 수 있다.
=> 보안이 취약하다.
=> 클라이언트 사이드의 코드는 유저가 확인할 수 있다.
=> 코드의 난독화를 이용해서 보안을 유지하는 경우가 있다.
의미없는 주석을 추가하거나 공백이나 엔터를 제거해서 수행
=> 웹의 경우는 HTML5의 속성이나 자바스크립트를 이용해서 수행
2. 서버 사이드
=> 클라이언트가 서버에게 데이터를 넘겨주는 시점에 수행
=> 보안이 필요한 데이터는 무조건 서버에서 유효성 검사를 해야하고 데이터는 네트워크를 이용해서 전송되면 변질의 위험성이 있다.
=> 쿼리의 매개변수로 사용되는 데이터는 반드시 데이터베이스 작업 전에 유효성 검사를 해야 한다.
암호화를 하지 않은 상태에서 로그인 처리
select email, pw
from user
where email = ggaingpae1@gmail.com and pw =ddd 1 or 1=1;
위의 경우에 비밀번호를 ddd or 1=1 형태로 대입하면 ?? 참이 되어 비번이 풀린다.
=> 비밀번호는 복호화가 불가능한 암호화를 이용해서 저장하고 데이터베이스가 아니라 Service에서 비교하도록 만들어야 한다.
3. 데이터베이스
=> 제약조건을 이용해서 유효성을 검사
** Spring에서의 유효성 검사는 서버에서 수행하는 것을 뜻한다.
** primary key 나 unique한 데이터의 중복 검사는 서버를 이용해야 한다.
초창기에는 팝업창을 이용해서 이런 작업을 처리했는데 그 이후 팝업창 대신에 ajax를 이용해서 검사를 수행하다가 최근에는 데이터를 전송할 때 서버에서 수행하는 형태로 변경되었다.
** Spring에서의 유효성 검사
=> validator 인터페이스와 Errors 그리고 BindingResult 클래스를이용해서 유효성 검사를 수행
1. Validator 인터페이스는 유효성 검사를 수행해주는 메소드를 소유한 인터페이스
=> 유효성 검사를 수행하고자 하는 경우 Validator 인터페이스를 상속받는 클래스를 생성
1) public boolean support(Class <?> clazz)
=> 유효성검사 수행 DTO 클래스.class.isAssignableFrom(clazz)를 호출해서 리턴
2) public voi validate(Object target, Errors errors)
=> 이 메소드에서 target을 DTO 클래스로 형변환하고 필요한 검사를 하고 errors에 에러메시지를 설정
2. Member 클래스의 유효성 검사 클래스
public class MemberValidator implements Validator{
public boolean supports(Class <?> clazz){
return
Member.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors){
Member member = (Member)target;
if(member.getEmail() == null){
errors.rejectValue("email", "메시지이름");
}
}
** Web SIte를 위한 서버, REST API 서버
- Web Site를 위한 서버 : 서버가 많은 일을 수행, spring 유효성 검사를 사용
- REST API 서버 : 클라이언트가 많은 일을 수행, spring 유효성 검사를 사용하지 않고 일반 로직으로 유효성 검사를 수행