포스팅의 목차는 다음과 같다. 금일 포스팅의 내용 또한 이전 게시판MVC나누기 실습에 대한 게시글의 내용을 기반으로 작성되었다.
1. FrontController
1) What?
2) Why?
3) How?
2. Controller
1) interface
2) implement
3. 결과 및 오류처리
1) RequestDispatcher
2) forward( )
1. FrontController
1) What?
카페 혹은 음식점의 카운터같은 개념으로, FrontController에 사용자의 요청정보를 받아서 모아두고, 뒤에 전달하여 처리될 수 있도록 한다.
2) Why?
사용자의 요청정보를 한곳으로 모아서 분기처리가 가능하기 때문에 유지보수가 용이해진다. 굳이 서블릿파일을 활용하여 FrontController의 개념을 활용하게 되는데, 그 이유는 jsp는 원래 컴파일을 통해 서블릿이 되기 때문에 일반 class가 아니라 서블릿파일로 전환하려는 것이다.
3) How?
해당 개념을 적절히 응용하려면 서블릿파일과 인터페이스, 각 분기별 로직을 담을 여러개의 POJO(Plain Old Java Object)가 필요하다. 우선 서블릿파일을 생성하는 로직에 대해 확인해보고, 각각의 단락에서 순차적으로 POJO파일을 생성하여, 어떤 단계로 진행되며 적용되는지 알아볼 필요가 있다.
① 서블릿 파일 생성
1. 작업을 시작하기에 앞서, controller의 작업과 관련된 JAVA파일을 담을 Package를 하나 생성한다.
2. 서블릿파일을 생성하는데, 이때 파일 이름 설정 후 next로 넘어가 URL Mappings을 수정해야한다.
수정 완료후 파일을 생성하게 되면 어노테이션에 위의 내용이 반영된 것을 확인할 수 있다.
3. 사용자의 요청을 한 번에 모을 수 있는 메서드 별도 생성
package controller;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class FrontController
*/
@WebServlet("*.do")//*.do로 요청하게 되면 어노테이션에 의해 FC로 오게 됨
public class FrontController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public FrontController() {//서블릿컨테이너 관리할때 사용할 기본생성자
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// get요청시 actionDo메서드로 이동
actionDo(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// post요청시 actionDo메서드로 이동
actionDo(request, response);
}
//사용자의 요청을 한 번에 모으는 친구!
private void actionDo(HttpServletRequest request, HttpServletResponse response) {
//메서드 생성 완료
}
}
코드로 확인할 수 있는 바와 같이 별도 생성한 메서드명은 actionDo로, get과 post 각각의 방식으로 사용자의 요청이 넘어왔을때 actionDo메서드가 실행될 수 있도록 코드를 작성한다. 이때, actionDo로 이동할때 request, response가 함께 넘어갈 수 있도록 하며 actionDo는 인자로 request와 response를 가져야한다.
② 별도 생성 메서드_actionDo
1. 사용자의 요청 URL에서 필요한 부분만 뽑기
getRequestURL로 사용자의 요청(URL)를 불러온다. 여기서 getContextPath로 필요하지 않은 패키지부분을 제거하면 필요한 데이터만을 가공해낼 수 있다.
private void actionDo(HttpServletRequest request, HttpServletResponse response) {
String uri = request.getRequestURI(); // 사용자의 요청을 알 수 있다! /day046/*.do
String cp = request.getContextPath(); // 패키지 부분을 ContextPath라고 부름! /day046/
// 패키지는 필요없고, *.do로만 요청을 했다면 이쪽으로 올 수 있게 하면 됨
String command = uri.substring(cp.length());
System.out.println(command); //*.do
}
2. 분기처리
뽑아낸 command변수를 활용하여 분기처리가 가능하게 된다.
private void actionDo(HttpServletRequest request, HttpServletResponse response) {
String uri = request.getRequestURI(); // 사용자의 요청을 알 수 있당! /day046/*.do
String cp = request.getContextPath(); // 패키지 부분을 컨텍스트 패스라고 부름! /day046/
String command = uri.substring(cp.length()); // 패키지는 필요없고, *.do로만 요청을 했다면 이쪽으로 올 수 있게 하면 됨
//System.out.println(command);
//분기처리!
if(command.equals("/login.do")) {
System.out.println("FC로그 : 로그인 요청 들어옴");
} else if(command.equals("/main.do")) {
System.out.println("FC로그 : 메인페이지 이동 요청 들어옴");
} else if(command.equals("/logout.do")) {
System.out.println("FC로그 : 로그아웃 요청 들어옴");
} else if(command.equals("/board.do")) {
System.out.println("FC로그 : 상세보기페이지 이동 요청 들어옴");
} else if(command.equals("/insert.do")) {
System.out.println("FC로그 : 글등록페이지 이동 요청 들어옴");
}else if(command.equals("/update.do")) {
System.out.println("글수정");
}else if(command.equals("/delete.do")) {
System.out.println("글삭제");
}else if(command.equals("/signup.do")) {
System.out.println("FC로그 : 회원가입 요청 들어옴");
}else if(command.equals("/mypage.do")) {
System.out.println("FC로그 : 마이페이지이동 요청 들어옴");
}else if(command.equals("/mupdate.do")) {
System.out.println("FC로그 : 회원정보수정 요청 들어옴");
}else if(command.equals("/mdelete.do")) {
System.out.println("회원삭제");
}else {
System.out.println("잘못된 요청");
}
}
이때 이 상태에서 모든 분기에 대한 로직을 작성하게 되면 여러가지의 단점이 발생하게 된다. 우선 병렬개발에 있어 용이하지 않으며, 오류에 대한 파급효과가 크고, 한개의 분기점에 대해 작은 코드 수정이 있을시 재컴파일이 진행되어야한다는 것이다. 이런 단점들을 방지하기 위해 사용자의 요청을 처리해줄 수 있는 각각의 로직들을 여러개의 POJO파일로 분포하여 생성할 수 있다.
2. Controller
1) interface
우선 모든 요청처리는 request와 response를 필수로 받아, 요청처리에 대한 결과를 반환해주어야하는데, 인터페이스를 통해 추상메서드로 메서드 시그니처를 강제할 수 있다.
코드는 다음과 같다.
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface Action {
//모든 요청처리들은 반드시 request와 response를 가질 수 있도록 메서드에 인자로 강제성 부여
//ActionForward로 아웃풋도 강제성 부여!
//자잘자잘 오류 생길 수 있으니까 쓰로우 익셉션 ㄱㄱ
public ActionForward execute(HttpServletRequest req, HttpServletResponse res) throws Exception;
}
① Input
request와 response를 필수로 받아야하기 때문에 두개의 객체를 인자로 두어야할 필요가 있다.
② Output
요청처리에 대한 결과를 반환해주어야하는데, 이때 결과 뿐 아니라 처리방식또한 함께 반환해줄 필요가 있다. 하나의 메서드에서 2개의 teturn은 불가능하기에 별도 class(자료형)을 생성하여 이 class를 반환해줄 수 있도록 해야한다.
package controller;
public class ActionForward {
//멤버변수
private String path;//어디로 갈지
private boolean redirect;//어떻게 갈지
//생성자
public ActionForward() {
}
//getter & setter
public String getPath() {
return path;
}
public boolean isRedirect() {
return redirect;
}
public void setPath(String path) {
this.path = path;
}
public void setRedirect(boolean redirect) {
this.redirect = redirect;
}
}
2) implement
사용자의 요청을 처리 하기 위한 POJO이다. 각 분기점별로 담당 POJO를 만들어 처리한다. 이때 모든 분기처리를 위한 POJO들은 기존에 생성한 interface를 implement해야한다. 어떤 형식으로 진행되는지 간단히 알기 위하여 login과 main에 대한 분기처리의 코드만 첨부해보았다. 내용은 주석을 통해 해석 가능하다.
Login분기점
package controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import member.MemberDAO;
import member.MemberVO;
public class LoginAction implements Action{
@Override
public ActionForward execute(HttpServletRequest req, HttpServletResponse res) throws Exception {
ActionForward forward = new ActionForward();//3. 아웃풋
// 1. 인코딩
req.setCharacterEncoding("UTF-8");
res.setCharacterEncoding("UTF-8");
// 2. 로직처리
// useBean부분
String mid = req.getParameter("mid");//입력된 id갖고온거 변수에 저장
String mpw = req.getParameter("mpw");//입력된 pw갖고온거 변수에 저장
MemberDAO dao=new MemberDAO();
MemberVO vo=new MemberVO();
// setProperty부분
vo.setMid(mid);
vo.setMpw(mpw);
//이제 m통해서 vo전달, 그거 다시 vo로 받고
vo=dao.selectOne(vo);//vo재사용
if(vo==null) {//만약 로그인에 실패하면,
System.out.println("로그: 로그인 실패");
forward.setPath("/a_login.jsp");//어디로?
forward.setRedirect(true);//리다이렉트 방식으로?
}else {//성공하면
//request.getSession을 통해 session정보에 접근!
HttpSession session=req.getSession();
session.setAttribute("data", vo);
forward.setPath("/main.do");//어디로?
forward.setRedirect(true);//리다이렉트 방식으로?
}
return forward;
}
}
main분기점
package controller;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import board.BoardDAO;
import board.BoardVO;
public class MainAction implements Action{
@Override
public ActionForward execute(HttpServletRequest req, HttpServletResponse res) throws Exception {
// 1. 필터,리스너 아직 사용안함 -> 인코딩 처리
req.setCharacterEncoding("UTF-8");
res.setCharacterEncoding("UTF-8");
// 2. output
ActionForward forward=new ActionForward();
forward.setPath("/main.jsp");
forward.setRedirect(false);
// 3. 로직처리
BoardDAO dao=new BoardDAO();
BoardVO vo=new BoardVO();
ArrayList<BoardVO> datas=dao.selectAll(vo);
req.setAttribute("datas", datas);
return forward;
}
}
3. 결과 및 오류처리
1) RequestDispatcher
특정 자원에 처리를 요청하고 처리 결과를 얻어오는 기능을 수행하는 클래스이다. 쉽게 말해 사용자로부터 들어온 요청을 처리하고, 그 처리 결과를 올바른 결과페이지로 보내는 역할을 수행하는 클래스라고 할 수 있다.
2) forward( )
해당 메서드를 통해 제어권이 넘어가면서 클라이언트가 응답을 받을 수 있게 된다. 즉, 요청의 처리 결과로 생성되는 request, response객체를 타겟페이지로 전달한다.
해당 부분의 코드는 다음과 같으며, 주석을 통해 내용을 파악할 수 있다.
package controller;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class FrontController
*/
@WebServlet("*.do")//*.do로 요청하게 되면 어노테이션에 의해 FC로 오게 됨
public class FrontController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public FrontController() {//서블릿컨테이너 관리할때 사용할 기본생성자
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
actionDo(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
actionDo(request, response);
}
//사용자의 요청을 한 번에 모으는 친구!
private void actionDo(HttpServletRequest request, HttpServletResponse response) {
String uri = request.getRequestURI(); // 사용자의 요청을 알 수 있당! /day046/*.do
String cp = request.getContextPath(); // 패키지 부분을 컨텍스트 패스라고 부름! /day046/
String command = uri.substring(cp.length()); // 패키지는 필요없고, *.do로만 요청을 했다면 이쪽으로 올 수 있게 하면 됨
//System.out.println(command);
//forward를 통해 결과 반환 예정인데, 한 번의 선언으로
//각 분기점에서 if를 탈출할때마다 페이지를 이동할 수 있도록 스코프조절하여 상단배치
ActionForward forward=null;
//분기처리!
if(command.equals("/login.do")) {
System.out.println("FC로그 : 로그인 요청 들어옴");
try {
forward=new LoginAction().execute(request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if(command.equals("/main.do")) {
System.out.println("FC로그 : 메인페이지 이동 요청 들어옴");
try {
forward=new MainAction().execute(request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if(command.equals("/logout.do")) {
System.out.println("FC로그 : 로그아웃 요청 들어옴");
try {
forward=new LogoutAction().execute(request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if(command.equals("/board.do")) {
System.out.println("FC로그 : 상세보기페이지 이동 요청 들어옴");
try {
forward=new BoardAction().execute(request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if(command.equals("/insert.do")) {
System.out.println("FC로그 : 글등록페이지 이동 요청 들어옴");
}else if(command.equals("/update.do")) {
System.out.println("글수정");
}else if(command.equals("/delete.do")) {
System.out.println("글삭제");
}else if(command.equals("/signup.do")) {
System.out.println("FC로그 : 회원가입 요청 들어옴");
try {
forward=new SignupAction().execute(request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else if(command.equals("/mypage.do")) {
System.out.println("FC로그 : 마이페이지이동 요청 들어옴");
try {
forward=new MypageAction().execute(request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else if(command.equals("/mupdate.do")) {
System.out.println("FC로그 : 회원정보수정 요청 들어옴");
try {
forward=new MupdateAction().execute(request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else if(command.equals("/mdelete.do")) {
System.out.println("회원삭제");
}else {
System.out.println("잘못된 요청");
}
//혹시 에러 발생하면 에러페이지로 가!
if(forward==null) {
forward=new ActionForward();
forward.setPath("error.jsp");
forward.setRedirect(false);
}
//처리 결과를 올바른 결과페이지로 보내는 역할을 수행하는 클래스
RequestDispatcher dispatcher=request.getRequestDispatcher(forward.getPath());//null이면 500오류뜸
try {
//.forward는 요청의 처리결과로 생성되는 request, response객체(set어트리뷰트)를 타겟페이지(위 친구 인자)로 전달하는 메서드
//제어권을 넘겨서 클라이언트가 응답받을 수 있도록하는 메서드이다.-> 긍게 화면을 넘긴다고.
dispatcher.forward(request, response);
} catch (ServletException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
* 기존의 컨트롤러를 제거하고, 이 방식을 채택하기 위해서는 이전에 작성된 View코드에서 경로에 관한 내용을 일괄 수정해야한다. *
연결방식에 대해 ?와 &표시에 대한 의미를 파악해야한다.
* <td><a href="board.do?bid=${v.bid}">${v.bid}</a></td> => 첫 파라미터는 ?로 연결, 그 뒤부터 &임 *
본 포스팅내용에 대한 전체 코드는 아래에 파일로 첨부하였다.
'JSP' 카테고리의 다른 글
[JDBC] MVC나누기 3_게시판+FC,필터,리스너 (0) | 2022.03.11 |
---|---|
[controller업그레이드] 리스너 & 필터_(크롤링 & 인코딩) (0) | 2022.03.10 |
[View업그레이드] 커스텀 태그 (0) | 2022.03.07 |
[JDBC] MVC나누기_2 : 게시판+로그인 (0) | 2022.03.06 |
[View업그레이드] EL & JSTL (0) | 2022.03.03 |