프로토콜이란?
통상적 의미: 서로간의 통신을 위한 약속 또는 규칙
컴퓨터 데이터 통신에서의 프로토콜: 주고 받을 데이터에 대한 형식을 정의한 것
실생활에서의 프로토콜 예시) 야구선수와 감독의 프로토콜
모자 - 번트
코 - 희생플라이
귀 - 히트 & run
Hyper Text Transfer Protocal
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
: 요청주소가 잘못됨)
GET
POST
post등 다른 요청에 관한 실습은 POSTMAN프로그램을 이용.
POSTMAN도 학원에서 실습할때 설치해둔 그대로 사용.
텍스트기반 프로토콜에 바이너리 데이터를 전송하기 위해 고안
HTTP의 Content-Type헤더에 사용. 데이터의 타입을 명시함.
바이너리 파일을 텍스트파일로 변경할때는 base64인코딩을 사용.
객체지향 설계에서 지켜야 할 세 가지 분리
1) 관심사의 분리
2) 변하는 것, (자주)변하지 않는 것의 분리
3) 공통, 중복코드의 분리처음부터 모든 원칙을 지켜 객체지향적 설계를 하는것은 어려우므로, 차차 개선해 나가는 형식으로 발전시켜나가자.
MVC구조에서의 요청처리과정
1. 입력 DispatherServlet으로 데이터를 받아 Controller로 전송
2. Controller가 전송받은 데이터를 처리한 후 Model객체에 담아 다시 DispatherServlet으로 전송
3. 전달받은 Model객체를 다시 View로 이동시켜 사용자에게 보여줌
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-82) 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임.
필요할 경우 이를 변경하면 된다.
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타입만 남겨두니 정상적으로 동작함.
이유는 아직 모르겠다....
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을 반드시 업데이트해야 한다.
설정을 마친 후 위 예제 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였어도 방식은 동일함.
-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로 출력.