코딩 개발/Java

Reflection API (feat. FrontController)

호소세 2023. 6. 4. 11:00
728x90
반응형

https://pabeba.tistory.com/164

 

MVC - FrontController Pattern

https://pabeba.tistory.com/157 Model 1 Architecture Model1 Architecture 이란? Model 1 은 View와 Model을 모두 JSP 페이지 하나에서 처리하는 구조를 말합니다. Java Bean은 class를 만들어서 로직을 작성해 놓은 것입니다.

pabeba.tistory.com

기존에 frontController를 이용해서 http 요청을 받아왔습니다. 그다음 hidden tag의 값을 이용해서 어떤 요청이 왔는지 확인했는데

만일 웹페이지에 이러한 요청이 500개가 있다고 생각했을 때

FrontController에 있는 doDispatch의 if, else if 문이 500개가 생길 것입니다. 이렇게 되면 코드를 유지보수할 때 굉장히 불편함을 겪을 것입니다.

 

그래서 그러한 불편함을 줄이기 위해서 Reflection API를 사용해 어떤 요청이 들어오든 그 요청을 처리하는 Controller로 연결해 줄 수 있습니다.

 

기존의 코드는

if (command.equals("findbyid")) {
	FindCustomerByIdController controller=new FindCustomerByIdController();
	String path=controller.findCustomer(request, response);
	request.getRequestDispatcher(path).forward(request, response);
} else if (command.equals("register")) { // 고객 등록
	RegisterController controller=new RegisterController();
	String url=controller.register(request, response);
	response.sendRedirect(url);
}

이렇게 계속 요청을 만들었어야했습니다.

 

이제 Reflection API를 이용하여 어떤 요청이 들어와도 controller로 잘 찾아갈 수 있게 만들어보겠습니다.

 

Reflection API란?

Reflection API는 자바에서 클래스, 메서드, 필드 등의 정보를 런타임에 얻을 수 있도록 해주는 API입니다. Reflection API를 사용하면 클래스 이름만 알고 있어도 해당 클래스의 정보를 얻을 수 있습니다. 또한, Reflection API를 사용하면 클래스, 메서드, 필드 등을 동적으로 생성하고 호출할 수 있습니다.

 

사용 예시

  • 클래스, 메서드, 필드 등의 정보를 얻을 때
  • 클래스, 메서드, 필드 등을 동적으로 생성하고 호출할 때
  • 디버깅을 할 때
  • 프레임워크를 개발할 때

Reflection API를 사용할 때 주의할 점

  • Reflection API는 성능이 저하될 수 있습니다. Reflection API를 사용하면 컴파일 타임에 컴파일되지 않은 코드를 실행해야 하기 때문에 성능이 저하될 수 있습니다. 따라서 Reflection API는 성능이 중요한 코드에서는 사용하지 않는 것이 좋습니다.
  • Reflection API는 보안 취약성을 유발할 수 있습니다. Reflection API를 사용하면 클래스, 인터페이스, 메서드, 변수에 대한 정보를 얻을 수 있습니다. 이 정보를 악용하여 보안 취약성을 유발할 수 있습니다. 따라서 Reflection API를 사용할 때는 보안을 고려해야 합니다.
  • Reflection API는 예기치 않은 동작을 유발할 수 있습니다. Reflection API를 사용하면 컴파일 타임에 컴파일되지 않은 코드를 실행해야 하기 때문에 예기치 않은 동작을 유발할 수 있습니다. 따라서 Reflection API를 사용할 때는 예기치 않은 동작을 유발할 수 있는 가능성을 고려해야 합니다.

 

 

이제 리팩토링을 통해서 기존의 지저분한 코드를 뜯어고쳐보아요!

 

예제

위에서 보여드린 것처럼 요청의 종류가 많아지면 if else 문이 지저분하게 계속 저렇게 작성될 것이고 유지보수에 굉장히 취약한 모습을 보입니다. 저 문장을 하나로 묶어버리는 작업을 reflection api를 사용해서 진행할 예정입니다.

 

index.jsp

일단 첫 번째로 index.jsp에서 form tag의 action 부분을 수정하겠습니다. *. do 로이.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web MVC + Front Controller Design Pattern</title>
</head>
<body>
<div>
<h4>Web MVC 기반 Front Controller Design Pattern 적용</h4>
<form action="FindCustomerById.do" method="get">
<input type="text" name="customerId" placeholder="아이디"  required="required">
<button type="submit">검색</button>
</form>
<br><br>
<form method="post" action="Register.do">
<input type="text" name="id"  placeholder="아이디" required="required"><br>
<input type="text" name="name"  placeholder="이름" required="required"><br>
<input type="text" name="address"  placeholder="주소" required="required"><br>
<button type="submit">고객등록</button>
</form>
</div>
</body>
</html>

FrontControllerServlet

다음은 FrontControllerServlet 파일을 변경하겠습니다.

@WebServlet("*.do")
public class FrontControllerServletVer7 extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		try {
			//	url-pattern 으로부터 command를 추출 
			 String servletPath=request.getServletPath();			
		     String command=servletPath.substring(1,servletPath.lastIndexOf("."));
			// Reflection API 로 자동으로 컨트롤러 객체 생성되게 업데이트
			Controller controller = HandlerMapping.getInstance().create(command);			
			String path = controller.handleRequest(request, response);
			if(path.startsWith("redirect:"))
				response.sendRedirect(path.substring(9));
			else
			    request.getRequestDispatcher(path).forward(request, response);
			
		} catch (Exception e) {
			e.printStackTrace();
			//공통정책 : 문제 발생시 콘솔에 메세지 남기고 사용자에게는 error.jsp 를 응답한다 
			response.sendRedirect("error.jsp");
		}
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doDispatch(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// post 방식 한글처리 => 공통정책
		request.setCharacterEncoding("utf-8");
		doDispatch(request, response);
	}
}

위에서부터 한 줄 한줄 읽어보겠습니다.

1.  annotation WebServlet("*. do")로 변경하면서 form에서 요청을 줄 때. do로 주는 모든 요청을 받아올 수 있습니다.

2. servletPath 변수를 활용해서 url의 path를 저장해 놓습니다.  (예시 : FindCustomerById.do)

3. command는. do를 제외한 부분을 저장합니다. (예시 : FindCustomerById)

4. HandlerMapping에서 reflection api를 사용해 class를 만들어 줄 것입니다. (조금 있다가 설명 예정)

 

일단 여기까지 설명해 놓고 reflection api를 보러 가보죠!

 

HandlerMapping

public class HandlerMapping {
	private static HandlerMapping instance = new HandlerMapping();

	private HandlerMapping() {
	}

	public static HandlerMapping getInstance() {
		return instance;
	}
   
	public Controller create(String command) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		StringBuilder classInfo=new StringBuilder(this.getClass().getPackage().getName());
		classInfo.append(".").append(command).append("Controller");
		return (Controller) Class.forName(classInfo.toString()).newInstance();
	}
}

객체의 무분별한 생성을 방지하기 위해서 singleton 패턴을 이용하였습니다.

Singleton pattern 관련 url -> https://pabeba.tistory.com/159

 

밑에 create 메서드를 살펴보시면

1. StringBuilder를 통해서 Controller들이 저장된 package(제가 제작한 프로그램은 package 안에 Controller가 다 있습니다) 이름을 가져옵니다.

2. 앞에 FrontController에서 가져온 url의 path 부분을 class package 명에 붙이고, 뒤에 Controller라는 이름도 붙여줍니다.

(결과 예시 : myproject.controller.FindCustomerByIdController) 이렇게 결과가 나옵니다.

3. 그다음 return 값으로 Reflection API를 이용해서 만든 class를 만들어서 보내주는 것입니다.

 

이렇게 되면 어떠한 요청이 들어와도 Controller의 명명만 잘한다면 알아서 어떤 Controller와 연결해야 하는지 알 수 있습니다.

 

소감

복습의 중요성을 깨닫게 되는 한 주였습니다. 분명히 1달 전에는 유창하게 잘 말할 수 있었던 것들인데, 지금에 와서 이야기하려고 보니 전혀 못하겠더라고요. 그래서 시간이 날 때마다 시간을 허투루 사용하지 않고 제가 이루고자 하는 목표물을 향해 시간을 소비해야겠다고 생각했습니다.

Reflection API를 통해서 코드를 리팩터링 했는데, 코딩은 결국 어떻게 더 편리하게 코드를 관리하느냐도 굉장히 중요하다고 생각합니다. 중복되고 반복되는 작업을 최대한 기피하려고 노력해야겠습니다. (코딩할 때만요)

반응형