본문 바로가기
Spring

[Spring] DispatcherServlet 구현하기

by eunoo 2022. 7. 5.

스프링에서는 클라이언트로부터 요청이 들어오면

DispatcherServlet이 요청을 처리하고, 모델을 생성하여

Controller에게 전달한다.

DispatcherServlet이 하는 일

1. 요청 처리

예를 들어 GET으로 요청한 데이터 타입은 String인데 메서드에선 int 로 받을 수 있다.

이건 중간에 DispatcherServlet이 String을 int로 변환해주는 작업을 하기 때문이다.

 

2. Model 생성

메서드의 매개변수에서 (Model model) 을 쓸 수 있는 이유는 DispatcherServlet이 Model을 생성하고 넘겨주기 때문이다.

 

다음 코드를 통해 DispatcherServlet이 어떻게 동작하는지 알아볼 수 있었다.

package com.hyocoding.ch1;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
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;

@WebServlet("/myDS")//localhost:8080/ch1/myDS?year=2022&month=7&day=1
public class MyDispatchServlet extends HttpServlet{
	@Override
	public void service(HttpServletRequest request, HttpServletResponse response) throws IOException{
		//요청으로 들어온 데이터를 map으로 생성한다.
		Map<String, String[]> map = request.getParameterMap();
	
		Model model = null;
		String viewName = "";
		try {
			//YoilTeller의 main메서드를 가져온다.
			Class clazz = Class.forName("com.hyocoding.ch1.YoilTeller");
			Object obj = clazz.newInstance();
			Method main = clazz.getDeclaredMethod("main", int.class, int.class, int.class, Model.class);
			
			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();
				//map에서 매개변수에 담을 값들을 얻는다.
				Object value = map.get(paramName);
				//매개변수 타입이 Model이라면 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) {
					String strvalue = ((String[])value)[0];
					//map에는 value가 String으로 들어있는데 이를 int로 변환해준다. 
					argArr[i] = convertTo(strvalue, paramType);
				}
				
			}
			viewName = (String) main.invoke(obj, argArr);
		}catch(Exception e) {
			e.printStackTrace();
		}
		render(viewName,model,response);
	}

	private void render(String viewName, Model model, HttpServletResponse response) throws IOException {
		String result="";
		
		response.setContentType("text/html");
		response.setCharacterEncoding("utf-8");
		PrintWriter out = response.getWriter();
		
		Scanner sc = new Scanner(new File(getResolveViewName(viewName)),"utf-8");
	
		while(sc.hasNextLine())
			result += sc.nextLine()+ System.lineSeparator();
		//model을 map으로 변환 
		Map map = model.asMap();
		Iterator it = map.keySet().iterator();
		while(it.hasNext()) {
			String key = (String)it.next();
			// 4. replace()로 key를 value로 바꾼다. 
			result = result.replace("${"+key+"}", map.get(key)+"");
		}
		//렌더링 결과를 화면에 출력. 
		out.println(result);
	}

	//ViewResolver 역할. jsp파일 진짜 경로 찾기
	private String getResolveViewName(String viewName) {
		return getServletContext().getRealPath("/WEB-INF/views/"+viewName+".jsp");
	}

	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;
	}
}

YoilTeller

package com.hyocoding.ch1;

import java.util.Calendar;

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

@Controller
public class YoilTeller {
	@RequestMapping("/getyoil")
	public String main(int year, int month, int day, Model m) {
		char yoil=getYoil(year,month,day);
		m.addAttribute("year",year);
		m.addAttribute("month",month);
		m.addAttribute("day", day);
		m.addAttribute("yoil", yoil);
		return "getyoil";
	}
	
	private char getYoil(int year, int month, int day) {
		Calendar cal = Calendar.getInstance();
		cal.set(year, month-1, day);
		
		int weekNum = cal.get(Calendar.DAY_OF_WEEK);//일:1, 월:2 ...
		char yoil = " 일월화수목금토일".charAt(weekNum);
		return yoil;
	}
}

 

DispatcherServlet에서 RequestMapping을 어떻게 처리하는지 구현해보고 싶었는데 아직 잘 모르겠다..ㅎㅎ

좀 더 공부해본 다음 해보는 걸로.

위의 코드를 intellij에서 작성해봤는데 메소드의 매개변수 이름을 arg0, arg1, arg2 이렇게 가져와서 안됐다..

진짜 매개변수 이름을 가져오려면 컴파일할때  -parameters 옵션을 넣어줘야하는데 어떻게 하는 건지 모르겠다.

이클립스에서는 window> preferencs> compiler 에서 store information about method parameters 옵션을 체크해주면 된다.

이 기능은 java 1.8 이상부터 지원하니 업데이트 필수~

댓글