Day67 실습프로젝트(JSP)
JSP(JavaServer Pages)
- JSP(JavaServer Pages)는 동적인 웹 페이지를 생성하기 위해 사용되는 서버 측 기술이다.
- JSP는 자바 코드를 HTML에 삽입하여 웹 서버에서 실행되는 방식으로 작동하며, 최종적으로 클라이언트에게 HTML 형식으로 응답을 보낸다.
- JSP는 Java Servlet의 확장으로 볼 수 있으며, 웹 애플리케이션 개발에서 자주 사용된다.
JSP의 특징
- 동적 웹 페이지 생성: JSP는 자바 코드를 통해 동적으로 웹 페이지의 내용을 생성할 수 있다.
- 플랫폼 독립성: JSP는 자바 기반이기 때문에 다양한 운영체제에서 동일하게 동작한다.
- 서버 측 실행: JSP 페이지는 서버에서 실행되며, 클라이언트는 최종적으로 HTML 문서를 받게 된다.
- 태그 기반 구조: JSP는 HTML 태그와 자바 코드를 혼합하여 사용할 수 있으며, JSP 표준 태그 라이브러리(JSTL)를 지원한다.
JSP 페이지의 실행 과정
- 클라이언트 요청: 클라이언트가 웹 페이지를 요청한다.
- JSP 페이지 처리: 웹 서버는 요청된 JSP 페이지를 서블릿으로 변환하고, 컴파일한 후 실행한다.
- class파일 생성 : 컴파일 된 class파일을 생성하고 실행한다.
응답 생성: JSP 페이지의 실행 결과로 HTML을 생성하여 클라이언트에게 응답한다.
Servlet과 JSP(MVC1 아키텍쳐)
Servlet
- Java 코드 안에 HTML 코드를 포함하는 형태로 작성된다.
- HTML과 Java 코드가 섞여 있어 코드가 복잡해질 수 있으며, 유지보수가 어렵다.
- 비즈니스 로직과 프레젠테이션 로직이 뒤섞이는 경우가 많아 코드 관리가 힘들 수 있다.
- 예를 들어,
out.println("<html><body>Hello World</body></html>");
와 같은 방식으로 HTML을 생성해야 한다.
JSP
- HTML 코드 안에 Java 코드를 삽입하는 형태로 작성된다.
- HTML 코드와 Java 코드를 분리하여 사용할 수 있어 유지보수가 용이하다.
- 템플릿을 사용하는 것처럼 웹 페이지를 설계할 수 있어 개발 생산성이 높다.
- 예를 들어,
<% out.println("Hello World"); %>
와 같이 HTML 내에 Java 코드를 쉽게 삽입할 수 있다.
MVC 아키텍쳐
- MVC1: 모델, 뷰, 컨트롤러의 역할이 명확히 구분되지 않았던 초기 방식으로, 종종 비즈니스 로직과 프레젠테이션 로직이 혼합되어 유지보수가 어렵다.
- MVC2: 모델, 뷰, 컨트롤러가 명확히 분리된 구조로, 유지보수가 용이하고 애플리케이션의 확장성을 높인다. 이 구조에서는 모든 요청이 컨트롤러를 통해 처리되며, 컨트롤러가 모델과 뷰를 연결한다.
MVC2 아키텍처
- MVC2(Model-View-Controller 2) 아키텍처는 웹 애플리케이션 개발에서 사용되는 디자인 패턴으로, 애플리케이션의 로직과 사용자 인터페이스를 명확히 분리하는 데 목적이 있다.
- MVC2는 특히 Java 기반 웹 애플리케이션에서 널리 사용되며, 웹 애플리케이션의 구조를 체계화하고 유지보수를 쉽게 할 수 있도록 돕는다.
MVC2 아키텍처 역할
1. 모델 (Model)
- 애플리케이션의 데이터와 비즈니스 로직을 관리한다.
- 데이터베이스나 외부 서비스와의 상호작용을 처리하며, 데이터의 상태를 유지한다.
- 비즈니스 로직을 포함하여 데이터의 처리 및 검증을 담당한다.
- 일반적으로 DAO(Data Access Object) 패턴과 함께 사용된다.
2. 뷰 (View)
- 사용자에게 데이터를 표시하는 역할을 한다.
- 모델로부터 데이터를 받아서 사용자에게 보여주는 역할을 한다.
- JSP, HTML, Thymeleaf, 또는 다른 템플릿 엔진을 사용하여 구현된다.
- 뷰는 로직 없이 단순히 데이터를 표시하는 데 집중한다.
3. 컨트롤러 (Controller)
- 사용자 입력을 처리하고, 모델과 뷰를 연결하는 역할을 한다.
- 컨트롤러는 사용자의 요청을 받고 이를 처리한 뒤, 적절한 모델을 호출하고 데이터를 뷰에 전달한다.
- 컨트롤러는 애플리케이션의 흐름을 제어하며, 요청에 따라 어떤 뷰를 보여줄지 결정한다.
MVC2의 장점
- 유지보수 용이: 각 컴포넌트가 명확히 분리되어 있어 코드의 유지보수가 쉽다.
- 확장성: 기능 확장이 용이하며, 코드 재사용성이 높다.
- 협업에 적합: 역할이 분리되어 있어 팀 내에서 개발 작업을 분담하기에 적합하다.
- 테스트 용이: 각 컴포넌트를 독립적으로 테스트할 수 있어, 유닛 테스트와 통합 테스트가 용이하다.
MVC2의 단점
- 복잡성: 각 컴포넌트가 분리되어 있기 때문에, 소규모 프로젝트에서는 오히려 복잡성을 증가시킬 수 있다.
- 초기 설정 비용: 프레임워크를 설정하거나 아키텍처를 구성하는 데 시간이 더 많이 소요될 수 있다.
MVC2의 대표적인 프레임워크
- Spring MVC: Java 기반의 대표적인 MVC2 프레임워크로, Spring Framework의 일부이다.
- Struts: Apache에서 제공하는 Java 기반 웹 애플리케이션 프레임워크로, MVC2 패턴을 구현하고 있다.
- Ruby on Rails: Ruby 언어로 작성된 프레임워크로, MVC2 아키텍처를 따른다.
템플릿 엔진 (Template Engine)
- 템플릿 엔진은 웹 애플리케이션에서 HTML, XML 같은 마크업 언어를 사용하여 동적인 페이지를 생성할 수 있도록 돕는 도구이다.
템플릿 엔진은 템플릿 파일에서 미리 정의된 태그나 문법을 사용하여 데이터를 삽입하고, 최종적으로 사용자에게 제공할 완성된 문서를 생성한다.
주요 특징
- 데이터 바인딩: 템플릿 엔진은 서버 측 또는 클라이언트 측 데이터와 템플릿을 결합하여 최종 문서를 생성한다.
- 논리적 제어: 템플릿 내에서 조건문, 반복문 등을 사용하여 동적인 콘텐츠를 생성할 수 있다.
- 유지보수성: 코드와 프레젠테이션(HTML)을 분리하여 유지보수가 용이하다.
장점
- 코드 재사용성: 반복되는 HTML 구조를 템플릿으로 정의하여 재사용할 수 있다.
- 읽기 쉬운 코드: 템플릿 엔진은 HTML 파일과 유사한 구조를 유지하므로, 코드 가독성이 높다.
- 빠른 개발 속도: 동적인 페이지 생성을 쉽게 해주어 개발 속도를 향상시킨다.
단점
- 러닝 커브: 템플릿 엔진마다 문법이 다르기 때문에, 새로운 템플릿 엔진을 배우는 데 시간이 필요할 수 있다.
- 추가적인 복잡성: 템플릿 엔진을 사용하면서 서버나 클라이언트 측에 추가적인 처리 로직이 필요할 수 있다.
MVC 적용하기
Myapp에서 JSP 작동원리
JSP태그의 종류
- 디랙티브 태그 :
- JSP 페이지의 전역 설정과 동작을 정의하는 데 사용한다.
- 디렉티브는 JSP 컨테이너에 페이지가 어떻게 처리될지를 지시하는 역할을 하며, 페이지의 컴파일 방식, 코드 구조 등을 제어한다.
- 액션 태그 :
- 객체와 상호 작용하거나, 다른 JSP 페이지나 서블릿을 포함 및 포워딩하는 데 사용한다.
- 주로 JSP페이지의 동적인 처리를 위해 사용한다.
<%@ page %> 디렉티브 태그
- @page : <%@ page %> 디렉티브는 JSP 페이지의 전역 설정을 정의한다.
- language : JSP페이지에서 사용할 프로그래밍 언어를 말하며 기본값은 java이다.
- contentType : response로 생성하는 HTML의 MIME타입과 글자 인코딩을 지정한다.
- pageEncoding : JSP페이지 자체의 문자 인코딩 방식을 지정한다.
trimDirectiveWhitespaces : SP 페이지에서 디렉티브 태그(<%@ … %>) 앞뒤의 공백 문자를 제거할지 여부를 설정한다.
1 2 3 4 5
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
액션태그
요청 시점에 포함된 페이지를 실행하고 그 결과를 포함하여 출력한다.
1
<jsp:include page="/header.jsp"/>
index.jsp 만들기
page디랙티브 태그와 include 액션태그를 사용하여 index.jsp를 생성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <jsp:include page="/header.jsp"/> <h1>환영합니다! - JSP</h1> <p>이 사이트는 팀 프로젝트를 관리하는 서비스를 제공합니다.</p> </body> </html>
List.jsp만들기
java
- Servlet에서는 list를 생성하고 request에 저장한다.
res.setContentType("text/html;charset=UTF-8")
: 생성 시 MIME타입과 문자 인코딩에 대한 설정을 한다.req.getRequestDispatcher("/user/list.jsp").include(req, res)
: jsp에 req와 res를 전달하고 생성된 html를 res에 반환한다.반환된 Res를 톰캣 서버에 보내서 웹 브라이저에 출력한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
@WebServlet("/board/list") public class BoardListServlet extends GenericServlet { private BoardDao boardDao; @Override public void init() throws ServletException { boardDao = (BoardDao) this.getServletContext().getAttribute("boardDao"); } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { try { List<Board> list = boardDao.list(); req.setAttribute("list", list); res.setContentType("text/html;charset=UTF-8"); req.getRequestDispatcher("/board/list.jsp").include(req, res); } catch (Exception e) { req.setAttribute("exception", e); req.getRequestDispatcher("/error.jsp").forward(req, res); } } }
jsp
<%@ page import="패키지명.클래스명"%>
: Import 할 객체를 설정하는 디랙티브 태그이다.<% %>
: 스크립틀릿(Scriptlet)로 자바 코드를 삽입할 때, 사용한다.<%= %>
: 표현식(Expression)으로 자바 코드를 실행하여 해당 코드의 결과값을 리턴한다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <%@ page import="bitcamp.myapp.vo.Board"%> <%@ page import="java.util.List"%> <jsp:include page="/header.jsp"/> <h1>게시글 목록</h1> <p><a href='/board/form'>새 글</a></p> <table> <thead> <tr><th>번호</th><th>제목</th><th>작성자</th><th>작성일</th><th>조회수</th></tr> </thead> <tbody> <% List<Board> list = (List<Board>) request.getAttribute("list"); for (Board board : list) { %> <tr> <td><%=board.getNo()%></td> <td><a href='/board/view?no=<%=board.getNo()%>'><%=board.getTitle()%></a></td> <td><%=board.getWriter().getName()%></td> <td><%=String.format("%tY-%1$tm-%1$td", board.getCreatedDate())%></td> <td><%=board.getViewCount()%></td> </tr> <% } %> </tbody> </table> </body> </html>
view 만들기
Servlet.java
- findBy(int no)를 통해 객체를 호출한다.
- 호출한 객체를 ServletRequest.setAttribute(“Object”, Obj)로 담는다.
ServletRequest.getRequestDispatcher(“URL”).include(ServletRequest, ServletResponse)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
@WebServlet("/project/view") public class ProjectViewServlet extends GenericServlet { private ProjectDao projectDao; private UserDao userDao; @Override public void init() throws ServletException { projectDao = (ProjectDao) this.getServletContext().getAttribute("projectDao"); userDao = (UserDao) this.getServletContext().getAttribute("userDao"); } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { try { int projectNo = Integer.parseInt(req.getParameter("no")); Project project = projectDao.findBy(projectNo); req.setAttribute("project", project); List<User> users = userDao.list(); req.setAttribute("users", users); res.setContentType("text/html;charset=UTF-8"); req.getRequestDispatcher("/project/view.jsp").include(req, res); } catch (Exception e) { req.setAttribute("exception", e); req.getRequestDispatcher("/error.jsp").forward(req, res); } } }
view.jsp
<%! method declare %>
: java의 메서드를 선언 하는 태그이며, 호출시<%= %>
으로 동일하게 호출이 가능하다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <%@ page import="bitcamp.myapp.vo.Project"%> <%@ page import="bitcamp.myapp.vo.User"%> <%@ page import="java.util.List"%> <%! private boolean isMember(List<User> members, User user) { for (User member : members) { if (member.getNo() == user.getNo()) { return true; } } return false; } %> <jsp:include page="/header.jsp"/> <h1>프로젝트 조회</h1> <% Project project = (Project) request.getAttribute("project"); if (project == null) { %> <p>없는 프로젝트입니다.</p> <% } else { List<User> users = (List<User>) request.getAttribute("users"); %> <form action='/project/update'> 번호: <input readonly name='no' type='text' value='<%=project.getNo()%>'><br> 프로젝트명: <input name='title' type='text' value='<%=project.getTitle()%>'><br> 설명: <textarea name='description'><%=project.getDescription()%></textarea><br> 기간: <input name='startDate' type='date' value='<%=project.getStartDate()%>'> ~ <input name='endDate' type='date' value='<%=project.getEndDate()%>'><br> 팀원:<br> <ul> <% for (User user : users) { %> <li><input <%=isMember(project.getMembers(), user) ? "checked" : ""%> name='member' value='<%=user.getNo()%>' type='checkbox'> <%=user.getName()%></li> <% } %> </ul> <button>변경</button> <button type='button' onclick='location.href="/project/delete?no=<%=project.getNo()%>"'>삭제</button> </form> <% } %> </body> </html>
Form 만들기
Servlet.java
마찬가지로 Jsp로 넘길 객체를 ServletResquest에 담는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
@WebServlet("/project/form") public class ProjectFormServlet extends GenericServlet { private UserDao userDao; @Override public void init() throws ServletException { userDao = (UserDao) this.getServletContext().getAttribute("userDao"); } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { try { List<User> users = userDao.list(); req.setAttribute("users", users); res.setContentType("text/html;charset=UTF-8"); req.getRequestDispatcher("/project/form.jsp").include(req, res); } catch (Exception e) { req.setAttribute("exception", e); req.getRequestDispatcher("/error.jsp").forward(req, res); } } }
Form.jsp
- Servlet의 include에 반환 할 html을 작성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <%@ page import="bitcamp.myapp.vo.User"%> <%@ page import="java.util.List"%> <jsp:include page="/header.jsp"/> <h1>프로젝트 등록</h1> <form action='/project/add'> 프로젝트명: <input name='title' type='text'><br> 설명: <textarea name='description'></textarea><br> 기간: <input name='startDate' type='date'> ~ <input name='endDate' type='date'><br> 팀원:<br> <ul> <% List<User> users = (List<User>) request.getAttribute("users"); for (User user : users) { %> <li><input name='member' value='<%=user.getNo()%>' type='checkbox'> <%=user.getName()%></li> <% } %> </ul> <input type='submit' value='등록'> </form> </body> </html>
add 만들기
Servlet.java
- 톰캣에서 addServlet을 실행하면 별도의 JSP로 보내는 것이 아니라 servlet에서 수행한다.
((HttpServletResponse) res).sendRedirect("/project/list")
등록이 완료되면 버퍼를 초기화하고 url로 이동한다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
@WebServlet("/project/add") public class ProjectAddServlet extends GenericServlet { private ProjectDao projectDao; private SqlSessionFactory sqlSessionFactory; @Override public void init() throws ServletException { ServletContext ctx = this.getServletContext(); this.projectDao = (ProjectDao) ctx.getAttribute("projectDao"); this.sqlSessionFactory = (SqlSessionFactory) ctx.getAttribute("sqlSessionFactory"); } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { try { Project project = new Project(); project.setTitle(req.getParameter("title")); project.setDescription(req.getParameter("description")); project.setStartDate(Date.valueOf(req.getParameter("startDate"))); project.setEndDate(Date.valueOf(req.getParameter("endDate"))); String[] memberNos = req.getParameterValues("member"); if (memberNos != null) { ArrayList<User> members = new ArrayList<>(); for (String memberNo : memberNos) { members.add(new User(Integer.parseInt(memberNo))); } project.setMembers(members); } projectDao.insert(project); if (project.getMembers() != null && project.getMembers().size() > 0) { projectDao.insertMembers(project.getNo(), project.getMembers()); } sqlSessionFactory.openSession(false).commit(); ((HttpServletResponse) res).sendRedirect("/project/list"); } catch (Exception e) { sqlSessionFactory.openSession(false).rollback(); req.setAttribute("exception", e); req.getRequestDispatcher("/error.jsp").forward(req, res); } } }
update 만들기
Servlet.java
- 톰캣에서 addServlet을 실행하면 별도의 JSP로 보내는 것이 아니라 servlet에서 수행한다.
- add와 동일한 구조를 갖는다.
delete 만들기
Servlet.java
- 톰캣에서 addServlet을 실행하면 별도의 JSP로 보내는 것이 아니라 servlet에서 수행한다.
- add와 동일한 구조를 갖는다.
Login 만들기
- login로직도 동일하게 작성한다.
error.jsp 만들기
- atch문에서 ServletRequest.getRequestDispatcher(“url”).foward(req,res)로 전달하고 servlet은 종료된다.
- 각 클래스에서 에러가 발생하면 에러를 웹에 표시할 jsp를 만든다.
호출된 Jsp는 컨테이너에게 출력문을 전달하고 컨테이너는 서버에 출력한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <%@ page import="java.io.PrintWriter"%> <jsp:include page="/header.jsp"/> <h1>실행 오류!</h1> <pre> <% Exception e = (Exception) request.getAttribute("exception"); if (e != null) { e.printStackTrace(new PrintWriter(out)); } %> </pre> </body> </html>
부록. ServletContext, HttpSession, ServletRequest의 특징과 생명주기
1. ServletContext
특징
- 애플리케이션 범위:
ServletContext
는 웹 애플리케이션의 전체 범위에서 공유되는 객체로, 웹 애플리케이션 내의 모든 서블릿과 JSP에서 접근 가능하다. - 애플리케이션 설정 정보:
web.xml
에 설정된 초기화 파라미터(context-param
)를 통해 전역 설정 정보를 제공한다. - 리소스 관리: 애플리케이션 내에서 파일, 이미지 등 리소스에 접근할 수 있으며, 애플리케이션 수준의 리소스를 관리한다.
- 정보 공유: 애플리케이션 전체에서 공유할 수 있는 데이터를 저장할 수 있어, 여러 서블릿 간에 정보를 공유할 수 있다.
생명주기
- 생성: 웹 애플리케이션이 시작될 때, 즉 서버가 애플리케이션을 로드할 때
ServletContext
객체가 생성된다. - 종료: 웹 애플리케이션이 종료될 때, 즉 서버가 애플리케이션을 언로드하거나 서버를 중지할 때
ServletContext
객체가 소멸된다.
2. HttpSession
특징
- 사용자 범위:
HttpSession
은 각 클라이언트(사용자)마다 고유하게 생성되며, 클라이언트별로 상태를 유지하기 위한 객체이다. - 세션 데이터 저장: 로그인 정보, 사용자 데이터 등 세션 범위에서 유지해야 하는 데이터를 저장할 수 있다.
- 쿠키와 세션 ID: 세션은 보통 쿠키에 저장된 세션 ID를 통해 관리된다. 세션 ID는 클라이언트의 요청과 연관된
HttpSession
객체를 식별한다.
생명주기
- 생성:
HttpSession
객체는 사용자가 애플리케이션에 처음 접근할 때 생성되거나, 서버 측에서 명시적으로 세션을 생성할 때 생성된다 (request.getSession()
호출 시). - 만료: 세션은 일정 기간 동안 사용되지 않으면 만료된다. 이 기간은 서버 설정에 따라 결정되며,
session.invalidate()
를 호출하여 명시적으로 세션을 만료시킬 수도 있다. - 종료: 서버가 종료되거나 애플리케이션이 언로드될 때, 모든 세션 객체는 무효화된다.
3. ServletRequest
특징
- 요청 범위:
ServletRequest
는 HTTP 요청에 대한 정보를 제공하며, 각 요청마다 새롭게 생성된다. - 클라이언트 정보 제공: 클라이언트의 IP 주소, 요청된 URL, 요청 파라미터 등 클라이언트와 관련된 다양한 정보를 제공한다.
- 속성 관리: 서블릿 간에 데이터를 전달하기 위해 요청 범위의 속성을 설정하고 공유할 수 있다.
- 입출력 스트림 관리: 요청 본문을 읽기 위한 입력 스트림과 응답 본문을 작성하기 위한 출력 스트림을 제공한다.
생명주기
- 생성:
ServletRequest
객체는 클라이언트가 서버에 요청을 보낼 때마다 생성된다. - 종료: 요청 처리가 완료되면, 즉 응답이 클라이언트에게 전송되면
ServletRequest
객체는 소멸된다. 따라서ServletRequest
의 생명주기는 매우 짧고, 요청-응답 사이클 동안에만 유효하다.
관계도
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.