CH02. Spring MVC 02

하파타카·2022년 9월 6일
0

패캠JAVA, Spring과정

목록 보기
10/20

05. HTTP 요청과 응답 - 이론

프로토콜이란?
통상적 의미: 서로간의 통신을 위한 약속 또는 규칙
컴퓨터 데이터 통신에서의 프로토콜: 주고 받을 데이터에 대한 형식을 정의한 것

실생활에서의 프로토콜 예시) 야구선수와 감독의 프로토콜
모자 - 번트
코 - 희생플라이
귀 - 히트 & run

HTTP

Hyper Text Transfer Protocal

  • 단순하고 읽기 쉬움 - 텍스트 기반의 프로토콜
  • 상태를 유지하지 않음(stateless) - 클라이언트의 정보를 저장x
    정보를 저장하지 않기 때문에 동일한 클라이언트가 요청을 2번 보내도 같은 클라이언트임을 구분하지 못함 => 이를 보완하기 위해 쿠키와 세션을 사용
  • 확장이 가능함 - 커스텀 헤더(header) 추가 가능
    => 데이터를 주고받는간에 협의가 된다면 형식을 추가하여 통신이 가능함

HTTP 통신 과정
1) 사용자가 URL을 입력하여 요청
2) 브라우저가 HTTP 요청 메시지를 자동으로 생성하여 서버에 요청을 보냄
3) 서버에서 HTTP 응답 메시지를 보냄
4) 브라우저에 응답 메시지가 데이터로 출력됨

-HTTP 응답 메시지 예시-

HTTP/1.1 200 OK
Content-Length:44
Content-Type:text/html
Date:Sat,20 Oct 2021 19:03:38 GMT

<html>
	<head></head>
    <body>
    	Hello
    </body>
</html>

1라인 : 상태라인(state line)
2~4라인 : header
~ header라인은 가변적이므로 body영역과 구분을 위해 빈 행이 들어감 ~
6라인~ : body

200 : 상태코드 (예 - 404 : 요청주소가 잘못됨)

요청 method

  • GET

    • 서버의 리소스를 가져오기 위해 설계됨(Read)
    • Query String을 통해 데이터 전달(소용량)
    • HTTP 요청 메시지 GET의 경우 body가 없음
    • 요청 라인과 header만 존재
    • URL에 데이터가 노출되므로 보안에 취약
    • 데이터 공유에 유리함
    • 예) 검색엔진에서 검색단어 전송
  • POST

    • 서버에 데이터를 올리기 위해 설계됨(Write)
    • 전송 테이터 크기의 제한이 없음(대용량)
    • 데이터를 요청 메시지의 body에 담아 전송
    • 보안에 유리, 데이터 공유에는 불리
    • 예) 게시판에 글쓰기, 로그인, 회원가입
    • HTTP에 TLS를 더해야 안전한 것이므로 단순히 POST를 이용했다고 보안에 유리한 것은 아님(HTTP + TLS => https://)

실습


post등 다른 요청에 관한 실습은 POSTMAN프로그램을 이용.
POSTMAN도 학원에서 실습할때 설치해둔 그대로 사용.


06. 텍스트와 바이너리

  • 텍스트 파일 :
    • 문자만 저장되어 있는 파일
    • 숫자를 문자로 변환 후 사용
    • 사람이 읽기 쉬움
  • 바이너리 파일 :
    • 문자와 숫자가 저장되어 있는 파일
    • 데이터를 있는 그대로 사용
    • 사람이 읽기 어려움

mime

텍스트기반 프로토콜에 바이너리 데이터를 전송하기 위해 고안
HTTP의 Content-Type헤더에 사용. 데이터의 타입을 명시함.

바이너리 파일을 텍스트파일로 변경할때는 base64인코딩을 사용.


07. 관심사의 분리와 MVC패턴 - 이론

OOP 5대 설계원칙 - SOLID

  1. SRP - 단일책임의 원칙
    하나의 메서드는 하나의 책임만 진다.

객체지향 설계에서 지켜야 할 세 가지 분리
1) 관심사의 분리
2) 변하는 것, (자주)변하지 않는 것의 분리
3) 공통, 중복코드의 분리

처음부터 모든 원칙을 지켜 객체지향적 설계를 하는것은 어려우므로, 차차 개선해 나가는 형식으로 발전시켜나가자.

MVC구조에서의 요청처리과정
1. 입력 DispatherServlet으로 데이터를 받아 Controller로 전송
2. Controller가 전송받은 데이터를 처리한 후 Model객체에 담아 다시 DispatherServlet으로 전송
3. 전달받은 Model객체를 다시 View로 이동시켜 사용자에게 보여줌


08. 관심사의 분리와 MVC패턴 - 실습

MVC형식을 도입하기 위해 관심사를 분리

1) 입력객체의 분리
request객체를 이용해 값을 한번에 받아오지 않고 원하는 값에 따른 객체를 생성하여 값을 받아오도록 수정.
2) 출력객체의 분리
JSP파일을 생성하여 출력

-YoilTellerMVC.java-
앞서 사용한 YoilTeller.java를 수정하여 새로운 클래스를 생성.

// 년월일을 입력하면 요일을 알려주는 프로그램
@Controller
public class YoilTellerMVC {
	
//	public static void main(String[] args) {
	@RequestMapping("/getYoilMVC")			// http://localhost:8090/ch2/getYoilMVC?year=2022&month=9&day=12
//	public void main(HttpServletRequest request, HttpServletResponse response) throws IOException{
	public String main(int year, int month, int day, Model model) throws IOException{
		
		// 1. 유효성검사
		if(!isValid(year, month, day))
			return "yoilError";
		
		// 2. 요일 계산
		char yoil = getYoil(year, month, day);
		
		// 3. 계산한 결과를 model에 저장
		model.addAttribute("year", year);
		model.addAttribute("month", month);
		model.addAttribute("day", day);
		model.addAttribute("yoil", yoil);
		
		return "yoil";		// /WEB-INF/views/yoil.jsp		
	}
	
	private boolean isValid(int year, int month, int day) {

		return false;
	}

	private char getYoil(int year, int month, int day) {
		Calendar cal = Calendar.getInstance();
		cal.set(year, month -1, day);

		int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);	// 1: 일요일, 2: 월요일 ...
		return " 일월화수목금토".charAt(dayOfWeek);
	}
}

-Yoil.jsp-

<%@ page contentType="text/html;charset=utf-8" %>
<html>
<head>
	<title>YoilTellerMVC</title>
</head>
<body>
<h1>${year}년 ${month}월 ${day}일은 ${yoil}요일입니다.</h1>
</body>
</html>

-YoilError.jsp-

<%@ page contentType="text/html;charset=utf-8" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
<h3> 잘못된 요청입니다. 년월일을 모두 올바르게 입력하세요. </h3>
</body>
</html>


http://localhost:8090/ch2/getYoil?year=2022&month=9&day=12
앞서 request객체로 값을 받아오던 예제와 결과가 같음을 확인할 수 있다.

참고
1) 인코딩 문제가 생기는 경우
아래의 링크를 참고하여 인코딩을 모두 utf-8로 변경해준다
[Spring] 스프링 STS 초기설정 UTF-8

2) jsp파일에 이유없이 에러가 생기는 경우
작성한 코드에 문제가 없음에도 에러가 생기는 경우 빨간 줄이 생긴 행을 잘라내 저장한 후 다시 붙여넣으면 사라지니 알아둘 것.

파일이름만 return해줘도 알아서 페이지를 찾을 수 있는 이유
프로젝트폴더/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml 의

<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<beans:property name="prefix" value="/WEB-INF/views/" />
	<beans:property name="suffix" value=".jsp" />
</beans:bean>

prefix는 파일명의 접두사를,
suffix는 파일명의 접미사를 설정하는 xml임.
필요할 경우 이를 변경하면 된다.

매개변수로 Model을 사용하지 않을 경우

public String main(int year, int month, int day, Model model) throws IOException

위의 코드처럼 사용자에게서 값을 받아올때 매개변수로
Model을 사용할 경우 DispacherServlet이 model객체를 생성해주지만 사용하지 않을 경우

public ModelAndView main(int year, int month, int day) throws IOException{
	// ModelAndView를 생성하고, 기본 뷰를 지정	
	ModelAndView mv = new ModelAndView();
    mv.setViewName("yoil");	// 뷰의 이름을 지정

위의 예시처럼 ModelAndView로 model객체를 따로 생성하여 작업결과를 보여줄 뷰를 지정하는 형태로 사용해야 함.
=> 컨트롤러 메서드의 반환 타입에 따른 분류
1) [String] 뷰의 이름을 반환
2) [ModelAndView] Model과 뷰의 이름을 반환
3) [void] 맵핑된 url의 끝단어가 뷰이름이 됨
3번의 경우 @RequestMapping("/getYoilMVC")일때 getYoilMVC가 뷰의 이름이 됨

단, ModelAndView가 잘 쓰이지는 않음.
보통은 String으로 반환하여 뷰의 이름을 지정하도록 사용하는 편.

아래의 예시는 위에서 Model 을 사용하는 경우이며, 아래는 사용하지 않고 ModelAndView가 사용되었을때의 예시.
위의 예제와 비교용으로 메모해둠.

-YoilTellerMVC.java-

// 년월일을 입력하면 요일을 알려주는 프로그램
@Controller
public class YoilTellerMVC {
	
//	public static void main(String[] args) {
	@RequestMapping("/getYoilMVC")			// http://localhost:8090/ch2/getYoilMVC?year=2022&month=9&day=12
//	public void main(HttpServletRequest request, HttpServletResponse response) throws IOException{
	public ModelAndView main(int year, int month, int day) throws IOException{
		
		ModelAndView mv = new ModelAndView();
		
		// 1. 유효성검사
//		if(!isValid(year, month, day))
//			return "yoilError";
		
		// 2. 요일 계산
		char yoil = getYoil(year, month, day);
		
		// 3. 계산한 결과를 model에 저장
		mv.addObject("year", year);
		mv.addObject("month", month);
		mv.addObject("day", day);
		mv.addObject("yoil", yoil);
		
		// 4. 결과를 보여줄 view를 지정
		mv.setViewName("yoil");
		
		return mv;
		
//		return "yoil";		// /WEB-INF/views/yoil.jsp		
	}
	
	private boolean isValid(int year, int month, int day) {

		return false;
	}

	private char getYoil(int year, int month, int day) {
		Calendar cal = Calendar.getInstance();
		cal.set(year, month -1, day);

		int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);	// 1: 일요일, 2: 월요일 ...
		return " 일월화수목금토".charAt(dayOfWeek);
	}
}

🛠️에러

이유는 모르겠으나 jsp파일에

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>

가 들어있으니 404에러가 발생함.
삭제 후 위의 예제처럼 UTF-8타입만 남겨두니 정상적으로 동작함.
이유는 아직 모르겠다....


09. 관심사의 분리와 MVC패턴 - 원리01

public class MethodInfo {

	public static void main(String[] args) throws Exception{

		// 1. YoilTeller클래스의 객체를 생성
		Class clazz = Class.forName("com.fastcampus.ch2.YoilTeller");
		Object obj = clazz.newInstance();
		
		// 2. 모든 메서드 정보를 가져와 배열에 저장
		Method[] methodArr = clazz.getDeclaredMethods();
		
		for(Method m : methodArr) {
			String name = m.getName();		// 메서드 이름
			Parameter[] paramArr = m.getParameters();	// 매개변수 목록
//			Class[] paramTypeArr = m.getParameterTypes();
			Class returnType = m.getReturnType();		// 반환타입
			
			// 각 매개변수에 구분자 , 와 접두사 ( 와 접미사 ) 를 붙여줌
			StringJoiner paramList = new StringJoiner(", ", "(", ")");	
			
			for(Parameter param : paramArr) {
				String paramName = param.getName();
				Class  paramType = param.getType();
				
				paramList.add(paramType.getName() + " " + paramName);
			}
			
			System.out.printf("%s %s%s%n", returnType.getName(), name, paramList);
		}
	} // main
}

값은 저장하되 매개변수의 이름을 저장하지 않기 때문에 arg0, arg1...으로 출력됨.
매개변수의 이름을 저장하기 위한 옵션이 parameters임.

매개변수를 저장하기 위해 이클립스의 옵션을 변경.

속성에서 자바 11버전으로 변경 후 사진에 표시된 설정을 체크해준다.
이후 현재 사용중인 ch2프로젝트의 버전을 11로 변경하는 작업이 필요.
pom.xml로 이동


화면에 표시된 두 부분을 수정.
이렇게 수정해두면 앞으로 변경이 필요할 때 위의 <java-version>값만 바꿔주면 아래는 수정할 필요가 없게됨.



pom.xml파일을 수정하여 프로젝트의 설정을 수정하면 maven을 반드시 업데이트해야 한다.

스프링이 매개변수를 저장하는 방법 2가지

  1. Reflection API => -parameter옵션을 이용. java8, jdk1.8 버전부터 지원.
  2. class file을 통해 직접 받아옴

설정을 마친 후 위 예제 MethodInfo.java클래스의 일부코드를
Class clazz = Class.forName("com.fastcampus.ch2.YoilTellerMVC");로 수정하여 YoilTellerMVC클래스의 객체를 생성하여 매개변수의 이름이 저장되었는지 확인해본다.

위 사진처럼 매개변수의 이름이 보존됨을 확인가능.

예제

-MethodCall.java-

class ModelController {
	public String main(HashMap map) {
		// 작업결과를 map에 저장
		map.put("id", "asdf");
		map.put("pwd", "1111");
		
		return "txtView2";	// view이름을 반환
	}
}

public class MethodCall {
	public static void main(String[] args) throws Exception{
		HashMap map = new HashMap();
		System.out.println("before:"+map);

		ModelController mc = new ModelController();
		String viewName = mc.main(map);
		
		System.out.println("after :"+map);
		
		render(map, viewName);
	}
	
	static void render(HashMap map, String viewName) throws IOException {
		String result = "";
		
		// 1. 뷰의 내용을 한줄씩 읽어서 하나의 문자열로 만든다.
		Scanner sc = new Scanner(new File(viewName+".txt"));
		
		while(sc.hasNextLine())
			result += sc.nextLine()+ System.lineSeparator();
		
		// 2. map에 담긴 key를 하나씩 읽어서 template의 ${key}를 value바꾼다.
		Iterator it = map.keySet().iterator();
		
		while(it.hasNext()) {
			String key = (String)it.next();

			// 3. replace()로 key를 value 치환한다.
			result = result.replace("${"+key+"}", (String)map.get(key));
		}
		
		// 4.렌더링 결과를 출력한다.
		System.out.println(result);
	}
}

main메서드가 메모리에 올라간 후 map을 생성
1) ModelController클래스의 main메서드가 메모리에 올라감(각 클래스별로 메모리의 공간을 따로 할당받음)
즉 main메서드에서 map을 만든 후 ModelController를 호출

2) 이어서 호출된 ModelController에서 앞서 생성된 map에 값을 저장

3) ModelController의 작업이 끝나고 view의 이름을 반환한 후 메모리에서 내려감

위처럼 일련의 과정을 거치면 굳이 ModelController에서 map을 반환할 필요가 없음.
main메서드에서 map을 먼저 만들어서 ModelController를 호출한 것이기 때문에 main메서드가 가리키는 map과 ModelController가 가리키는 map이 동일하기 때문에 ModelController로 map에 값을 넣으면 그대로 main메서드에서 사용이 가능함.

이 부분이 이해가 안될경우 유투브의 자바의 정석 기초편 ch6-24,25 참조형 매개변수, 참조형 반환타입 영상을 참고.

view역할을 해줄 txt파일을 생성

프로젝트루트폴더 아래에 txtView.txt, txtView2.txt생성
각각 내용은

id:${id}
pwd:${pwd}
id:${id}, pwd:${pwd}

로 넣고 실행

txt파일을 view로 삼아 값이 출력됨을 확인할 수 있음.
view가 jsp였어도 방식은 동일함.


10. 관심사의 분리와 MVC패턴 - 원리02

예제

-MethodCall3-

public class MethodCall3 {
	public static void main(String[] args) throws Exception{
		// 1. 요청할때 제공된 값 - request.getParameterMap();
		Map map = new HashMap();
		map.put("year", "2022");
		map.put("month", "9");
		map.put("day", "14");

		Model model = null;
		Class clazz = Class.forName("com.fastcampus.ch2.YoilTellerMVC");
		Object obj  = clazz.newInstance();
		
		// YoilTellerMVC.main(int year, int month, int day, Model model)
		Method main = clazz.getDeclaredMethod("main", int.class, int.class, int.class, Model.class);
				
		Parameter[] paramArr = main.getParameters();	// main메서드의 매개변수 목록을 가져옴
		Object[] argArr = new Object[main.getParameterCount()];	// 매개변수 갯수와 같은 길이의 Object배열을 생성
		
		for(int i=0;i<paramArr.length;i++) {
			String paramName = paramArr[i].getName();
			Class  paramType = paramArr[i].getType();
			Object value = map.get(paramName); // map에서 못찾으면 value는 null

			// paramType중에 Model이 있으면, 생성 & 저장 
			if(paramType==Model.class) {
				argArr[i] = model = new BindingAwareModelMap(); 
			} else if(value != null) {  // map에 paramName이 있으면,
				// value와 parameter의 타입을 비교해서, 다르면 변환해서 저장  
				argArr[i] = convertTo(value, paramType);				
			} 
		}
		System.out.println("paramArr="+Arrays.toString(paramArr));
		System.out.println("argArr="+Arrays.toString(argArr));
		
		
		// Controller의 main()을 호출 - YoilTellerMVC.main(int year, int month, int day, Model model)
		String viewName = (String)main.invoke(obj, argArr); 	
		System.out.println("viewName="+viewName);	
		
		// Model의 내용을 출력 
		System.out.println("[after] model="+model);
				
		// 텍스트 파일을 이용한 rendering
		render(model, viewName);			
	} // main
	
	private static Object convertTo(Object value, Class type) {
		if(type==null || value==null || type.isInstance(value)) // 타입이 같으면 그대로 반환 
			return value;

		// 타입이 다르면, 변환해서 반환
		if(String.class.isInstance(value) && type==int.class) { // String -> int
			return Integer.valueOf((String)value);
		} else if(String.class.isInstance(value) && type==double.class) { // String -> double
			return Double.valueOf((String)value);
		}
			
		return value;
	}
	
	private static void render(Model model, String viewName) throws IOException {
		String result = "";
		
		// 1. 뷰의 내용을 한줄씩 읽어서 하나의 문자열로 만든다.
		Scanner sc = new Scanner(new File("src/main/webapp/WEB-INF/views/"+viewName+".jsp"), "utf-8");
		
		while(sc.hasNextLine())
			result += sc.nextLine()+ System.lineSeparator();
		
		// 2. model을 map으로 변환 
		Map map = model.asMap();
		
		// 3.key를 하나씩 읽어서 template의 ${key}를 value바꾼다.
		Iterator it = map.keySet().iterator();
		
		while(it.hasNext()) {
			String key = (String)it.next();

			// 4. replace()로 key를 value 치환한다.
			result = result.replace("${"+key+"}", ""+map.get(key));
		}
		
		// 5.렌더링 결과를 출력한다.
		System.out.println(result);
	}
}

jsp로 출력.

profile
천 리 길도 가나다라부터

0개의 댓글