WEB-INF폴더
WEB-INF는 Web Information의 약자로 web에 관련된 정보를 의미한다.
Web 정보에 관한 중요한 파일들은 WEB-INF폴더에 넣게된다.
예를 들어web.xml, lib폴더, java파일(/WEB-INF/classes) 등이 있다.
이런 중요한 파일들이 있는 WEB-INF폴더에는 브라우저에서 직접 접근할 수 없다.
오직 서버내에서만 접근이 가능하다.(forwarding을 통해서만..)
만약 브라우저가 WEB-INF폴더에 있는 파일에 직접 접근하려고 하면 404에러를 발생시킨다.
WEB-INF 폴더의 장점과 단점
장점 | 브라우저의 직접 접근이 불가, 보안이 좋다 |
단점 | 브라우저의 직접 접근이 불가, View(jsp)의 경우 직접적으로 볼 수없어 Controller를 통해서만 이동이 가능. |
일반적으로 jsp 파일은 WEB-INF폴더에 넣고 브라우저의 직접적인 요청을 막는다.
css, img,jQuery 등의 정적 파일은
브라우저의 직접요청을 허용하기 위해서 WEB-INF폴더에 넣지 않는다.
모델1구조와 모델2 구조
모델 1 구조
- 모델 1 구조는 JSP를 이용한 단순한 모델
- 웹 브라우저의 요청이 곧바로 JSP에 전달된다.
- 웹 브라우저의 요청을 받은 JSP는 자바빈, Service,DAO 클래스를 이용해서 요청한 작업을
처리하고 그 결과를 클라이언트에 출력해준다.
모델 2 구조
- 모델 2 구조는 모델 1 구조와 달리 웹 브라우저의 요청을 하나의 서블릿이 받게 된다
- 서블릿은 웹 브라우저의 요청을 알맞게 처리한 후 그 결과를 보여줄 JSP 페이지로 포워딩한다.
이 때 JSP페이지는 WEB-INF폴더에 있기 때문에 직접접근이 안 된다. 서블릿에서 포워딩을 통해서만
접근가능하다. - 포워딩을 통해 요청 흐름을 받은 JSP 페이지는 결과화면을 클라이언트에 전송한다.
※그림에서 보듯 모델2 구조의 처리방식에서 일어나는 일들은 한번의 요청을 처리하는 과정입니다.
즉, 서블릿 ,로직클래스,jsp 등이 하나의 request를 공유합니다.
※JSP를 WEB-INF폴더에 넣는 이유
계층화 아키텍처를 적용해서 서블릿(+서비스,dao클래스 )가 먼저 실행된 후
jsp로 실행한 화면에는 각 jsp화면에 맞는 데이터가 포함되어 있을 것이다.
jsp로 직접 요청을 허용하면 사용자는 아무 데이터도 없는 껍데기 jsp화면을 보게된다.
MVC패턴과 모델2구조
MVC패턴
MVC(Model-View-Controller)패턴은 크게 모델,뷰,컨트롤러의 세 부분으로
각각의 요소는 다음과 같은 역할을 한다.
- 모델 : 비즈니스 영역의 상태 정보를 처리한다.
- 뷰 : 비즈니스 영역에 대한 프리젠테이션 뷰(사용자가 보게 될 결과화면)을 담당한다.
- 컨트롤러 : 사용자의 입력 및 흐름 제어를 담당한다.
MVC패턴과 JSP의 모델2구조는 완벽하게 일치한다.
- 컨트롤러=서블릿
- 모델=비즈니스 로직클래스,자바빈
- 뷰=JSP
너무헷갈려하지말자. 완벽히 똑같은 개념인데 MVC패턴은 일반적인 웹 발패턴이고
모델2는 이런 MVC패턴을 JSP에서적용하면서 JSP의 모델2라고 이름지은것 뿐이다.
MVC패턴 ⊃ JSP모델2.
앞으로는 JSP모델2라는말보단 MVC패턴이라는 말을 사용하도록 하겠다.
MVC패턴의 핵심
- 어플리케이션의 흐름제어나 사용자의 처리요청은 컨트롤러(Servlet)가 한다
- 비즈니스 로직을 처리하는 모델(Service,Dao,VO 자바클래스)과
화면을 보여주는 뷰(JSP)는 철저히 분리한다.
JSP로 구현한 MVC 동작과정
1. HTTP요정을 받음
- 웹 브라우저가 전송한 HTTP 요청을 받는다.
서블릿의 doGet() ,doPost(), service()메소드 등이 호출된다.
2. 클라이언트 요청을 분석
- 브라우저가 어떤 요청을 했는지 분석한다.
freeList인지, freeForm인지 아니면 memberList인지 알아낸다.
3.요청에 맞는 비즈니스 로직을 처리하는 모델 사용
- 모델(service, DAO) 클래스에 요청에 맞는 일을 수행하도록 한다.
4. 컨트롤러부터 요청을 받음
- 요청에 맞는 일이 모델에게 넘어감
5. 비즈니스 로직 수행+DB 접근
- 요청에 맞는 로직을 수행함
6. 수행 결과 컨트롤러에 리턴
- 로직을 수행하고 얻은 결과(FreeBoardVO, List<FreeBoardVO> 등)을 컨트롤러에 리턴한다.
7. 결과를 request 또는 session에 저장
- 모델(Service,DAO)로부터 받은 결과값을 request나 session 객체에 저장.
8. 알맞은 뷰로 포워딩 또는 리다이렉트
- 2번에서 분석한 요청 및 수행결과에 맞는 알맞은 뷰(jsp)로 포워딩하거나 리다이렉트 요청.
포워딩은 jsp로 포워딩하고 해당 jsp로 응답함.
리다이렉트는 jsp로 포워딩하지않고 바로 response에 redirect요청을 담고 바로 응답.
(서블릿의 service(),doGet(),doPost()메소드 등에서 포워딩하지않고
메소드가 끝나면 바로 브라우저로 응답하게 된다.)
여러가지 MVC 패턴
1. 하나의 요청을 하나의 서블릿이 처리하는 형태
A00SimpleFreeListController.java
package com.study.servlet;
public class A00SimpleFreeListController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//List에 필요한 코드 작성
req.setAttribute("data",data) ; // 요청에 data를 담아 jsp에서도 사용할 수 있게.
String viewPage = "/WEB-INF/views/free/freeList.jsp";
RequestDispatcher dispatcher = req.getRequestDispatcher(viewPage);
dispatcher.forward(req, resp);
}
}
A00SimpleFreeViewController.java
public class A00SimpleFreeViewController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//view에서 필요한 코드
String viewPage = "/WEB-INF/views/free/freeView.jsp";
RequestDispatcher dispatcher = req.getRequestDispatcher(viewPage); // 서블릿에서 foward해주는 객체
dispatcher.forward(req, resp);
}
}
서블릿을 만들었다면 어떤 요청이 왔을 때 이 서블릿이 실행되는지 설정을하자.
web.xml
<servlet>
<servlet-name>freeList</servlet-name>
<servlet-class>com.study.servlet.A00SimpleFreeListController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>freeList</servlet-name>
<url-pattern>/free/freeList.wow</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>freeView</servlet-name>
<servlet-class>com.study.servlet.A00SimpleFreeViewController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>freeView</servlet-name>
<url-pattern>/free/freeView.wow</url-pattern>
</servlet-mapping>
이렇게 만들 경우 서블릿 클래스가 요청종류( ≒페이지 갯수)에 따라 늘어난다.
이럴 경우 다음과 같은 문제가 있다.
- 뷰와 컨트롤러 역할을 분리한 건 좋지만 페이지가 늘어남에 따라 컨트롤러 내 중복 코드 다량 발생
- View로 이동하는 Forward 코드 중복
- View 주소 즉, ViewPath 설정하는 코드 중복
- 별도 응답을 보낼 필요가 없는 경우 서블릿 내 response 코드가 사용되지 않음
- HttpServletRequest, HttpServletResponse를 사용하는 테스트 코드 작성하기 쉽지 않음
- 정리하자면, 공통 처리가 어려운 것이 문제
이런 문제들을 해결하기 위해 FrontController라는 개념이 등장했다.
2. 모든 요청을 처리해주는 하나의 컨트롤러(하나의 서블릿)
이런식으로 처리를 하게될 것이다.
서블릿 내에서 요청에 따라 조건문으로 분기하고 그에 따른 코드를 실행할 것이다.
A01SimpleFreeBoardController.java
public class A01SimpleFreeBoardController extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri=req.getRequestURI();
String cp=req.getContextPath();
uri=uri.substring(cp.length());
String viewPage="";
if(uri.equals("/free/freeList.wow")) {
//List의 내용
viewPage="/WEB-INF/views/free/freeList.jsp;
} else if(uri.equals("/free/freeView.wow")) {
//view의 내용
viewPage="/WEB-INF/views/free/freeView.jsp";
}
..// 나머지 내용
RequestDispatcher dispatcher=req.getRequestDispatcher(viewPage); //서블릿에서 foward해주는 객체
dispatcher.forward(req, resp);
}
}
이러면 각 요청마다 서블릿을 만들필요는 없어진다.
하지만 요청의 종류가 많으면 조건문 분기가 많아져 코드가 길어지게 된다.
이를 해결하기 위해 FrontController에서는URI 분기만 처리하도록 하고
나머지 세부로직(model을 실행하고, 결과를 request에 담는 등)은
interface를 통해 처리하는 command패턴을 적용한다.
3. 요청을 분기시켜주는 서블릿과
세부로직을 실행시켜주는 interface (command패턴)
모든 요청을 직접 처리하지 않고 특정 클래스에 요청을 위임하는 패턴을 command패턴이라 한다.
여기서 모든요청을 직접 처리하지 않는 서블릿을 DispatcherServlet이라 하고
interface의 이름은 요청을 처리하는 클래스라는 뜻에서 Controller라 하겠다.
command패턴을 적용하면 동작 흐름은 다음과 같다.
DispatcherServlet .java
(어떤 요청이 오더라도 처음 처리하는 건 FrontController역할을 맡은
DispatcherServlet이 하기 때문에, urlPattern은 "/" 가 된다.)
@WebServlet("/") //web.xml에 servlet 등록 대신 @WebServlet을 이용해 등록할 수도 있다.
public class DispatcherServlet extends HttpServlet {
private Map<String, Controller> controllerMap=new HashMap<>();
@Override
public void init() throws ServletException {
controllerMap.put("/free/freeList.wow" , new FreeList());
controllerMap.put("/free/freeView.wow" , new FreeView());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri=req.getRequestURI();
Controller controller=controllerMap.get(uri);
if(controller==null){
resp.sendError(HttpServletResponse.SC_NOT_FOUND,"해당 URL을 처리할 컨트롤러가 존재하지 않습니다");
return;
}
String viewName = controller.process(req, resp);
String viewPage="/WEB-INF/views" + viewName+ ".jsp";
RequestDispatcher rd= req.getRequestDispatcher(viewPage);
rd.forward(req,resp);
}
}
Controller.java
public interface Controller {
public String process(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException;
}
FreeList, FreeView (Controller 구현체)
public class FreeList implements Controller {
@Override
public String process(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//freeList 코드내용
return "free/freeList";
}
}
public class FreeView implements Controller {
@Override
public String process(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//freeView 코드
return "free/freeView";
}
}
Spring은 이 command 패턴에서 더 발전된 MVC패턴을 지원한다.
그에 대한 설명은 여기를 참고하자 https://brilliantdevelop.tistory.com/193
※ *.jsp에 대한 모든 요청을 처리하는 jspServlet이 있다.
이는 Servers프로젝트의 web.xml에서 확인가능하다.
사실 JSP로 직접 요청하는 모델1구조는 없다고 볼 수 있지 않을까??
*.jsp에 대한 요청을 jspServlet이 처리하기 때문이다.