https://pabeba.tistory.com/164
기존에 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를 통해서 코드를 리팩터링 했는데, 코딩은 결국 어떻게 더 편리하게 코드를 관리하느냐도 굉장히 중요하다고 생각합니다. 중복되고 반복되는 작업을 최대한 기피하려고 노력해야겠습니다. (코딩할 때만요)
'코딩 개발 > Java' 카테고리의 다른 글
Java - DBCP (Database Connection Pool) (0) | 2023.06.12 |
---|---|
AJAX - JSON 이용 (5) | 2023.06.09 |
MVC - FrontController Pattern (0) | 2023.05.31 |
JSTL 기본 문법 1 (param, getter&is 메서드 접근, if) (0) | 2023.05.29 |
JSTL (Jsp Standard Tag Library) (2) | 2023.05.29 |