[First Shop] Servlet 과 Struts framework

Jason·2023년 9월 26일
0

FirstShop

목록 보기
1/5
post-thumbnail

첫 프로젝트를 완성도 있게 만들기 위해

나의 부트캠프에서의 첫 프로젝트인 Shopping mall 프로젝트 정기 리팩터링이 모두 끝났다.
쇼핑몰을 정말 쇼핑몰 답게 만들기 위해 고민하다 보니 이제부터 진짜 시작이라는 생각이 든다.
기능을 추가할 때마다 코드와 함께 나의 느낀 점 등을 기록하려 한다.

앞으로 새로운 기능이나, 현재 로직을 손 대서 완성도를 높여 볼 예정이다.


그 전에..

이전의 학습 여정에서 알게 된 새로운 지식과 느낀 점 부터 차례로 작성해야겠다.
메인 프로젝트에 들어가기 전이니, 최대한 빨리 기록하자..

0-1 과정을 즐기자

첫 프로젝트를 진행하면서 수 많은 에러들과 좌절을 맛보았고, 그러다가 기능 하나가 제대로 돌아갈 때의 행복을 느끼는 수순으로 흘러갔는데, 기능이 돌아갈 때만 행복감을 느끼면 이 일을 오래 하기 힘들 것 같다고 생각이 들었다.

그래서 문제를 해결해 가는 과정을 즐기려고 한다.



0-2 Servlet

Servlet 은 Java 의 HTTP 요청을 처리하는 Web 기술이다.

Servlet 과 뗄 수 없는 LifecycleSingleton

일단 Servlet 의 생명주기에 대해 먼저 정리하자면 외부에서 초기 요청이 오면,
Servlet Container 가 init() method 호출해서 상태값을 세팅해 준다.

그와 동시에 service() method 를 호출하고, 이는 doGet() doPost() 등을 호출해준다.

이후 동일한 요청이 들어오면 service() 호출만 반복되고 새 생성자는 만들어지지 않는다.
동일한 작업은 instance 하나로 모두 처리하게 된다.

앱이 종료되는 시점, 혹은 일정한 앱 내 구현된 로직에 따라 destroy() method 가 호출되며 작업에서 없앤다.

이와 같이 한 번의 상태값 세팅으로 여러 곳에서 사용하게 관리되는 패턴을 Singleton 이라 한다.

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LifecycleServlet extends HttpServlet {

    // init 메서드
    @Override
    public void init() throws ServletException {
        System.out.println("Servlet is being initialized.");
    }

    // GET 요청 처리 메서드
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        resp.getWriter().write("Hello from LifecycleServlet!");
    }

    // 서블릿 소멸 메서드
    @Override
    public void destroy() {
        System.out.println("Servlet is being destroyed.");
    }
}

Singleton pattern 의 장점

Singleton 이 아닌 일반적인 경우 특정 클래스 내의 기능을 사용하기 위해서는 매 번 생성자로 메모리에 인스턴스화 해줘야 하는데, 요청이 너무 많으면 메모리 부하가 걸릴 가능성이 있다.

그러나 Singleton 의 경우 메모리 영역에 하나의 객체만 인스턴스화 하고 이후는 method 호출만 이뤄지니, Server 메모리 관리가 용이해 진다.


0-3 Struts framework

Servlet 을 MVC 패턴 개발을 용이하게 해준다.

Action 에서 시작하고 Action 으로 끝난다.

첫 프로젝트 진행에서 가장 기억에 남는 것은, Action 클래스가 많았다는 것이다.

프로젝트가 회사라고 보았을 때 역할 별로 비유 해보겠다.

0-3-1. Action = 일처리

package com.model2.mvc.framework;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public abstract class Action {
	
	private ServletContext servletContext;
	
	public Action(){
	}
	
	public ServletContext getServletContext() {
		return servletContext;
	}

	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

	public abstract String execute(HttpServletRequest request, HttpServletResponse response) throws Exception ;
}

일처리(Action) 자체다. 고객 요청이 오면 무언가를 한다. ( execute )
근데 추상적인 일처리 라서 구체적으로 만들어 줘야한다. (후술 예정)


0-3-2. ActionServlet = 사장

package com.model2.mvc.framework;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.model2.mvc.common.util.HttpUtil;


public class ActionServlet extends HttpServlet {
	
	private RequestMapping mapper;

	@Override
	public void init() throws ServletException {
		
		super.init();
		String resources=getServletConfig().getInitParameter("resources");
		mapper=RequestMapping.getInstance(resources);
		// 초기 properties 를 RequestMapping 으로 전달되며, 인스턴스 생성 시 값 전달됨.
	}

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) 
																									throws ServletException, IOException {
		
		String url = request.getRequestURI();
		String contextPath = request.getContextPath();
		String path = url.substring(contextPath.length());
		System.out.println(path);
		
		try{
			Action action = mapper.getAction(path);
			action.setServletContext(getServletContext());
			
			String resultPage=action.execute(request, response);
			String result=resultPage.substring(resultPage.indexOf(":")+1);
			
			if(resultPage.startsWith("forward:"))
				HttpUtil.forward(request, response, result);
			else
				HttpUtil.redirect(response, result);
			
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
}

ActionServlet 은 사장님이다. 고객의 요청이 오면 사장님은 RequestMapping 이라는 부장님에게 일을 맡긴다. 사장님은 .do 라는 요청이 들어왔을 때만 움직인다. (단일인입점 구성)
ex) /product/addProduct.do

아래는 프로젝트 메타데이터를 저장해 두는 web.xml 파일 내용 일부이다.
url 이 *.do 로 왔을 때 ActionServlet 사장님이 호출된다.

<servlet>
		<servlet-name>action</servlet-name>
		<servlet-class>com.model2.mvc.framework.ActionServlet</servlet-class>
        
        <!-- 나중에 RequestMapping 이 참고할 경로가 있는 곳 -->
		<init-param>
			<param-name>resources</param-name>
			<param-value>com/model2/mvc/resources/actionmapping.properties</param-value>
		</init-param>
</servlet>

<servlet-mapping>
		<servlet-name>action</servlet-name>
		<url-pattern>*.do</url-pattern>
</servlet-mapping>

init-param 태그 내의 경로에 있는 actionmapping.properties 라는 사내연락망에서 *.do 의 요청 이름에 따라 일 할 직원을 찾는다. 그 예는 아래 처럼 기록 되어있다.

/addProduct.do = com.model2.mvc.view.product.AddProductAction
/getProduct.do = com.model2.mvc.view.product.GetProductAction
/updateProductView.do = com.model2.mvc.view.product.UpdateProductViewAction
/updateProduct.do = com.model2.mvc.view.product.UpdateProductAction
/listProduct.do = com.model2.mvc.view.product.ListProductAction



0-3-3. RequestMapping = 부장님

package com.model2.mvc.framework;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;


public class RequestMapping {
	
	private static RequestMapping requestMapping;
	private Map<String, Action> map;
	private Properties properties;
	
	private RequestMapping(String resources) {
		map = new HashMap<String, Action>();
		InputStream in = null;
		try{
			in = getClass().getClassLoader().getResourceAsStream(resources);
			properties = new Properties();
			properties.load(in);
		}catch(Exception ex){
			System.out.println(ex);
			throw new RuntimeException("actionmapping.properties 파일 로딩 실패 :"  + ex);
		}finally{
			if(in != null){
				try{ in.close(); } catch(Exception ex){}
			}
		}
	}
	// 본 method 는 synchronized 로 한 번에 하나의 thread 만 실행한다.
	public synchronized static RequestMapping getInstance(String resources){
		if(requestMapping == null){
			requestMapping = new RequestMapping(resources);
		}
		return requestMapping;
	}
	
	public Action getAction(String path){
		Action action = map.get(path);
		if(action == null){
			String className = properties.getProperty(path);
			System.out.println("prop : " + properties);
			System.out.println("path : " + path);			
			System.out.println("className : " + className);
			className = className.trim();
			try{
				Class c = Class.forName(className);
				Object obj = c.newInstance();
				if(obj instanceof Action){
					map.put(path, (Action)obj);
					action = (Action)obj;
				}else{
					throw new ClassCastException("Class형변환시 오류 발생  ");
				}
			}catch(Exception ex){
				System.out.println(ex);
				throw new RuntimeException("Action정보를 구하는 도중 오류 발생 : " + ex);
			}
		}
		return action;
	}
}

RequestMapping 은 부장님이다. Class 명을 보면 알 수 있듯이 요청이 오면 사장님이 resources 를 파라미터로 주면서 일을 시킨다. (getAction)
부장님은 상술한 actionmapping.properties 로 가서 목적에 맞는 직원 (**Action) 에게 일을 시킨다.



0-3-4. **Action = 사무 직원

package com.model2.mvc.view.product;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.model2.mvc.framework.Action;
import com.model2.mvc.service.product.ProductService;
import com.model2.mvc.service.product.impl.ProductServiceImpl;
import com.model2.mvc.service.product.vo.ProductVO;

public class AddProductAction extends Action {

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		ProductVO productVO = new ProductVO();
		
		productVO.setProdName(request.getParameter("prodName"));
		productVO.setProdDetail(request.getParameter("prodDetail"));
		productVO.setManuDate(request.getParameter("manuDate").replace("-", ""));
		productVO.setPrice(Integer.parseInt(request.getParameter("price")));
		productVO.setFileName(request.getParameter("fileName"));
		
		System.out.println(productVO);
		
		ProductService service = new ProductServiceImpl();
		service.addProduct(productVO);
		
		return "redirect:/product/addProductView.jsp";
	}

}

**Action 은 사무를 담당하는 직원이다.
무언가를 하라고 지시가 내려오면 본인이 맡은 업무를 execute 한다.

AddProductAction 이라는 사무 직원을 예로 들겠다.
AddProductAction 은 제품을 신규 등록하는 업무를 하는데, ProductVO 라는 제품 정보를 담는 양식에 정보를 각 항목에 맞게 적어 넣고 ( setter ),
제품 등록 service 주문을 넣는다. ( service.addProduct(productVO) )
사무 직원은 service 주문서에 주문을 넣고 결과를 받는 것만 하지, 현장에서 실제 등록은 어떻게 이뤄지는지 모른다. (3-tier architecture , 후술)



0-3-5. **DAO or Dao = 업무 처리 현장

package com.model2.mvc.service.product.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;

import com.model2.mvc.common.SearchVO;
import com.model2.mvc.common.util.DBUtil;
import com.model2.mvc.service.product.vo.ProductVO;

public class ProductDAO {
	//Constructor
	public ProductDAO() {
	}
	//Method
	public void insertProduct(ProductVO productVO) throws Exception {
		
		Connection con = DBUtil.getConnection();
		
		String sql = "insert into PRODUCT values ( seq_product_prod_no.nextval, ?, ?, ?, ?, ?, sysdate ) ";
		
		PreparedStatement pstmt = con.prepareStatement(sql);
		//prod_no = seq_product_prod_no.nextval (다음 번호 시퀀스 진행)
		pstmt.setString(1, productVO.getProdName()); //prod_name
		pstmt.setString(2, productVO.getProdDetail()); //prod_detail
		pstmt.setString(3, productVO.getManuDate()); //manufacture_day
		pstmt.setInt(4, productVO.getPrice()); // price
		pstmt.setString(5, productVO.getFileName()); // image_file
		// regDate 는 sysdate
		pstmt.executeUpdate();
		
		con.close();
		
	} //end of insertProduct

**DAO 는 업무가 진행되는 현장이다. ( 물류창고 느낌 )
ProductDAO 를 예로 들겠다.
제품등록 주문서를 받아서 여기서 실제 등록을 처리 한다.

여기까지 제품 등록이 완료되면 **Action 이 지정한 경로에 제품 등록 정보를 보여준다.

profile
어제보다 매일 1% 성장하고 있습니다.

0개의 댓글