package kr.ac.jipark09.ch2;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
// 년,월,일을 입력하면 요일을 알려주는 프로그램
@Controller
public class YoilTeller {
@RequestMapping("/getYoil")
public void main(HttpServletRequest request, HttpServletResponse response) throws IOException {
String year = request.getParameter("year");
String month = request.getParameter("month");
String day = request.getParameter("day");
int yyyy = Integer.parseInt(year);
int mm = Integer.parseInt(month);
int dd = Integer.parseInt(day);
// 2. 처리
Calendar cal = Calendar.getInstance();
cal.set(yyyy, mm - 1, dd);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
char yoil = " 일월화수목금토".charAt(dayOfWeek);
// response 객체에서 브라우저로의 출력 스트림을 얻는다.
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
out.println(year + "년" + month + "월" + day + "일");
out.println(yoil + "요일 입니다.");
}
}
✔️ YoilTeller객체를 만들고 처리하는 클래스
✔️ Spring의 역할을 알아보기 위한 클래스
package kr.ac.jipark09.ch2;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.StringJoiner;
public class MethodInfo {
public static void main(String[] args) throws Exception{
// 1. YoilTeller클래스의 객체를 생성
Class clazz = Class.forName("kr.ac.jipark09.ch2.YoilTeller");
Object obj = clazz.newInstance();
// 2. YoilTeller의 모든 메서드의 정보를 가져와서 배열에 저장
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
}
void main(javax.servlet.http.HttpServletRequest arg0, javax.servlet.http.HttpServletResponse arg1)
YoilTeller로 컴파일할 때 컴파일러는 매개변수 타입은 중요한데 이름은 중요하다고 생각하지 않는다. 그래서 arg0
, arg1
로 나온다.
우리는 메서드 매개변수 이름이 필요하다. 그래서 매개변수 이름을 저장하려면 컴파일 옵션에다가 javac -paramaters
를 적어줘야 한다.
❗️ javac -paramaters
: 매개변수 이름 저장 옵션
Store information about method parameters (usable via reflaction)
을 체크해주면 매개변수 이름이 저장 된다.
java 버전 1.8 이상 설정되어 있는지 확인해야 한다. 그 이전 버전은 되지 않는다.
❗️ 그래도 바뀌지 않았다면 pom.xml에 들어가보자.
${java-version}
이라고 넣어준다.✔️ 우리가 사용하는 MVC 프로젝트는 Maven을 이용해서 관리한다.
Update Project
를 해줘야 한다.Reflection API 이용
: javac -parameters
옵션 사용
Class file을 직접 읽음
: JDK 1.8 이하는 다 이 방법을 이용
✔️ Controller 직접 생성
✔️ View의 반환값에 따라 달라지는 결과 값을 보자
package kr.ac.jipark09.ch2;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
class ModelController {
public String main(HashMap map) {
// 작업결과를 map에 저장: Controller 메서드가 하는 역할
map.put("id", "asdf");
map.put("pwd", "1111");
return "txtView1"; // View 이름을 반환
//return "txtView2";
}
}
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);
}
}
id=${id}
pwd=${pwd}
[결과값]
before:{}
after :{id=asdf, pwd=1111}
id=asdf
pwd=1111
id=${id}, pwd=${pwd}
[결과값]
before:{}
after :{id=asdf, pwd=1111}
id=asdf, pwd=1111
✔️ Reflection API를 이용해서 Controller을 생성하고 메서드를 호출
✔️ View의 반환값에 따라 달라지는 결과 값을 보자
package kr.ac.jipark09.ch2;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
public class MethodCall2 {
public static void main(String[] args) throws Exception{
// 1. YoilTellerMVC의 객체를 생성
Class clazz = Class.forName("kr.ac.jipark09.ch2.YoilTellerMVC");
Object obj = clazz.newInstance();
// 2. main 메서드의 정보를 가져온다.
Method main = clazz.getDeclaredMethod("main", int.class, int.class, int.class, Model.class);
// 3. Model 생성 (Interface)
Model model = new BindingAwareModelMap();
System.out.println("[before] model=" + model);
// 4. main 메서드 호출
// Reflection API를 이용하는 것이기 때문에 invoke로 호출한다.
// String viewName = obj.main(2021, 10, 1, model); 아래줄과 동일
String viewName = (String)main.invoke(obj, new Object[] { 2021, 10, 1, model });
System.out.println("viewName=" + viewName);
System.out.println("[after] model=" + model);
// 텍스트 파일을 이용한 rendering
render(model, viewName);
}
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);
}
}
/* [실행결과]
[before] model={}
viewName=yoil
[after] model={year=2021, month=10, day=1, yoil=금}
<%@ page contentType="text/html;charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>YoilTellerMVC</title>
</head>
<body>
<h1>2021년 10월 1일은 금요일입니다.</h1>
</body>
</html>
*/
[결과값]
[before] model={}
viewName=yoil
[after] model={year=2021, month=10, day=1, yoil=금}
<%@ page contentType="text/html;charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>YoilTellerMVC</title>
</head>
<body>
<h1>2021년 10월 1일은 금요일입니다.</h1>
</body>
</html>
String viewName = (String)main.invoke(obj, new Object[] { 2021, 10, 1, model });
- 여기서는 요청으로 받은 값이 아닌, 직접 값을 넣어줬다.
✔️ 요청할 때 넘겨준 값(파라미터)을 이용해서 동적으로 구성해 보자.
package kr.ac.jipark09.ch2;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
public class MethodCall3 {
public static void main(String[] args) throws Exception{
// 1. 요청할 때 제공된 값 - request.getParameterMap();
Map map = new HashMap();
map.put("year", "2021");
map.put("month", "10");
map.put("day", "1");
Model model = null;
Class clazz = Class.forName("kr.ac.jipark09.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);
}
}
[결과값]
paramArr=[int year, int month, int day, org.springframework.ui.Model model]
argArr=[2021, 10, 1, {}]
viewName=yoil
[after] model={year=2021, month=10, day=1, yoil=금}
<%@ page contentType="text/html;charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>YoilTellerMVC</title>
</head>
<body>
<h1>2021년 10월 1일은 금요일입니다.</h1>
</body>
</html>
spring이 메서드의 매개변수에 맞게 값을 넣어주고 동작하는지 이해할 수 있을 것이다.
✔️ 브라우저에서 호출해 보기
package kr.ac.jipark09.ch2;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
// @Controller + @RequestMapping = @WebServlet
@WebServlet("/myDispatcherServlet") // http://localhost/ch2/myDispatcherServlet?year=2021&month=10&day=1
public class MyDispatcherServlet extends HttpServlet {
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map map = request.getParameterMap();
Model model = null;
String viewName = "";
try {
Class clazz = Class.forName("com.fastcampus.ch2.YoilTellerMVC");
Object obj = clazz.newInstance();
// 1. main메서드의 정보를 얻는다.
Method main = clazz.getDeclaredMethod("main", int.class, int.class, int.class, Model.class);
// 2. main메서드의 매개변수 목록(paramArr)을 읽어서 메서드 호출에 사용할 인자 목록(argArr)을 만든다.
Parameter[] paramArr = main.getParameters();
Object[] argArr = new Object[main.getParameterCount()];
for(int i = 0; i < paramArr.length; i++) {
String paramName = paramArr[i].getName();
Class paramType = paramArr[i].getType();
Object value = map.get(paramName);
// paramType중에 Model이 있으면, 생성 & 저장
if(paramType == Model.class) {
argArr[i] = model = new BindingAwareModelMap();
} else if(paramType == HttpServletRequest.class) {
argArr[i] = request;
} else if(paramType == HttpServletResponse.class) {
argArr[i] = response;
} else if(value != null) { // map에 paramName이 있으면,
// value와 parameter의 타입을 비교해서, 다르면 변환해서 저장
String strValue = ((String[])value)[0]; // getParameterMap()에서 꺼낸 value는 String배열이므로 변환 필요
argArr[i] = convertTo(strValue, paramType);
}
}
// 3. Controller의 main()을 호출 - YoilTellerMVC.main(int year, int month, int day, Model model)
viewName = (String)main.invoke(obj, argArr);
} catch(Exception e) {
e.printStackTrace();
}
// 4. 텍스트 파일을 이용한 rendering
render(model, viewName, response);
} // main
private 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 String getResolvedViewName(String viewName) {
return getServletContext().getRealPath("/WEB-INF/views") + "/" + viewName + ".jsp";
}
private void render(Model model, String viewName, HttpServletResponse response) throws IOException {
String result = "";
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
// 1. 뷰의 내용을 한줄씩 읽어서 하나의 문자열로 만든다.
Scanner sc = new Scanner(new File(getResolvedViewName(viewName)), "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.렌더링 결과를 출력한다.
out.println(result);
}
}
서블릿은 메서드 단위로 맵핑이 되지 않는다.(@RequestMapping 불가능)
클래스 단위로만 맵핑이 된다. 그래서 서블릿은 스프링에 비해서 클래스를 많이 만들어야되는 단점이 있다.
또한 서블릿은 HttpServlet
을 상속받아야 하며 꼭 서비스 메서드를 사용해야 한다. 파라미터는 request와 response를 받는다.
Reference
: https://fastcampus.co.kr/dev_academy_nks