[스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] 03. 서블릿, JSP, MVC 패턴

Turtle·2024년 6월 26일
0
post-thumbnail

🙄회원 관리 웹 애플리케이션 요구사항

✔️회원 정보

  • 이름 : username
  • 나이 : age

✔️기능 요구사항

  • 회원 저장
  • 회원 목록 조회

✔️회원 도메인

@Getter
@Setter
public class Member {
	private Long id;
	private String username;
	private int age;

	protected Member() {

	}

	public Member(String username, int age) {
		this.username = username;
		this.age = age;
	}
}

✔️회원 리포지토리

public class MemberRepository {
	private static Map<Long, Member> store= new HashMap<>();
	private static long sequence = 0L;

	public static final MemberRepository instance = new MemberRepository();

	private MemberRepository() {

	}

	public static MemberRepository getInstance() {
		return instance;
	}

	public Member save(Member member) {
		member.setId(++sequence);
		store.put(member.getId(), member);
		return member;
	}

	public Member findById(long id) {
		Member member = store.get(id);
		return member;
	}

	public List<Member> findAll() {
		return new ArrayList<>(store.values());
	}

	public void clearStore() {
		store.clear();
	}
}

✔️회원 리포지토리 테스트 코드

class MemberRepositoryTest {

	private final MemberRepository memberRepository = MemberRepository.getInstance();

	// 테스트가 실행되고 난 후
	@AfterEach
	void afterEach() {
		memberRepository.clearStore();
	}

	@Test
	void save() {
		// given
		Member member = new Member("김아무개", 20);

		// when
		Member savedMember = memberRepository.save(member);

		// then
		Member findMember = memberRepository.findById(savedMember.getId());
		Assertions.assertThat(findMember).isEqualTo(savedMember);
	}

	@Test
	void findAll() {
		// given
		Member member1 = new Member("김아무개", 20);
		Member member2 = new Member("박아무개", 20);

		memberRepository.save(member1);
		memberRepository.save(member2);

		// when
		List<Member> memberList = memberRepository.findAll();

		// then
		Assertions.assertThat(memberList.size()).isEqualTo(2);
	}
}

🙄서블릿으로 회원 관리 웹 애플리케이션 만들기

✔️서블릿으로 작성한 회원 관리 웹 애플리케이션 요청 코드

@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {
	private final MemberRepository memberRepository = MemberRepository.getInstance();

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("text/html");
		resp.setCharacterEncoding("UTF-8");

		PrintWriter w = resp.getWriter();
		w.write("<!DOCTYPE html>\n" +
				"<html>\n" +
				"<head>\n" +
				" <meta charset=\"UTF-8\">\n" +
				" <title>Title</title>\n" +
				"</head>\n" +
				"<body>\n" +
				"<form action=\"/servlet/members/save\" method=\"post\">\n" +
				" username: <input type=\"text\" name=\"username\" />\n" +
				" age: <input type=\"text\" name=\"age\" />\n" +
				" <button type=\"submit\">전송</button>\n" +
				"</form>\n" +
				"</body>\n" +
				"</html>\n");
	}
}

✔️서블릿으로 작성한 회원 관리 웹 애플리케이션 응답 코드

@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {
	private final MemberRepository memberRepository = MemberRepository.getInstance();

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("text/html");
		resp.setCharacterEncoding("UTF-8");

		String username = req.getParameter("username");
		int age = Integer.parseInt(req.getParameter("age"));

		Member member = new Member(username, age);

		memberRepository.save(member);

		PrintWriter w = resp.getWriter();
		w.write("<html>\n" +
				"<head>\n" +
				" <meta charset=\"UTF-8\">\n" +
				"</head>\n" +
				"<body>\n" +
				"성공\n" +
				"<ul>\n" +
				" <li>id="+member.getId()+"</li>\n" +
				" <li>username="+member.getUsername()+"</li>\n" +
				" <li>age="+member.getAge()+"</li>\n" +
				"</ul>\n" +
				"<a href=\"/index.html\">메인</a>\n" +
				"</body>\n" +
				"</html>");

	}
}

🙄JSP로 회원 관리 웹 애플리케이션 만들기

✔️JSP 문서

  1. JSP의 경우 JSP문서라는 점을 알려주기 위해 아래와 같은 코드를 첫 줄에 사용한다.
  2. 자바 코드를 그대로 사용할 수 있다.(import 사용 가능)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.Member" %>
<%@ page import="hello.servlet.repository.MemberRepository" %>

✔️서블릿과 JSP의 한계

  1. 서블릿으로 개발할 때는 자바 코드 내에 HTML을 작성하는 부분이 있었는데 길어지면 길어질수록 매우 비효율적이다.
  2. JSP가 이를 해결한 것처럼 보이지만 자바 코드, 데이터를 조회하는 리포지토리 부분 등의 다양한 코드가 JSP에 다수 노출되어있다.
  3. 비즈니스 로직은 서블릿처럼 다른 곳에서 처리하고 JSP는 목적에 맞게 HTML로 화면을 보여주는 일에만 집중하도록 하기 위해서 MVC패턴이 도입되었다.

🙄MVC패턴

컨트롤러(Controller) : HTTP 요청을 받아서 비즈니스 로직을 실행(요청 URL 매핑) + 컨트롤러의 파라미터로 모델을 넣고 이 모델에 뷰에 전달할 결과를 담는다.
모델(Movel) : 뷰에 출력할 데이터를 담는다.
뷰(View) : 모델에 담겨있는 데이터를 사용해서 화면에 나타내는 일에 집중한다.

🙄MVC패턴 적용 → 서블릿을 컨트롤러로 사용하고 JSP를 뷰로 사용

✔️서블릿에서 사용하는 request 내부에 데이터 저장소가 존재하여 이를 모델 대용으로 사용

  1. /WEB-INF : 이 경로 안에 JSP가 있으면 외부에서 JSP를 호출할 수 없다. 컨트롤러를 통해서 JSP가 호출되는 것이 목표
  2. redirect vs forward : 리다이렉트는 실제 클라이언트에 응답이 나갔다가 클라이언트가 redirect 경로로 다시 요청한다. 클라이언트가 인지할 수 있으며 URL 경로도 실제로 변경되지만 포워드의 경우 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 인지할 수 없다.
@WebServlet(name = "mvcMemberFormServlet", urlPatterns = "/servlet-mvc/members/new-form")
public class MvcMemberFormServlet extends HttpServlet {
	private final MemberRepository memberRepository = MemberRepository.getInstance();

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String viewPath = "/WEB-INF/views/new-form.jsp";					// 회원 등록 폼 절대 경로
		RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);	// 컨트롤러에서 뷰로 이동
		dispatcher.forward(req, resp);										// 서블릿에서 JSP 호출
	}
}
@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mvc/members/list")
public class MvcMemberListServlet extends HttpServlet {

	private final MemberRepository memberRepository = MemberRepository.getInstance();

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String viewPath = "/WEB-INF/views/members.jsp";
		List<Member> members = memberRepository.findAll();
		
		// 모델에 데이터 보관
		req.setAttribute("members", members);

		RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
		dispatcher.forward(req, resp);
	}
}
@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {

	private final MemberRepository memberRepository = MemberRepository.getInstance();

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String viewPath = "/WEB-INF/views/save-result.jsp";					// 회원 등록 폼 절대 경로
		String username = req.getParameter("username");
		int age = Integer.parseInt(req.getParameter("age"));

		Member member = new Member(username, age);
		memberRepository.save(member);

		// 모델에 데이터 보관
		req.setAttribute("member", member);
		RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
		dispatcher.forward(req, resp);
	}
}

✔️MVC패턴 - 한계

  1. View로 이동하는 코드가 항상 중복 호출 → forward 반복
  2. viewPath 중복
  3. request, response가 항상 사용되지않는다는 점

✔️해결

  1. 공통 처리가 필요 → 프론트 컨트롤러(Front Controller) 패턴 도입

0개의 댓글