새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
MVC
목표 D-day : 52 일
학습목표Permalink
- MVC에 대해서 학습한다.
JSP 모델 구조Permalink
모델 1Permalink
모델 1 구조는 웹 브라우저의 요청을 JSP가 직접 처리한다.
JSP 페이지 내에서 클라이언트의 요청을 받아 서비스 클래스를 생성이나 호출하여 작업을 직접 제어한다.
요청한 작업의 결과를 클라이언트에게 출력한다.
JSP 페이지내에서 비즈니스 로직을 처리하기 위한 코드와 웹 브라우저에 결과를 출력하는 코드가 섞인다는 것
- 클라이언트의 요청을 받고 필요한 비즈니스 로직을 선택하는 흐름 제어
- 비즈니스 로직을 수행하는 코드
- HTML 코드를 작성하여 결과를 사용자에게 응답
모든 코드가 하나의 파일 안에 작성되는 방식
-
장점
전체 코드가 짧은 경우 빠르게 개발할 수 있다는 장점
-
단점
비즈니스 코드와 요청 결과 화면이 모두 같이 있어 테스트를 하기 어렵다.
비즈니스 로직을 변경해도 해당 파일을 변경해야하고, 출력 화면을 수정해도 해당 파일을 변경해야한다.
코드가 길어질 경우 유지보수가 어려워진다.
모델 2Permalink
모델 2 구조는 웹 브라우저의 요청을 서블릿이 처리한다.
서블릿은 필요한 비즈니스 로직을 호출하여 클라이언트의 데이터를 전달하고 결과를 받는다.
사용자 요청에 맞는 결과 화면을 선택하여 포워딩을 하거나 리다이렉트로 결과 화면을 선택한다.
JSP 모델 2는 이러한 특징으로 MVC 패턴과 비슷하다.
MVC 패턴Permalink
MVC 패턴은 크게 모델, 뷰, 컨트롤러의 세 부분으로 구성되며 각 부분의 역할이 분명히 분리되어 코드가 더 간결하고 유지보수가 쉽다.
- 모델(Model) : 데이터와 비즈니스 로직 처리
- 뷰(JSP) : 사용자가 보는 화면(HTML)을 구성
- 컨트롤러(서블릿) : 어플리케이션의 흐름 제어나 사용자의 처리 요청을 한다.
컨트롤러는 사용자가 전달한 데이터와 요청에 맞는 모델을 선택하여 데이터를 전달한다.
모델은 입력받은 데이터로 비즈니스 로직을 처리하고 컨트롤러에게 결과를 반환한다.
컨트롤러는 반환된 결과에 따라 뷰(결과 화면)을 선택한다.
비즈니스 로직이 변경되면 모델 코드만 변경하면된다.
결과 화면이 수정되면 JSP 파일만 수정하면 된다.
사용자의 처리 방식이나 웹 컴포넌트 흐름이 달라지는 경우 컨트롤러만 변경하면 된다.
이렇게 역할을 분리하는 경우 장점이 있습니다.
-
유지보수하기 편합니다.
-
관심사의 분리로 각 역할에 맞는 코드를 명확하게 작성할 수 있습니다.
-
확장하기 유리합니다.
결과에 따라
HTML
이나JSON
으로 반환할지 선택할 수 있어 디바이스에 따라 확장하기 용이합니다. -
데스트하기 편합니다.
비즈니스 로직을 테스트하려면 모델만 별도로 테스트를 할 수 있습니다.
컨트롤러는 모델과 뷰에 의존한다.Permalink
컨트롤러는 흐름을 제어하고 필요한 모델과 뷰를 선택하여 호출한다.
모델을 직접 의존하는 경우에는 강한 결합이 생길 수 있다.
따라서 직접 의존하지 않고 의존성 주입을 통해 결합을 낮추면 확장에 유리하게 코드를 작성할 수 있다.
뷰는 결과를 정해진 템플릿에 맞춰 데이터를 렌더링하는 것이 목적이므로 결과에 따라 화면이 정해집니다.
대신 뷰 리졸버를 통해서 다른 템플릿 엔진을 선택할 수 있습니다.
단, 웹 어플리케이션에 일괄적으로 적용하는 공통적인 기능인 사용자인증이나 보안과 같은 것은 컨트롤러에서 처리하는 것이 유지보수에 유리합니다.
컨트롤러의 역할
- HTTP 요청을 받는다.
- 클라이언트가 요구하는 기능을 분석한다.
- 요청한 비즈니스 로직을 처리하는 모델을 사용한다.
- 결과를 request 또는session에 저장한다.
- 알맞은 뷰를 선택후, 뷰로 포워딩(리다이렉트)한다.
모델은 관련된 로직만 처리한다.Permalink
모델은 함수 처럼 입력받은 데이터를 가지고 결과를 컨트롤러에 반환하면 됩니다.
사용자에게 어떤 화면을 보여줄지, 어떻게 사용자 요청이 흘러가는지 알 필요가 없습니다.
이렇게 분리를 해야 모델의 내부 로직이 변경되더라도 뷰에는 영향이 없습니다.
모델은 명확하게 어떤 것을 통해서 구현해야한다는 규칙이 없습니다.
비즈니스 로직을 처리해주면 모델이 될 수 있습니다.
모델은 서비스 클래스나 DAO 클래스를 이용해서 비즈니스 로직을 수행합니다.
처리 결과는 자바빈을 통해 반환됩니다.
자바빈 클래스는 자바빈 규약을 지키며, private 필드에 접근하기 위한 getter
와 setter
메서드가 있는 클래스입니다.
커맨드 패턴Permalink
컨트롤러는 사용자가 어떤 기능을 요청했는지 분석하는 과정이 필요합니다.
커맨드 패턴을 통해서 글 목록 조회
, 글 쓰기 폼
, 사용자 정보 조회
와 같이 기능 요청을 명령어를 사용하는 방식이 있습니다.
웹 브라우저를 통해서 명령어를 전달하는 방법은 두 가지가 있습니다.
- 특정 이름의 파라미터에 명령어 정보를 전달한다.
- 요청
URL
자체를 명령어로 사용한다.
파라미터에 명령어 정보 전달Permalink
localhost:4000/contoller?cmd=findInfo
와 같이 cmd
라는 특정 이름의 파라미터에 명령어를 전달하는 방식입니다.
서버는 request.getParameter("cmd")
로 findInfo
에 맞는 뷰 페이지를 처리합니다.
String command = request.getParameter("cmd");
String viewPage = null;
if(command == null){
// 명령어 오류 처리
} else if(command.equals("findInfo")){
/** 사용자 정보 조회 */
// 1. DB에서 사용자 정보를 조회한다.
// 2. request에 사용자 정보를 저장한다.
// 3. 관련된 userInfo.jsp 파일로 응답한다.
viewPage = "/info.jsp"
}
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPage);
dispatcher.forword(request,reponse);
이런 방식으로 명령어와 관련된 로직 처리를 컨트롤러 서블릿에서 하게 된다면 명령어가 많아지고 복잡해지면 유지보수가 어려워집니다.
로직 처리 코드Permalink
이런 복잡함을 줄이기 위해 각 명령어를 처리할 수 있는 별도 클래스를 만들어서 사용하는 방식입니다.
String command = request.getParameter("cmd");
CommandHandler handler = null; // 인터페이스 process 추상메서드를 가지고 있다.
if(command == null){
// 명령어 오류 처리
handler = new ErrorPageHandler();
} else if(command.equals("findInfo")){
/** 사용자 정보 조회 */
handler = new FindMemberHandler();
}
/** 각각 명령어를 처리하는 실행 코드를 가진 클래스를 호출한다. */
String viewPage = handler.process(request,response);
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPage);
dispatcher.forword(request,reponse);
실제 로직 처리는 CommandHandler
를 통해서 처리를 합니다.
public interface CommandHandler {
String process(HttpSevlertRequest req, HttpServletResponse res) throw Exception;
}
명령어를 처리하는 로직은 인터페이스를 구현하면 됩니다.
public class FindMemberHandler implements CommandHandler {
public String process(HttpSevlertRequest req, HttpServletResponse res) throw Exception {
// 1. 회원을 조회하는 로직 실행
// 2. 뷰 페이지에 저장할 정보를 저장
request.setAttribute("userInfo", info);
// 3. 사용할 뷰 페이지 URI 리턴
return "/info.jsp";
}
}
현재 방식은 명령어 처리 클래스를 별도로 만들어 컨트롤러 서블릿 안에 사용자 요청 처리하는 로직을 분리했습니다.
Handler
는 명령어를 처리하는 로직과, 처리 결과를 보여줄 뷰 페이지를 반환합니다.
컨트롤러 서블릿은 Handler
가 반환한 뷰 페이지로 이동하고, 뷰 페이지는 Handler
가 저장한 데이터를 표현합니다.
커맨드 설정과 컨트롤러 문제Permalink
커맨스 설정이 추가될때마다 컨트롤러에 중첩된 if-else
문장이 추가됩니다.
명령어가 추가 될때마다 컨트롤러 서블릿 코드를 직접 변경해야하는 단점이 있습니다.
-
명령어 추가를 코드가 아닌 설정 파일로 관리하면 컨트롤러 코드를 변경하지 않아도 됩니다.
-
if-else
가 길어지는 것보다Map
자료구조를 활용하면 불필요한 순회를 줄 일 수 있습니다.
명령어 방식 설정파일
// commandHandler.properties
findMember=com.command.FindMemberController
addMember=com.command.AddMemberController
:
URL 방식 설정파일
// commandHandler.properties
/find/member=com.command.FindMemberController
/add/member=com.command.AddMemberController
:
설정파일 조회방법
properties
파일은 서블릿 컨테이너 설정파일인 web.xml
에 추가하여 서블릿 마다 필요한 파일을 등록하여 읽을 수 있습니다.
// web.xml
<servlet>
<servlet-name>CommandServlet</servlet-name>
<servlet-class>com.example.CommandServlet</servlet-class>
<!-- init-param으로 파일 경로 등록 -->
<init-param>
<param-name>configFile</param-name>
<param-value>/WEB-INF/commandHandler.properties</param-value>
</init-param>
</servlet>
설정 파일에 등록하면 서블릿 init()
메서드에서 등록한 클래스를 읽을 수 있습니다.
getInitParameter()
, getServletContext()
등을 활용하여 파일 읽고, value는 리플렉션으로 인스턴스로 생성하여
private Map<String, CommandHandler> commandHashMap
으로 등록하여 사용할 수 있습니다.
명령어 방식은 URL에 사용자의 요청이 명령어로 노출이 되며, 명령어를 작성하게 되면 HTTP 규약과 맞지 않을 수 있습니다.
URL 방식은 RESTful API 설계와 같이 리소스 중심으로 자연스럽게 설계할 수 있으며 웹 프레임워크에도 적합합니다.
댓글남기기