매일_공부

MVC 패턴이란..? [정의부터 한계까지] + 서블릿,JSP

매일_공부 2024. 4. 19. 20:52
반응형

 기존에 웹사이트를 개발할 때에는 서블릿을 사용하여 HTTP 요청과 응답을 통해 개발하였다.

서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱해준다.

 

서블릿이란..?

 

클라이언트의 요청을 처리하고, 그 결과를 반환하는 Servlet 클래스의 구현 규칙을 지킨 자바 웹 프로그램의 기술

 

MVC 패턴에서의 C인 Controller의 역할로 이용한다. 

쉽게 서블릿 = 컨트롤러라고 이해하면 간단할 것 같다. 

 

 

 

1. 클라이언트는 서버에게 URL 요청을 보낸다.-> HTTP Request가 서블릿 컨테이너에게 전송된다.

2. web.xml을 기반으로 요청한 URL에 맞는 서블릿을 찾아낸다.

3. 서블릿 컨테이너는 HttpServletRequest, HttpServletResponse 객체를 생성한다.

4. 해당 서블릿에서 service 메소드를 호출한 후 클라이언트의 GET, POST 여부에 따라 doGet() 또는 doPost()를 호출한다.

5. 호출된 메서드는 역할을 수행한후에 HttpServletResponse 객체에 응답을 보낸다.

6. 응답이 끝나면 HttpServletRequest, HttpServletResponse 두 객체를 소멸시킨다.

 

 

 

웹서버의 요청의 흐름

 


 

서블릿 컨테이너

 

그렇다면 서블릿 컨테이너는 무엇일까?

서블릿 컨테이너는 서블릿을 관리해주는 컨테이너이다.

 

서블릿은 스스로 작동할 수가 없다. 스프링으로 생각해보자. 가만히 있는 컨트롤러가 URL을 인식해서 저절로 실행이 될까?

 

아니다. 서블릿 컨테이너가 URL을 입력받은 후 알맞은 컨테이너를 실행해주고, 결과 값을 받아 다시 전달해 주는 거다.

 

이처럼 서블릿은 클라이언트의 요청을 받아주고 응답할 수 있게, 웹 서버와 소캣으로 통신한다.

대표적이 예로는 톰캣(Tomcat)이 있다. 

 

서블릿 컨테이너의 역할

 

 

    • 웹서버와의 통신 지원
    • 서블릿 컨테이너는 서블릿과 웹서버가 손쉽게 통신할 수 있게 해준다.   
    • 일반적으로는 소켓을 만들고, listen, accept 등의 처리를 해야하지만
    • 서블릿 컨테이너는 이러한 기능을 API로 제공하여 복잡한 과정을 생략할 수 있게 해준다.

 

    • 서블릿 생명주기
    • 서블릿 컨테이너는 서블릿의 생성과 소멸을 관리한다
    • 서블릿 클래스를 로딩하여 인스턴스화하고, 초기화 메소드르 호출하고,
    • 요청이 들어오면 적절한 서블릿 메소드를 호출한다.
    • 또한 생명을 다한 서블릿을 적절하게 Garbage Collection을 진행하여 편의를 제공한다.
    • 요청이 들어오면 서블릿 컨테이너는 메로리에 해당 URL의 서블릿의 유무를 파악 후, init() 메소드로 적재한다.
    • 실행 중 서블릿이 변경되면 기존 서블릿을 파괴하고 init()을 통해 새로 적재한다.
    • 서블릿 컨테이너가 서블릿을 소멸시킬 때 destroy()가 호출되는데, 종료시에 처리하는 작업들은 destroy()를 오버라이딩하여 구현한다.

 

  • 서블릿 생명주기
  • 서블릿 컨테이너는 요청이 올 때마다 새로운 자바 쓰레드를 하나 생성한다.
  • 그때 HTTP 서비스 메소드를 실행하고 나면 쓰레드는 자동으로 죽게된다.
  • 따라서 쓰레드의 안정성에 대해서 걱정하지 않아도 된다.

 

 

 


JSP란..?

java 코드가 들어가 있는 HTML 코드

 

기존의 서블릿 컨테이너가 만든 HttpResponse로 결과를 반환하는 과정은 매우 번거로웠다.

 

public class Example04 extends HttpServlet{

	private static final long serialVersionUID = 1L;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		req.setCharacterEncoding("utf-8");
		String name = req.getParameter("name");
		String phone = req.getParameter("phone");
		String addr = req.getParameter("addr");
		
		System.out.println("get입니다.");
		System.out.println("name : "+ name + " phone: " + phone + " addr: " + addr);
		
//		PrintWriter - 출력보조스트림
		
//		OutputStream 
//		OutputStreamWriter 
//		BufferedWriter - 이 세개의 클래스로 문자열 입력을 받은 다음 바이너리데이터로 외부로 보낼 수 있다.
//		하지만 이 세개의 클래스를 한번에 제공해주는 HttpServletResponse의 강력한 함수가있다.
		resp.setContentType("text/html;charset=utf-8");
//		resp.getCharacterEncoding();
		PrintWriter pw = resp.getWriter(); // getWRiter()가 출력스트림역할을 해준다.
		pw.print("<html>");
		pw.print("<head><title></title></head>");
		pw.print("<body>");
		pw.print("<h3>회원가입이 완료됬습니다.!" + name +"</h3>");
		pw.print("</body>");
		pw.print("<html>");
		
		pw.close();
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doGet(req,resp); // 코드 중복을 막기 위해 post 요청이 들어올때 doGet()을 호출해준다.
	}

}

 

이처럼 서블릿은 자바 소스코드 속에 HTML 코드가 들어가는 형태로 되어있다.

 

 

 

 

 

 

하지만 JSP는

 

<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
//
request, response 사용 가능
	MemberRepository memberRepository = MemberRepository.getInstance();

	System.out.println("save.jsp");
	String username = request.getParameter("username");
	int age = Integer.parseInt(request.getParameter("age"));
	

	Member member = new Member(username, age);
	System.out.println("member = " + member);
	memberRepository.save(member);




%>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
성공
<ul>
<li>id=<%=member.getId()%></li>
<li>username=<%=member.getUsername()%></li>
<li>age=<%=member.getAge()%></li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>

 

 

위처럼 HTML 부분과 자바 코드가 분리되어 있다.

 

따라서 자바 코드로 HTML을 만들어 내는 것 보다. 차라리 HTML 문서에 동적으로 변경해야 하는 부분만 자바 코드에 넣음으로서 편리해졌다.

 


서블릿과 JSP의 한계

 

하지만 서블릿과 JSP에도 명백한 한계점이 존재한다.

 

기존에 서블릿으로 개발할 때에는 HTML을 만드는 작업이 자바 코드에 섞여서 지저분하고 복잡했다.

 

따라서 JSP을 사용하게 되었고, 덕분에 뷰를 생성하는 HTML 작업을 깔끔하게 가져가고, 동적이 필요한 부분에만

자바 코드를 적용했다.

 

 

하지만..!

 

JSP를 보면 자바 코드 부분은 비즈니스 역할을 수행하기 위한 로직이고, 나머지 HTML은 뷰의 역할을 한다.

 

즉 비즈니스 역할을 위한, 데이터를 조회하고, 서비스 로직을 수행하는 등 다양한 부분의 코드와 뷰를 위한 코드가 모두 같은 JSP 에 노출되어있다.

 

비즈니스의 수정이 필요할 때나, 뷰적인 부분의 수정이 필요할 때 모두 같은 JSP를 가져와서 파야할 것이다.

여러가지 역할을 하기 때문에 분량은 방대하고 복잡하니 유지보수에 매우 어려움을 겪을 것이다.

 

이는 하나의 JSP가 너무 많은 역할을 하고 있다는 단점을 보여줬다.

 


MVC 패턴의 등장

 

MVC란 뭘까?

 

MVC는 Model. View, Controller의 약자이다. 

 

각각은 하나의 애플리케이션을 구성할 때의 구성요소를 세가지로 구분한 패턴이다.

 

 

 

 

모델(Model)


모델은 뷰에 출력할 데이터를 담는다.

 

모델은 뷰를 만들 때 필요한 데이터를 모두 가지고 있다.

 

따라서 뷰는 모델 덕분에 필요한 데이터를 데이터베이스에서 가져오는 등 다른 비즈니스 로직을 수행할 필요가 없으며 뷰만의 일을 할 수 있다.

 

참고로 모델은 HttpServletRequest 객체로 사용할 수 있다.

 

request 객체 안에는 데이터 저장소가 있는데,

 

request.setAttribute()를 사용하여 데이터를 보관할 수 있으며

 

request.getAttribute()를 통하여 데이터를 조회할 수 있다.

 

이 정보를 듣고 왜 response 가 아닌 request에 데이터를 보관할까 궁금증이 생겼다.

 

request 객체는 요청의 처리부터 끝날 때까지 사용자의 파라미터 + 내부에서 사용하는 파라미터를 저장하는 역할을 수행하고

 

response 객체는 응답을 만들어서 생성하는데 사용된다.

애초에 객체의 용도가 정해져 있다고 한다.

 

 

 

 

뷰(View)

 

모델에 담겨있는 데이터를 사용하여 화면을 그린다.

 

ex)

1. 회원 저장 요청이 간다.

2. 서블릿 컨테이너는 회원 저장 컨테이너를 호출한다.

3.  서비스 로직 실행 (비즈니스 로직)

4. 맴버 객체 생성

5. 모델에 맴버 보관

6. viewpath나 dispathcer.forward()를 통해 다른 서블릿이나 뷰로 이동

 

그러면 회원 저장 뷰는 컨트롤러가 보내준 모델의 정보로 뷰를 그린다.

<%= request.getAttribute("member")%>

JSP의 경우 username=${member.username} 처럼 ${} 문법으로 모델에 담긴 데이터를 조회할 수 있다.

 

 

 

컨트롤러(Controller)

HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 수행한다

그리고 뷰에 전달할 데이터를 조회해서 모델에 담아준다.

MVC의 흐름

컨트롤러에 비즈니스 로직을 둘 수도 있지만, 그렇게 하면 컨트롤러가 너무 많은 역할을 하게 된다.

모델에 데이터를 담고, 뷰를 호출해주는 것만이 아니라 복잡하고 중요한 비즈니스 로직까지 담겨 있으면 매우 유지보수가 힘들것이다.

 

그래서 일반적으로 비즈니스 로직은 서비스(Service) 라는 계층을 별도로 만들어서 처리한다.

 

컨트롤러는 비즈니스 로직이 있는 서비스를 호출하는 역할을 담당한다.

 

 


MVC 패턴의 한계

 

MVC 패턴을 적용한 덕분에 컨트롤러의 역할과 뷰를 렌더링 하는 역할을 명확하게 구분할 수 있게 되었다.

 

특히 뷰는 화면을 그리는 역할에 충실한 덕분에, 코드가 깔끔하고 직관적이다. 단순히 모델에서 데이터를 꺼내 화면을 만들면 된다

 

하지만

 

컨트롤러에는 중복이 많으며 불필요한 코드가 존재한다.

 

컨트롤러는 다음과 같은 역할을 수행한다.

1. 비즈니스 로직 수행

2. 모델에 데이터 담기

3. 뷰 호출

 

 

하지만 위의 역할에서 문제가 발생한다.

 

1. 컨트롤러마다 수행하는 비즈니스 로직이 다르므로, HttpServlerResponse, HttpServletRequest가 필요할 때도 있고, 필요 없을 경우도 있다.

굳이 필요 없는 객체를 만들 수고는 필요가 없다.

 

2. 뷰를 호출할 때 중복되는 코드가 많다.

뷰에 모델을 담고, viewPath를 호출하는 과정이 모든 컨트롤러에 똑같이 쓰여져야 한다.

또한 ViewPath도 중복되는 경우가 많을 것이다.

그리고 다른 뷰로 변경하면 코드가 변경될 것이다

만약 jsp 에서 thymeleaf로 변경하면 viewPath가 달라질 것이고, 디테일적인 수정이 필요할 것이다.

 

 

정리하면 공통 처리가 어렵다는 문제가 발생한다.

기능이 복잡해질 수록 컨트롤러에서 공통으로 처리해야 하는 부분이 많아질 것이다.

공통 메서드로 뽑을 수도 있겠지만, 결국 메서드를 호출하는 것 자체가 또 중복될 것이며 

실수로 호출하지 않는 경우도 발생할 것이다.

 

 

이 문제를 해결하려면 

컨트롤러를 호출하기 전에 미리 공통 기능을 처리해주면 된다!

프론트 컨트롤러(Front Controller) 패턴을 도입함으로서 이런 문제를 해결할 수 있다.

 

반응형