교재 실습/자바 웹 개발 워크북

87. 리플랙션 API를 이용하여 프런트 컨트롤러 개선하기 (3)

Jint 2022. 8. 6. 17:59

4. 프런트 컨트롤러의 변경

페이지 컨트롤러를 변경했으니, 이제는 프런트 컨트롤러를 변경한다. spms.servlets.DsipatcherServlet 클래스를 다음과 같이 편집한다.

package spms.servlets;

import java.io.IOException;
import java.util.HashMap;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import spms.bind.DataBinding;
import spms.bind.ServletRequestDataBinder;
import spms.controls.Controller;
import spms.controls.LogInController;
import spms.controls.LogOutController;
import spms.controls.MemberAddController;
import spms.controls.MemberDeleteController;
import spms.controls.MemberListController;
import spms.controls.MemberUpdateController;
import spms.vo.Member;

@SuppressWarnings("serial")
@WebServlet("*.do") //프런트 컨트롤러의 배치
public class DispatcherServlet extends HttpServlet {//서블릿이기 때문에 HttpServlet 상속
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html; charset=UTF-8");
		String servletPath = request.getServletPath(); //서블릿 경로 추출
		try {
			ServletContext sc = this.getServletContext();
			
			//페이지 컨트롤러에게 전달할 Map 객체 준비
			HashMap<String,Object> model = new HashMap<String,Object>();
			//model.put("memberDao", sc.getAttribute("memberDao")); //memberDao 객체는 더이상 Map 객체에 담을 필요 없음.
			model.put("session", request.getSession()); //로그인 및 로그아웃 페이지 컨트롤러에서 사용할 세션 객체
			
			//String pageControllerPath = null;
			//인터페이스 타입의 참조 변수 선언 - Controller의 구현체 클래스들 객체 주소 저장 위함
			//Controller pageController = null;
			//ServletContext에서 페이지 컨트롤러 꺼낼 때 서블릿 URL 사용
			Controller pageController = (Controller)sc.getAttribute(servletPath);
			
			//DataBinding 구현 여부 검사하여, 해당 인터페이스를 구현한 경우에만 호출
			if(pageController instanceof DataBinding) {
				prepareRequestData(request, model, (DataBinding)pageController);
			}
			
			//페이지 컨트롤러로 위임
			//조건문 변경 : 페이지 컨트롤러가 사용할 데이터를 준비하는 부분 외 제거
			/*if("/member/list.do".equals(servletPath)) {
				//pageControllerPath = "/member/list";
				//pageController = new MemberListController();
			}else
			if("/member/add.do".equals(servletPath)) {
				//pageControllerPath = "/member/add";
				//pageController = new MemberAddController();
				if(request.getParameter("email") != null) {
					//요청 매개변수로부터 VO 객체 준비
					//request.setAttribute("member", new Member().setEmail(request.getParameter("email"))
															   //.setPassword(request.getParameter("password"))
															   //.setName(request.getParameter("name")));
					//Map 객체에 VO 객체 준비
					model.put("member", new Member().setEmail(request.getParameter("email"))
													.setPassword(request.getParameter("password"))
													.setName(request.getParameter("name")));
				}
			}else if("/member/update.do".equals(servletPath)) {
				//pageControllerPath = "/member/update";
				//pageController = new MemberUpdateController();
				if(request.getParameter("email") != null) {
					//요청 매개변수로부터 VO 객체 준비
					//request.setAttribute("member", new Member().setNo(Integer.parseInt(request.getParameter("no")))
															   //.setEmail(request.getParameter("email"))
															   //.setName(request.getParameter("name")));
					//Map 객체에 VO 객체 준비
					model.put("member", new Member().setNo(Integer.parseInt(request.getParameter("no")))
													.setEmail(request.getParameter("email"))
													.setName(request.getParameter("name")));
				}else {
					model.put("no", new Integer(request.getParameter("no")));
		        }
			}else if("/member/delete.do".equals(servletPath)) {
				//pageControllerPath = "/member/delete";
				//pageController = new MemberDeleteController();
				model.put("no", new Integer(request.getParameter("no")));
			}else if("/auth/login.do".equals(servletPath)) {
				//pageControllerPath = "/auth/login";
				//pageController = new LogInController();
				if(request.getParameter("email") != null) {
					model.put("loginInfo", new Member().setEmail(request.getParameter("email"))
													   .setPassword(request.getParameter("password")));
				}
			}
			else if("/auth/logout.do".equals(servletPath)) {
				//pageControllerPath = "/auth/logout";
				//pageController = new LogOutController();
			}*/
			
			//페이지 컨트롤러로 실행 위임
			/*RequestDispatcher rd = request.getRequestDispatcher(pageControllerPath);
			rd.include(request, response);*/
			
			//페이지 컨트롤러 실행
			String viewUrl = pageController.execute(model); //뷰 URL 반환받음
			
			//Map 객체에 저장된 값을 ServletRequest에 복사
			for(String key : model.keySet()) {//keySet() : key값 가져옴
				request.setAttribute(key, model.get(key)); //model.get(key) : value값 가져옴
			}

			//JSP로 위임
			/*String viewUrl = (String)request.getAttribute("viewUrl");
			if(viewUrl.startsWith("redirect:")) {
				response.sendRedirect(viewUrl.substring(9));
				return;
			}else {
				rd = request.getRequestDispatcher(viewUrl);
				rd.include(request, response);
			}*/
			//JSP로 실행 위임
			if(viewUrl.startsWith("redirect:")) {
				response.sendRedirect(viewUrl.substring(9));
				return;
			}else {
				RequestDispatcher rd = request.getRequestDispatcher(viewUrl);
				rd.include(request, response);
			}
		} catch (Exception e) {//오류 처리
			e.printStackTrace();
			request.setAttribute("error", e);
			RequestDispatcher rd = request.getRequestDispatcher("/Error.jsp");
			rd.forward(request, response);
		}
	}
	
	//페이지 컨트롤러에 필요한 데이터 준비
	private void prepareRequestData(HttpServletRequest request, HashMap<String, Object> model, DataBinding dataBinding) throws Exception {
		//페이지 컨트롤러에게 필요한 데이터 묻기
		Object[] dataBinders = dataBinding.getDataBinders();
		//배열에서 꺼낸 값 보관할 임시 변수
		String dataName = null; //데이터 이름(String)
		Class<?> dataType = null; //데이터 타입(Class)
		Object dataObj = null; //데이터 객체(Object)
		//배열 반복하며 값 꺼내기
		for(int i=0 ; i < dataBinders.length ; i+=2) {
			dataName = (String)dataBinders[i];
			dataType = (Class<?>)dataBinders[i+1];
			dataObj = ServletRequestDataBinder.bind(request, dataType, dataName);
			model.put(dataName, dataObj);
		}
	}
}

 

- service() 메서드

드디어 service() 메서드에서 조건문이 사라졌다. 이제부터는 매개변수 값을 사용하는 페이지 컨트롤러를 추가하더라도 조건문을 삽입할 필요가 없다. 대신 데이터 준비를 자동으로 수행하는 prepareRequestData() 메서드를 호출한다.

//DataBinding 구현 여부 검사하여, 해당 인터페이스를 구현한 경우에만 호출
if(pageController instanceof DataBinding) {
    prepareRequestData(request, model, (DataBinding)pageController);
}

매개변수 값이 필요한 페이지 컨트롤러에 대해 DataBinding 인터페이스를 구현하기로 규칙을 정했다. 따라서 DataBinding을 구현했는지 여부를 검사하여, 해당 인터페이스를 구현한 경우에만 prepareRequestData() 메서드를 호출하여 페이지 컨트롤러를 위한 데이터를 준비했다.

 

- prepareRequestData() 메서드

prepareRequestData() 메서드에서 어떤 방법으로 데이터를 준비하는지 살펴본다. 먼저 페이지 컨트롤러에게 필요한 데이터가 무엇인지 묻는다.

//페이지 컨트롤러에게 필요한 데이터 묻기
Object[] dataBinders = dataBinding.getDataBinders();

getDataBinders() 메서드가 반환하는 것은 ["데이터이름", 데이터타입, "데이터이름", 데이터타입 ...] 으로 나열된 Object 배열일 것이다.

배열을 반복하기 전에 배열에서 꺼낸 값을 보관할 임시 변수를 준비한다. 데이터 이름(String), 데이터 타입(Class), 데이터 객체(Object)를 위한 참조 변수이다.

//배열에서 꺼낸 값 보관할 임시 변수
String dataName = null; //데이터 이름(String)
Class<?> dataType = null; //데이터 타입(Class)
Object dataObj = null; //데이터 객체(Object)

데이터 이름과 데이터 타입을 꺼내기 쉽게 2씩 증가하면서 반복문을 돌린다.

//배열 반복하며 값 꺼내기
for(int i=0 ; i < dataBinders.length ; i+=2) {
    dataName = (String)dataBinders[i];
    dataType = (Class<?>)dataBinders[i+1];
    dataObj = ServletRequestDataBinder.bind(request, dataType, dataName);
    model.put(dataName, dataObj);
}

for문 안을 들여다보면, ServletRequestDataBinder 클래스의 bind() 메서드를 호출하고 있다. 이 메서드는 dataName과 일치하는 요청 매개변수를 찾고 dataType을 통해 해당 클래스의 인스턴스를 생성한다. 찾은 매개변수 값을 인스턴스에 저장하며 그 인스턴스를 반환한다.

dataObj = ServletRequestDataBinder.bind(request, dataType, dataName);
model.put(dataName, dataObj);

bind() 메서드가 반환한 데이터 객체는 Map 객체에 담는다. 이 작업을 통해 페이지 컨트롤러가 사용할 데이터를 준비하는 것이다.

 

참고도서 : https://freelec.co.kr/book/1674/

 

[열혈강의] 자바 웹 개발 워크북

[열혈강의] 자바 웹 개발 워크북

freelec.co.kr