81일차 요청포워드, MVC패턴형태의 Front Controller

쿠우·2022년 7월 21일
0

M = Model : 비지니스 로직 수행결과 데이터

V = View : Model 데이터를 이용한, 응답화면을 생성

C = Controller
: MVC 흐름을 제어
: 하나의 Servlet 이다
: 모든 Request Message를 받는 집중화 수행
(적용패턴:Front Controller Pattern)
이를 위해, url-pattern mapping 에 달려있다!
(1) 디렉토리 패턴 : 반드시 '/' 로 시작해야 함
(2) 확장자 패턴: 반드시 *. 확장자로 끝나야 함

JSP로 구성할 수 있는 웹어플리케이션 아키텍쳐의 종류

model1 아키텍쳐 = JSP에서 모든것을 처리하려함

출력을 위한 view코드와 로직처리를 위한 자바코드도 함께있다.
빠르게 만들 수 있지만 / 복잡할수록 유지보수성 저하

model2 아키텍쳐 = MVC패턴

  • JSP에서 화면 view
  • 흐름제어인 Controller인 서블릿
  • 요청에 대한 로직처리는 Model인 서비스 클래스 또는 자바빈이 담당

요청포워드

요청을 받은 서블릿이나 JSP가 자신의 역할을 한 뒤
다른 서블릿과 JSP에게 다음 역할을 위임을 구현하기 위함이다.

Request forwarding 방법(2가지): ==> 화면전환기법이다!!!!

▼리퀘스트 포워딩
-RequestDispatcher객체의 getRequestDispatcher() , forward()메소드

(1)RequestDispatcher 객체의 forward 메소드 이용하는 방법
ex)RequestDispatcher dispatcher = req.getRequestDispatcher(target);
dispatcher.forward(req, res); => URL주소를 변경하지 않음 (****)

 
 @WebServlet("/Request")
public class RequestServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       

	// 포워딩 예제
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		
		// Step.1 비즈니스 로직수행
		
		// Step.2  비즈니스 데이터 (= Model)을 request scope 공유 데이터 영역에 올려 놓음 
		request.setAttribute("name", "홍길동");
		request.setAttribute("address", "서울");
		
		// -- 응답을 만들어 낼 웹컴포넌트(예: Serlvet)에 요청을 위임 (View)
		
		// Step.3 요청 위임 (by request forwarding)
		// 
		RequestDispatcher dispatcher = request.getRequestDispatcher("/Response");
		dispatcher.forward(request,response);
		
		log.info("forwarded");
		
	}// service
}// end class
 @WebServlet("/Response")
public class ResponseServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) 
			throws ServletException, IOException {
		
		String name = (String) request.getAttribute("name");
		String address = (String) request.getAttribute("address");
		
		
		response.setContentType("text/html; charset=EUC-KR");
		@Cleanup
		PrintWriter out = response.getWriter();
		out.print("<html><body>");
		 
		out.print("username 값:" + name +"<br>");
		out.print("useraddress 값:" + address +"<br>");
		
		out.print("</body></html>");
		
		out.flush();
		
	}// service

} //end class

결과 Request에서 수행했는데 Response와 연계되어 처리값을 보내준다.

▼ 다이렉션 예제2 response.sendRedirect()

(2)Response 객체의 sendRedirect 메소드를 이용하는 방법 => 리다이렉션 기법
ex)response.sendRedirect(target); => target으로 URL주소가 변경됨 (****)

 @WebServlet("/RequestRedirect")
public class RequestRedirectServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       

	// 포워딩 예제#2
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		log.trace("service(request, response) invoked",request,response);
		
//		===============================
//		Step.1 비지니스 데이터 (Model)을 Request Scope에 속성 바인딩
//		===============================
		
		request.setAttribute("name", "홍길동");
		request.setAttribute("address", "서울");
		
//		===============================
//		Step.2 Redirect 응답 전송 
//		===============================

		response.sendRedirect("/ResponseRedirect");
		
		log.info("Redirection forwarding 예제");
		
	}// service
}// end class
@WebServlet("/ResponseRedirect")
public class ResponseRedirectServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       

	// 포워딩 예제#2
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		String name = (String) request.getAttribute("name");
		String address = (String) request.getAttribute("address");
		
		
		response.setContentType("text/html; charset=EUC-KR");
		@Cleanup
		PrintWriter out = response.getWriter();
		out.print("<html><body>");
		 
		out.print("username 값:" + name +"<br>");
		out.print("useraddress 값:" + address +"<br>");
		
		out.print("</body></html>");
		
		out.flush();
		
	}// service
}// end class

결과
redirect 되었기 때문에 웹 브라우저의 URL값이 ResponseRedirect 로 변경되고, 설정된 속성 (attribute)값을 가져올 수 없다

위 (1), (2)에서 인자값이 "target"은, 요청을 위임받아 나머지역할을 처리할 웹컴포넌트 의미

=> 리퀘스트 포워딩과 리다이렉션의 차이점! (조사)
Request forwarding : 하나의 요청을 처리하기 위해 재요청을 통해 몇개의 컴파운드가 동원되야하는가가 판단기준 (기존요청에 대한 연계)(데이터를 유지)

Redirection : 목적에 맞지 않는 요청에 다른 요청을 준비할 수 있게
재요청한다가 판단기준 (전혀 새로운 요청이 수행)(데이터를 유지하지 않음)


MVC패턴에 따른 Front Controller 연습

M Model

1. DTO로 테이블에 대한 스키마를 일치시켜 주고 받을 수 있는 그릇을 만듬

//POJO
// DTO는 화면상에서 오는 모든 전송 파라미터를 읽고 쓸 수 있게 해줘야함 
@Data
public class EmpDTO {
	
	private Integer empno;
	private String ename;
	private Double sal;
	private Integer deptno;
	
}// end class

2. DAO로 테이블과 연결하여 쿼리문을 통해 CRUD조작 할 수 있게 하는 메소드들을 정의

@Log4j2
@NoArgsConstructor
public class EmpDAO {

	private static  DataSource dataSource;
	
	static { // JNDI lookup을 통해 
		try {
//		-- WAS가 생성한 DataSource 객체를 획득 하는 방법 #2----
		// WAS의 표준 API인, JNDI API를 이용해서, 설정에 의해 자동생성된 데이터 소스 획득
			
		// 1. JNDI tree 의 뿌리(root)에 접근하게 해주는 객체를 획득
		Context ctx	=  new InitialContext(); // 100% 성공 (Web App 안에서 수행만 된다면)
		
		
		//2. Context 객체를 가지고, 지정된 이름을 가지는 리소스=열매를 찾아서 획득
		//java:comp/env/ 는 리소스에 접근하는 규약(루트)
		dataSource =(DataSource)ctx.lookup("java:comp/env/jdbc/OracleCloudATP");
		
		log.info("\t this.dataSource:  {}",EmpDAO.dataSource);
		
		} catch (Exception e) {
			e.printStackTrace();
		} // try- catch
	} //static initializer
	
	
	
	public	List<EmpVO> select() throws SQLException{
		log.trace("select() invoked");
		
		// Scott 스키마의 `emp` 테이블을 모두 조회해서, 리스트로 반환 
		// 리스트 컬렉션 요소는 EmpVO 이어야함.
	
		Connection conn = EmpDAO.dataSource.getConnection();
		String sql = "SELECT * FROM emp ORDER BY empno";
		PreparedStatement pstmt = conn.prepareStatement(sql);
		

		ResultSet rs = pstmt.executeQuery();
		
		//Vector 쓰는 이유는 쓰레드가 많아졌을때를 고려하여 사용한다. 안전성
		List<EmpVO> list = new Vector();
		
		try (conn; pstmt; rs;){
			while(rs.next()) {
				Integer empno = rs.getInt("empno");
				String ename = rs.getString("ename");
				String job = rs.getString("job");
				Integer mgr = rs.getInt("mgr");
				Date hireDate = rs.getDate("hireDate");
				Double sal = rs.getDouble("sal");
				Double comm = rs.getDouble("comm");
				Integer deptno = rs.getInt("deptno");
				 
				
				EmpVO vo =new EmpVO(empno, ename, job, mgr, hireDate, sal, comm, deptno);
				list.add(vo);
				
			}//while
		
		}// try-with-resources
		
		return list;
	} // select
	
	public int delete(EmpDTO dto) throws SQLException {
		log.trace("delete({}) invoked", dto);
		
		Connection conn = EmpDAO.dataSource.getConnection();
		
		String sql = "DELETE FROM emp WHERE empno = ?";
		PreparedStatement pstmt = conn.prepareStatement(sql);
		pstmt.setInt(1, dto.getEmpno());
		
		
		try (conn; pstmt; ){
			return pstmt.executeUpdate(); // sql update  메소드 
		}// try- with - resources
		
	}// delete
	
	// DTO를 쓰는 이유에 대해 생각해보아라.
	public int insert(EmpDTO dto) throws SQLException {
		log.trace("insert({}) invoked", dto);
		
		Connection conn = EmpDAO.dataSource.getConnection();
		
		String sql = "INSERT INTO emp (empno,ename,sal, deptno) VALUES (?,?,?,?)";
		PreparedStatement pstmt = conn.prepareStatement(sql);
		pstmt.setInt(1, dto.getEmpno());
		pstmt.setString(2, dto.getEname());
		pstmt.setDouble(3, dto.getSal());
		pstmt.setInt(4, dto.getDeptno());
		
		
		try (conn; pstmt; ){
			return pstmt.executeUpdate(); // sql update  메소드 
		}// try- with - resources
	} // insert
	
	public int update(EmpDTO dto) throws SQLException {
		log.trace("update({}) invoked", dto);
		
		Connection conn = EmpDAO.dataSource.getConnection();
		
		String sql = "UPDATE emp SET ename = ? , sal = ? , deptno = ? WHERE empno = ?";
		PreparedStatement pstmt = conn.prepareStatement(sql);
		pstmt.setString(1, dto.getEname());
		pstmt.setDouble(2, dto.getSal());
		pstmt.setInt(3, dto.getDeptno());
		pstmt.setInt(4, dto.getEmpno());
		
		
		try (conn; pstmt; ){
			return pstmt.executeUpdate(); // sql update  메소드 
		}// try- with - resources
	} // update
	

}// end class

3. 비즈니스 로직을 수행할 수 있게 하는 클래스를 각각 생성

(1) Service인터페이스


public interface Service {
	public static final String DTO = "__DTO__";
	public static final String MODEL = "__MODEL__";
	
	
	public abstract void execute(HttpServletRequest req, HttpServletResponse res) 
		throws BusinessException;
	
} // end interface

(2) 유저 정의의 예외 설정

// User-defined Exception 으로, 비즈니스 로직 수행시 오류가 발생하면 
// 이 예외를 던지도록 하는데 사용

@NoArgsConstructor
public class BusinessException extends Exception {

	private static final long serialVersionUID = 1L;
	
	public BusinessException(String message) {
		super(message);
	} // constructor
	
	public BusinessException(Exception e) {
		super(e);
	}// constructor
	
} // end class

(3) Service 인터페이스를 DAO와 DTO를 사용하여 비즈니스 로직에 맞춰 수행되게 하는 클래스 생성

public class InsertServiceImpl implements Service {
	
	@Override
		public void execute(HttpServletRequest req, HttpServletResponse res) throws BusinessException {
			
			log.trace("execute(req,res) invoked");
			
			try { // 비즈니스 로직을 수행하고 , 그 결과를 데이터인 Model 을 Request scope의 바인딩
				//Step.1 Req.Scope에서 DTO 객체 획득
				EmpDTO dto = (EmpDTO) req.getAttribute(Service.DTO);
				
				// Step.2 획득한 DTO를 이용해서 , DAO의 메소드 호출 
				EmpDAO dao =new EmpDAO();
				
				// Step.3 비즈니스 로직 수행(신규사원등록)
				int deletedRows = dao.insert(dto); 
				req.setAttribute(Service.MODEL, deletedRows);
				
			} catch (Exception e) {
				throw new BusinessException(e); 
			}// try- catch
			
		}// execute
} // end class

C Controller

- model 부분에서 만들어진 그릇과 비즈니스 로직을 이용하여 요청과 응답에 대해 선별적 수행



@NoArgsConstructor
@Log4j2
// FrontController를 이용한 MVC 패턴
@WebServlet("*.do")
public class FrontControllerServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
//		request.setCharacterEncoding("utf8"); // MyFilter 혹은 Servers에 web.xml에서 처리
		
		String empno = request.getParameter("empno");
		String ename = request.getParameter("ename");
		String sal = request.getParameter("sal");
		String deptno = request.getParameter("deptno");
		
		log.info("\t empno: {} , ename: {} , sal: {} , deptno:{}", empno, ename, sal, deptno);
		
//		----
		
		// 수집된 모든 전송 파라미터를 DTO객체의 필드로 수집(저장)
		// 이 DTO(데이터 전달 객체)는 웹 3계층에서 , 앞 -> 뒤로 전달된다. 
		EmpDTO dto = new EmpDTO();
		
		log.info("dto" + dto);
		
		if(empno != null  && !"".equals(empno)) {
			dto.setEmpno(Integer.parseInt(empno));
		}
		
		dto.setEname(ename);
		
		if(sal != null && !"".equals(sal)) {
			dto.setSal(Double.parseDouble(sal));
		}// if

		if(deptno != null && !"".equals(deptno)) {
			dto.setDeptno(Integer.parseInt(deptno));
		}// if 
		
		// 현재의 Request에 대한, Response가 나가기 전까지 살아있는
		// Request Scope에 DTO 객체를 binding해 주어서, 웹 3계층의 어디에서든 
		//사용할 수 있도록 공유 
		
		request.setAttribute(Service.DTO, dto); // 공유 속성 이름 : __DTO__
		
		
		
//		===============================================
		//Step.2 Request URI를 획득하여 요청을 식별 
//		===============================================
		
		String command = request.getRequestURI();
		log.info("command" + command);

//		===============================================
//		Step.3 얻어낸 command를 처리할 비지니스 수행 객체를 식별하고 ,처리를 위임(Delegation)
//		===============================================
		
		
		try {
			// command pattern에 따라, 각 Command를 처리할 객체가 반드시 구현할 메소드를
			// 인터페이스로 규격화 하고, 이에 맞게 Command 처리객체를 생성할 수 있도록 클래스 구현하는 패턴
			switch(command) { // 유형별로 위임 처리
			case "/insert.do":		//C
				new InsertServiceImpl().execute(request, response);
				
				break;
			case "/update.do":		//R
				new UpdateServiceImpl().execute(request, response);
				break;
			case"/delete.do":		//U
				new deleteServiceImpl().execute(request, response);
				break;
			case "/select.do":		//D
				new SelectServiceImpl().execute(request, response);
				break;
			default : 				//Unknown command
				new UnknownServiceImpl().execute(request, response);
				break;
			}// switch
		} catch (Exception e) {
			throw new ServletException(e);
		}
		
//		===============================================
//		Step.4 비지니스 수행결과 데이터인 Model을 함께 전달하여 
//				응답결과 화면을 동적으로 생성하도록 View를 호출 
//		===============================================
		
		RequestDispatcher dispatcher = request.getRequestDispatcher("/View");
		dispatcher.forward(request, response);
		
		log.info("fowarding reqeust into /View");
		
	}// service
}// end class

Step4.에서 보면 View 부분을 처리하기위해 리퀘스트 포워드를 한다.

View (아직 JSP 안해서 서블릿으로 처리)

-처리된 비즈니스 로직에 대한 결과를 화면상에 출력해주는 역할

@WebServlet("/View")
public class ViewServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		log.info("view요");
		
//		============================================================
//		최종으로 request scope에 공유된 Model(비즈니스 수행데이터)와 
//		넘겨받은 request, response를 이용하여, 최종으로 동적인 응답화면 생성 및 전송
//		============================================================
		
		try {
		
		response.setContentType("text/html, charse=utf8");
			
		@Cleanup("close")
		PrintWriter out = response.getWriter();
		
		// 각 command(요청유형)별, 서비스 객체의 비지니스 수행 결과 데이터를 
		// "Request Scope"에서 얻어서, 응답문서 생성에 사용
		Object bizResult = request.getAttribute(Service.MODEL);
		
		out.println("<p>"+bizResult+"</p>");
		
		out.flush();
		
		} catch(Exception e) {
			throw new ServletException(e);
		} // try-catch
		
	}// service
	
}// end class
profile
일단 흐자

0개의 댓글