코딩항해기

[JSP] Controller - 심화 (싱글톤패턴 : 핸들러맵핑 적용) 본문

JSP

[JSP] Controller - 심화 (싱글톤패턴 : 핸들러맵핑 적용)

miniBcake 2024. 8. 20. 15:20

 

 

 

 

[JSP] Controller Servlet

기존 실습들에서는 Controller가 분할되었다 다시 하나로 통합되는 과정을 거쳤다.하지만 여전히 응집도가 낮고 하나의 기능을 수정하기 위해서는 전체 기능이 사용 불가하다는 단점이 있었다.또

minibcake.tistory.com

 

전의 게시물에서 작성했던 Controller 버전을 살펴보면, if문으로 분기를 나누는 기능 부분이 있다.

		ActionForward forward=null;
		if(command.equals("/main.do")) {
			MainAction mainAction=new MainAction();
			forward = mainAction.execute(request, response);
		}
		else if(command.equals("/login.do")) {
			LoginAction loginAction=new LoginAction();
			forward = loginAction.execute(request, response);
		}
		else if(command.equals("/joinPage.do")) {
			JoinPageAction joinPageAction=new JoinPageAction();
			forward = joinPageAction.execute(request, response);
		}
		else if(command.equals("/join.do")) {
			JoinAction joinAction=new JoinAction();
			forward = joinAction.execute(request, response);
		}
		else if(command.equals("/logout.do")) {
			LogoutAction logoutAction=new LogoutAction();
			forward = logoutAction.execute(request, response);
		}
		else if(command.equals("/board.do")) {
			forward = new BoardAction().execute(request, response);
		}

 

이 부분을 보면 매번 new 연산자를 사용해서 Action을 수행하고 있다. 즉, 하나의 요청 당 한 개의 new 연산자가 사용돼서 객체가 요청 개수 만큼 무한히 힙 메모리에 쌓이고 있다.

 

즉, 싱글톤 패턴이 깨진 상황이다.

싱글톤 패턴
new를 절약하는 패턴 중 하나로, 한 번 new해서 존재하는 객체가 있다면 해당 객체를 계속 재사용하는 패턴이다.

 

그럼 싱글톤 패턴을 지키기 위해 어떤 방법을 쓰면 좋을까?

싱글톤 패턴의 핸들러 맵핑을 사용할 수 있다.

 

핸들러 맵핑
싱글톤 패턴을 유지시키는 장치 중 하나로 가장 대표적인 장치이다.

 

 

그럼, 핸들러 맵핑을 만들어보자.

핸들러 맵핑은 Map을 필드 값으로 가지며, 요청에 따라 맞는 기능을 반환한다.

전 게시물을 기준으로 설명하면, if문 조건을 key값으로 받아 new Action()객체를 반환하는 것이다.

 

public class HandlerMapper {
	private Map<String, Action> mapper; //요청에 대해 기능을 반환
	
	public HandlerMapper() {
		this.mapper = new HashMap<>();
		
        //요청값과 그에 따른 Action을 작성한다.
		this.mapper.put("/main.do", new MainAction());
		this.mapper.put("/login.do", new LoginAction());
		this.mapper.put("/joinPage.do", new JoinPageAction());
		this.mapper.put("/join.do", new JoinAction());
		this.mapper.put("/logout.do", new LogoutAction());
		this.mapper.put("/board.do", new BoardAction());
	}

	//바로 value값을 반환한다.
	public Action getMapper(String command) {
		return this.mapper.get(command);
	}
}

 

새로운 기능이 생기면 if문 분기로 나누는 것이 아니라 핸들러 맵핑 기본 생성자에 키와 밸류로 추가하면 된다.

그럼 FrontController는 어떻게 바뀔까?

 

@WebServlet("*.do")
public class frontController extends HttpServlet {
	private static final long serialVersionUID = 1L; //직렬화 코드
	private HandlerMapper mappings;
       
    public frontController() {//기본생성자(웹에서는 기본생성자 사용)
        super();
        this.mappings = new HandlerMapper();//한 번만 핸들러 맵핑 객체를 만든다.
    }
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doAction(request, response);
	}
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doAction(request, response);
	}
	
	//get으로 오든 post로 오든 실행할 기능
	private void doAction(HttpServletRequest request, HttpServletResponse response) {
		// 1. 사용자가 무슨 요청을 했는지 추출 및 확인
		String uri=request.getRequestURI();
		String cp=request.getContextPath();
		String command=uri.substring(cp.length());
		System.out.println("command : "+command);
		
		// 2. 요청을 수행
		
        //핸들러 맵핑으로 싱글톤 패턴이 지켜진 코드
		Action action = mappings.getMapper(command);
		ActionForward forward = action.execute(request, response);
		
        //기존 코드
//		ActionForward forward=null;
//		if(command.equals("/main.do")) {
//			MainAction mainAction=new MainAction();
//			forward = mainAction.execute(request, response);
//		}
//		else if(command.equals("/login.do")) {
//			LoginAction loginAction=new LoginAction();
//			forward = loginAction.execute(request, response);
//		}
//		else if(command.equals("/joinPage.do")) {
//			JoinPageAction joinPageAction=new JoinPageAction();
//			forward = joinPageAction.execute(request, response);
//		}
//		else if(command.equals("/join.do")) {
//			JoinAction joinAction=new JoinAction();
//			forward = joinAction.execute(request, response);
//		}
//		else if(command.equals("/logout.do")) {
//			LogoutAction logoutAction=new LogoutAction();
//			forward = logoutAction.execute(request, response);
//		}
//		else if(command.equals("/board.do")) {
//			forward = new BoardAction().execute(request, response);
//		}
		
		// 3. 응답(페이지 이동 등)
		//  1) 전달할 데이터가 있니? 없니? == 포워드? 리다이렉트?
		//  2) 어디로 갈까? == 경로
		if(forward == null) {
			// command 요청이 없는 경우
			System.out.println("error 발생");
			forward = new ActionForward();
			forward.setRedirect(true);
			forward.setPath("error/error.jsp");
		}
		if(forward.isRedirect()) {
			try {
				response.sendRedirect(forward.getPath());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		else {
			RequestDispatcher dispatcher=request.getRequestDispatcher(forward.getPath());
			try {
				dispatcher.forward(request, response);
			} catch (ServletException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

 

주석 처리된 부분이 기존 코드고, 위의 두 줄의 코드가 핸들러 맵핑을 통해 변경된 코드다.

Action 인터페이스를 통해 메서드 명이 통일되어있기 때문에 나누어 쓸 필요없이 알맞게 반환된 객체의 메서드를 바로 호출할 수 있다.