Java 프레임워크 만들기 - JSP

자바 서블릿으로 컨트롤러 만들기(모델뷰 처리) 3 - Java Servlet Controller ModelAndView, 이클립스(Eclipse)

carrotweb 2021. 4. 26. 17:47
728x90
반응형

컨트롤러(Controller)의 리턴 값으로 "RequestDispatcher"를 사용하기 때문에 JSP 페이지 처리에는 문제가 없지만 "Ajax" 요청을 처리할 수 없습니다. 그리고 서비스(Service Class)에도 "HttpServletRequest request", "HttpServletResponse", "RequestDispatcher"를 사용하고 있어 클래스간의 의존관계가 높습니다.

그래서 "RequestDispatcher"대신 새로운 처리 전달 객체를 만들어 처리 조건에 따라 JSP 페이지 처리나 Response이 처리되게 하겠습니다. 그리고 서비스(Service Class)에서도 의존관계를 줄이기 위해 "RequestDispatcher"와 "request.setAttribute"를 대처하여 처리할 수 있게 Model(모델) 과 View(뷰)를 결합한 "ModelAndView"란 객체를 만들어 사용하겠습니다. "SpringFramework"의 "ModelAndView"와 유사한 기능으로 처리되게 하겠습니다.

1. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.home.project.test2.controller"를 선택한 후 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Class]를 클릭하고 "Constructors from superclass"를 선택하여 "ModelAndView.java"를 생성합니다.

"viewName"은 JSP파일 리소스 경로(path)로 컨트롤러(Controller)에서 "RequestDispatcher"로 처리되게 하고 "modelMap"은 모델명과 객체를 관리하여 JSP파일에서 처리되게 합니다.

/**
 * 뷰명
 */
private String viewName = "";

/**
 * 모델맵
 */
private Map<String, Object> modelMap = new HashMap<String, Object>();

마우스를 "viewName"과 "modelMap"위로 이동하고 툴팁에서 "Create getter and setter ..."를 선택하여 "Getter"와 "Setter"를 생성합니다.

/**
 * 뷰명을 가져옵니다.
 * @return 뷰명
 */
public String getViewName() {
	return viewName;
}

/**
 * 뷰명을 설정합니다.
 * @param viewName 뷰명
 */
public void setViewName(String viewName) {
	this.viewName = viewName;
}

/**
 * 모델맵을 가져옵니다.
 * @return 모델맵
 */
public Map<String, Object> getModelMap() {
	return modelMap;
}

/**
 * 모델맵을 설정합니다.
 * @param modelMap 모델맵
 */
public void setModelMap(Map<String, Object> modelMap) {
	this.modelMap = modelMap;
}

"viewName"를 파라메타로 하는 생성자를 추가합니다.

/**
 * ModelAndView 생성자
 * @param viewName 뷰명
 */
public ModelAndView(String viewName) {
	this.viewName = viewName;
}

모델맵에 모델명과 모델 객체를 추가하고 찾고 삭제하는 메소드를 생성합니다.

/**
 * 모델을 추가합니다.
 * @param modelName 모델명
 * @param modelObject 모델 객체
 * @return 모델 추가 여부
 */
public boolean addModel(String modelName, Object modelObject) {
	if (modelName == null || modelName.isEmpty()) {
		System.out.println("모델명이 Null이거나 빈문자열입니다.");
		return false;
	}

	boolean bResult = false;

	try {
		if (modelMap.containsKey(modelName)) {
			System.out.println("모델맵에 동일한 모델명이 있습니다. 기존 모델명에 새로운 객체로 변경합니다.");
		}
		modelMap.put(modelName, modelObject);
		bResult = true;
	} catch (Exception e) {
		System.out.println("모델맵에 모델을 추가하지 못하였습니다. 에러정보 : " + e.toString());
	}

	return bResult;
}

/**
 * 모델을 가져옵니다.
 * @param modelName 모델명
 * @return 모델 객체
 */
public Object getModel(String modelName) {
	if (modelName == null || modelName.isEmpty()) {
		System.out.println("모델명이 Null이거나 빈문자열입니다.");
		return null;
	}

	Object object = null;

	try {
		if (modelMap.containsKey(modelName)) {
			object = modelMap.get(modelName);
		} else {
			System.out.println("모델맵에서 모델을 찾지 못하였습니다.");
		}
	} catch (Exception e) {
		System.out.println("모델맵에서 모델을 찾지 못하였습니다. 에러정보:" + e.toString());
		object = null;
	}

	return object;
}

/**
 * 모델을 삭제합니다.
 * @param modelName 모델명
 * @return 모델 삭제 여부
 */
public boolean removeModel(String modelName) {
	if (modelName == null || modelName.isEmpty()) {
		System.out.println("모델명이 Null이거나 빈문자열입니다.");
		return false;
	}

	boolean bResult = false;

	try {
		if (modelMap.containsKey(modelName)) {
			modelMap.remove(modelName);
			bResult = true;
		} else {
			System.out.println("모델맵에서 모델을 찾지 못하였습니다.");
		}
	} catch (Exception e) {
		System.out.println("모델맵에서 모델을 삭제하지 못하였습니다. 에러정보:" + e.toString());
	}

	return bResult;
}

/**
 * 모든 모델을 지웁니다.
 */
public void clear() {
	modelMap.clear();
	System.out.println("모델맵에 있는 모든 모델을 지웠습니다.");
}

/**
 * 모델맵이 비어있는지 확인합니다.
 * @return 빈 모델맵 여부
 */
public boolean isModelEmpty() {
	return (modelMap.size() == 0);
}

/**
 * 뷰명이 비어있는지 확인합니다.
 * @return 빈 뷰명 여부
 */
public boolean isViewNameEmpty() {
	return viewName.isEmpty();
}

2. "ITestController.java"에 있는 "doProcess"의 리턴 값을 "RequestDispatcher"에서 "ModelAndView"로 변경합니다.

/**
 * 프로세스를 처리합니다.
 * @param request HTTP 요청 객체
 * @param response HTTP 응답 객체
 * @return RequestDispatcher 객체
 */
public abstract RequestDispatcher doProcess(HttpServletRequest request, HttpServletResponse response);
/**
 * 프로세스를 처리합니다.
 * @param request HTTP 요청 객체
 * @param response HTTP 응답 객체
 * @return ModelAndView 객체
 */
public abstract ModelAndView doProcess(HttpServletRequest request, HttpServletResponse response);

그러면 "ITestController"를 구현(implements)한 "TestController"와 "TestController"를 사용한 "TestDispatcherServlet"도 오류가 발생합니다.

 

3. "TestController.java"에 있는 "doProcess"와 "methodInvoke"의 리턴 값을 "RequestDispatcher"에서 "ModelAndView"로 수정합니다.

@Override
public RequestDispatcher doProcess(HttpServletRequest request, HttpServletResponse response) {
	String contextPath = request.getContextPath();
	String requestURI = request.getRequestURI();
	if (null != contextPath && 0 < contextPath.length()) {
		requestURI = requestURI.replaceFirst(contextPath, "");
	}

	Method method = methodMappingMap.get(requestURI);
	if (null == method) {
		return null;
	}

	return methodInvoke(method, request, response);
}

/**
 * 컨트롤 클래스에있는 메소드를 호출합니다.
 * @param method 메소드 명
 * @param request HTTP 요청 객체
 * @param response HTTP 응답 객체
 * @return RequestDispatcher 객체
 */
private RequestDispatcher methodInvoke(Method method, HttpServletRequest request, HttpServletResponse response) {
	RequestDispatcher requestDispatcher = null;

	Object parameter[] = new Object[2];
	parameter[0] = request;
	parameter[1] = response;

	try {
		requestDispatcher = (RequestDispatcher)method.invoke(this, parameter);
		System.out.println(this.toString() + "의 " + method.getName() + "메소드를 호출함");
	} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
		System.out.println(this.toString() + "의 " + method.getName() + "메소드를 호출하지 못함");
		requestDispatcher = null;
		//e.printStackTrace();
	}

	return requestDispatcher;
}
@Override
public ModelAndView doProcess(HttpServletRequest request, HttpServletResponse response) {
	String contextPath = request.getContextPath();
	String requestURI = request.getRequestURI();
	if (null != contextPath && 0 < contextPath.length()) {
		requestURI = requestURI.replaceFirst(contextPath, "");
	}

	Method method = methodMappingMap.get(requestURI);
	if (null == method) {
		return null;
	}

	return methodInvoke(method, request, response);
}

/**
 * 컨트롤 클래스에있는 메소드를 호출합니다.
 * @param method 메소드 명
 * @param request HTTP 요청 객체
 * @param response HTTP 응답 객체
 * @return ModelAndView 객체
 */
private ModelAndView methodInvoke(Method method, HttpServletRequest request, HttpServletResponse response) {
	ModelAndView modelAndView = null;

	Object parameter[] = new Object[2];
	parameter[0] = request;
	parameter[1] = response;

	try {
		modelAndView = (ModelAndView)method.invoke(this, parameter);
		System.out.println(this.toString() + "의 " + method.getName() + "메소드를 호출함");
	} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
		System.out.println(this.toString() + "의 " + method.getName() + "메소드를 호출하지 못함");
		modelAndView = null;
		//e.printStackTrace();
	}

	return modelAndView;
}

 

 

4. "TestDispatcherServlet.java"에 있는 "service"를 수정합니다.

ITestController controller = (ITestController) requestMappingMap.get(requestURI);
if (null != controller) {
	System.out.println("[RequestMapping] URL : " + requestURI + "요청을 " + controller + "로 실행함");
	requestDispatcher = controller.doProcess(request, response);
} else {
ITestController controller = (ITestController) requestMappingMap.get(requestURI);
if (null != controller) {
	System.out.println("[RequestMapping] URL : " + requestURI + "요청을 " + controller + "로 실행함");
	ModelAndView modelAndView = null;
	modelAndView = controller.doProcess(request, response);
	if (null != modelAndView) {
		if (!modelAndView.isModelEmpty()) {
			Iterator<String> modelKeyIter = modelAndView.getModelMap().keySet().iterator();
			while (modelKeyIter.hasNext()) {
				String modelName = modelKeyIter.next();
				Object modelObject = modelAndView.getModel(modelName);
				request.setAttribute(modelName, modelObject);
			}
		}
		if (!modelAndView.isViewNameEmpty()) {
			RequestDispatcher dispatcher = request.getRequestDispatcher(modelAndView.getViewName());
			dispatcher.forward(request, response);
		}
	}
} else {

"ITestService.java"는 서비스 호출을 위해 만들어진 인터페이스(interface)로 컨트롤러(Controller)에서 동적으로 호출하기 위해 만들었습니다. 그래서 "TestService1.java"만을 위한 새로운 인터페이스(interface)를 만들어서 처리하겠습니다.

5. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.home.project.test2.service"를 선택한 후 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Interface]를 클릭하여 "ITestService1"를 생성합니다.

"TestService1.java"에서는 이전부터 테스트로 "request.setAttribute("name", "철수");" 를 사용하였습니다. 그래서 이름을 가져오는 메소드로 "getUserName"를 선언하였습니다.

public interface ITestService1 {

	/**
	 * 성별로 사용자명을 가져옵니다.
	 * @param gender 성별
	 * @return 사용자명
	 */
	public abstract String getUserName(int gender);
}

 

"TestService1.java"의 인터페이스(interface)인 "ITestService"를 새로만든 "ITestService1"를 수정하고 이전에 테스트를 위해 만들어진 소스는 "TestRootController.java"로 이동시켜 삭제합니다.

public class TestService1 implements ITestService {

public void process(HttpServletRequest request) {
	System.out.println("Locale : " + request.getLocale());
	System.out.println("Default Locale : " + Locale.getDefault());

	request.getSession().setMaxInactiveInterval(10);
	TestSessionCounter testSessionCounter = TestSessionCounter.getInstance();
	request.setAttribute("sessionTotalCount", testSessionCounter.getTotalCount());
	request.setAttribute("sessionCount", testSessionCounter.getCount());
	request.setAttribute("name", "철수");
}

@Override
public RequestDispatcher doProcess(HttpServletRequest request, HttpServletResponse response) {
	request.getSession().setMaxInactiveInterval(1000);
	TestSessionCounter testSessionCounter = TestSessionCounter.getInstance();
	request.setAttribute("sessionTotalCount", testSessionCounter.getTotalCount());
	request.setAttribute("sessionCount", testSessionCounter.getCount());
	request.setAttribute("name", "철수");
		
	RequestDispatcher requestDispatcher = request.getRequestDispatcher("/WEB-INF/jsp/testservlet.jsp");
	return requestDispatcher; 
}
public class TestService1 implements ITestService1 {

	@Override
	public String getUserName(int gender) {
		String userName = "";
		if (gender == 1) {
			userName = "철수";
		} else if (gender == 2) {
			userName = "영희";
		}
		return userName;
	}
}

 

"TestDispatcherServlet.java"에서 "TestService1.java"의 "process"가 없어져서 에러가 발생합니다. 이전 소스에서 "/test1.do"과 "/test2.do"를 주석처리하겠습니다.

/*
if ("/test1.do".equals(requestURI)) {
} else if ("/test2.do".equals(requestURI)) {
} else
*/

 

6. "TestRootController.java"에 있는 "test1"를 수정합니다.

public class TestRootController extends TestController {

	/**
	 * ITestService 인터페이스 객체
	 */
	ITestService testService = null;

	/**
	 * TestRootController 생성자
	 */
	public TestRootController() {
		TestService1 testService1 = new TestService1();
		testService = (ITestService)testService1;
	}

	/**
	 * TestService1의 doProcess를 실행합니다.
	 * @param request HTTP 요청 객체
	 * @param response HTTP 응답 객체
	 * @return RequestDispatcher 객체
	 */
	public RequestDispatcher test1(HttpServletRequest request, HttpServletResponse response) {
		return testService.doProcess(request, response);
	}
}
public class TestRootController extends TestController {

	/**
	 * ITestService1 인터페이스 객체
	 */
	ITestService1 testService = null;

	/**
	 * TestRootController 생성자
	 */
	public TestRootController() {
		TestService1 testService1 = new TestService1();
		testService = (ITestService1)testService1;
	}

	/**
	 * test1를 실행합니다.
	 * @param request HTTP 요청 객체
	 * @param response HTTP 응답 객체
	 * @return ModelAndView 객체
	 */
	public ModelAndView test1(HttpServletRequest request, HttpServletResponse response) {
		ModelAndView modelAndView = new ModelAndView();

		System.out.println("Locale : " + request.getLocale());
		System.out.println("Default Locale : " + Locale.getDefault());

		request.getSession().setMaxInactiveInterval(60 * 60);
		TestSessionCounter testSessionCounter = TestSessionCounter.getInstance();
		modelAndView.addModel("sessionTotalCount", testSessionCounter.getTotalCount());
		modelAndView.addModel("sessionCount", testSessionCounter.getCount());

		String userName = testService.getUserName(1);
		modelAndView.addModel("name", userName);
		
		modelAndView.setViewName("/WEB-INF/jsp/testservlet.jsp");
		return modelAndView;
	}
}

섹션 유효시간을 60분으로 변경했습니다.

 

 

7. "Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭합니다. 웹 브라우저에서 "http://localhost:8080/test2/test1.do"를 입력합니다.

 

8. "test2" 프로젝트의 "Java Resources"에 "src/main/resources"에서 "/com/home/project/test2/config"에 있는 "requestmapping.properties"파일에 "/test2.do"를 추가합니다.

/test2.do=com.home.project.test2.controller.TestRootController@test2

 

9. "TestRootController.java"에 있는 "test2"를 추가합니다.

/**
 * test2를 실행합니다.
 * @param request HTTP 요청 객체
 * @param response HTTP 응답 객체
 * @return ModelAndView 객체
 */
public ModelAndView test2(HttpServletRequest request, HttpServletResponse response) {
	ModelAndView modelAndView = new ModelAndView();

	request.getSession().setMaxInactiveInterval(60 * 60);
	TestSessionCounter testSessionCounter = TestSessionCounter.getInstance();
	modelAndView.addModel("sessionTotalCount", testSessionCounter.getTotalCount());
	modelAndView.addModel("sessionCount", testSessionCounter.getCount());

	String userName = testService.getUserName(2);
	modelAndView.addModel("name", userName);
		
	modelAndView.setViewName("/WEB-INF/jsp/testservlet.jsp");
	return modelAndView;
}

 

10. "Servers"탭에서 "tomcat8"를 선택하고 "start"버튼(start the server)을 클릭합니다. 웹 브라우저에서 "http://localhost:8080/test2/test2.do"를 입력합니다.

 

이처럼 요청(Request) URL별로 컨트롤러(Controller)의 메소드(Method)가 호출되고 서비스(Service)와의 의존관계도 낮추었습니다.

Request -> DispatcherServlet -> Controller -> Service -> Controller -> DispatcherServlet -> Response 순으로 처리됩니다.

728x90
반응형