본문 바로가기
Java/Spring

전략패턴) Servlet - Controller - Service - Dao 흐름

by 박채니 2022. 8. 4.

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


지금까지 해왔던 MVC패턴을 전략패턴 이용하여 진행해보려고 합니다.

 

요청 별로 늘 servlet을 생성해왔지만, 하나의 Servlet을 이용하여 제어해보겠습니다.

구조
구조

 

url-command.properties (Command 패턴)

##########################
# url-command.properties #
##########################
/student/studentEnroll.do = com.ce.app.student.controller.StudentEnrollController
/student/selectList.do = com.ce.app.student.controller.SelectListController

 

 

AbstractController (Strategy)

public abstract class AbstractController {
	
	public String doGet(HttpServletRequest request, HttpServletResponse response) 
			throws ServletException, IOException {
		// GET방식만 구현하였는데 POST를 요청한다면 예외던짐!
		
		throw new MethodNotAllowException("GET");
	}
	
	public String doPost(HttpServletRequest request, HttpServletResponse response) 
			throws ServletException, IOException {
		// POST방식만 구현하였는데 GET를 요청한다면 예외던짐!
		
		throw new MethodNotAllowException("POST");
	}
}

만일 GET방식만 구현하였는데, POST로 요청을 한다거나 POST방식만 구현하였는데 GET으로 요청을 했을 때 MethodNotAllowException()이 던져지도록 하였습니다. (커스텀 예외클래스)

 

StudentEnrollController (Concrete Strategy)

public class StudentEnrollController extends AbstractController {
	private StudentService studentService;
	
	public SelectListController (StudentService studentService) {
		this.studentService = studentService;
	}
    
	@Override
	public String doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// super 호출하면 예외던져지므로 호출 X!
		
		return "redirect:/";
	}
}

자식 클래스들은 필요에 따른 doGet/doPost 메소드를 오버라이딩하여 사용하며, return 값으로 redirect 처리/forward처리를 해주어 DispatcherServlet에서 이를 처리하도록 해줍니다.

 

StudentListController (Concrete Strategy)

public class SelectListController extends AbstractController {
	private StudentService studentService;
	
	public SelectListController (StudentService studentService) {
		this.studentService = studentService;
	}

	@Override
	public String doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		return "student/selectList";
	}
}

 

StudentService interface

public interface StudentService {
	
}

 

StudentServiceImpl

public class StudentServiceImpl implements StudentService {
	private StudentDao studentDao;
	
	public StudentServiceImpl(StudentDao studentDao) {
		this.studentDao = studentDao;
	}
}

 

StudentDao interface

public interface StudentDao {
	
}

 

StudentDaoImpl

public class StudentDaoImpl implements StudentDao {
	
}

 

DispatcherServlet

public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private Map<String, AbstractController> urlCommandMap = new HashMap<>();
       
    public DispatcherServlet() throws FileNotFoundException, IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        // 1. url-command.properties -> Properties 객체
    	String filename = DispatcherServlet.class.getResource("/url-command.properties").getPath(); // / -> target/class 의미
    	Properties prop = new Properties();
    	prop.load(new FileReader(filename));
    	
    	// 2. Properties 객체 -> urlCommandMap에 요소추가 (String=AbstractController객체)    	
    	Set<String> urls = prop.stringPropertyNames();	// 모든 키 셋을 리턴
    	
    	StudentDao studentDao = new StudentDaoImpl();
    	StudentService studentService = new StudentServiceImpl(studentDao);
    	
    	for(String url : urls) {
    		String className = prop.getProperty(url);
    		Class<?> clz = Class.forName(className);
    		Constructor<?> constructor = clz.getDeclaredConstructor(StudentService.class);
    		AbstractController controller = (AbstractController) constructor.newInstance(studentService);
    		urlCommandMap.put(url, controller);
    	}
    	System.out.println("urlCommandMap = " + urlCommandMap);
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

@콘솔출력값
urlCommandMap = {/student/studentEnroll.do=com.ce.app.student.controller.StudentEnrollController@4e6b5f56, 
/student/selectList.do=com.ce.app.student.controller.SelectListController@422fdd77}

url-command.properties에 작성한 url = 객체를 Map의 String, AbstractController타입으로 받아주려고 합니다.

다만 Properties는 String = String으로 AbstractController 객체로 변환하여 저장해줘야 합니다.

따라서 stringPropertyNames()를 이용해 모든 key 값을 Set으로 리턴 받아 reflection을 통해 해당 객체를 생성해주었습니다.

각 Controller는 StudentServiceImpl 를 의존하고 있으며, StudentServiceImpl는 StudentDaoImpl를 의존하고 있습니다.

출력값을 확인하면 {url = 객체}로 잘 출력 된 것을 확인할 수 있습니다.

 

doGet, doPost 메소드 완성

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 1. 요청 주소
    String uri = request.getRequestURI();		// /hello-maven2/student/selectList.do
    uri = uri.replace(request.getContextPath(), ""); // student/selectList.do
    AbstractController controller = urlCommandMap.get(uri);

    // 2. 해당 controller 호출
    String method = request.getMethod(); // GET 요청 시 GET, POST 요청 시 POST 리턴
    String viewName = null;
    
    // 등록 controller가 없을 때 예외 던짐!
    if(controller == null) {
    	throw new RuntimeException("해당 요청을 처리할 Controller가 존재하지 않습니다.");
    }

    switch(method) {
    case "GET" : viewName = controller.doGet(request, response); break;
    case "POST" : viewName = controller.doPost(request, response); break;
    default : throw new MethodNotAllowException();
    }

    // 3. viewName에 따라 forward/redirect처리
    if(viewName != null) {
        if(viewName.startsWith("redirect:")) {
            // redirect 처리
            String location = request.getContextPath() + viewName.replace("redirect:", "");
            response.sendRedirect(location);
        } else {
            // forward 처리
            String prefix = "/WEB-INF/views/";
            String suffix = ".jsp";
            viewName = prefix + viewName + suffix;
            request.getRequestDispatcher(viewName).forward(request, response);
        }
    }

    // viewName이 null인 경우 컨트롤러에서 응답메세지를 직접 작성한 경우 (비동기) 아무것도 하지 않는다.
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    doGet(request, response);
}

요청 주소를 getRequsetURI()로 가져온 후 contextPath()를 제외한 uri를 이용해 controller 객체를 uriCommandMap에서 가져와줍니다.

GET/POST 방식에 따른 redirect/forward 처리 분기를 위하여 getMethod() 를 이용해 사용자의 요청방식을 가져온 후 switch문으로 분기 처리 해줍니다.

이 때, controller의 return 값이 redirect 처리 혹은 forward 처리(uri) 이므로, 해당 값을 viewName에 담아주어 redirect 혹은 forward 처리 해줍니다.

(만일 비동기로 요청을 했다면, return 값은 null)

 

Post 요청 시 doGet 메소드를 선언하여 doGet메소드 내에서 모두 처리!!

 

SqlSessionUtils

public class SqlSessionUtils {
	
	static SqlSessionFactory factory;
	
	static {
		String resource = "/mybatis-config.xml";
		
		// 1. FactoryBuilder
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		
		// 2. Factory
		InputStream is = null;
		try {
			is = Resources.getResourceAsStream(resource); // resource를 읽어서 stream으로 반환
		} catch (IOException e) {
			e.printStackTrace();
		} 
		factory = builder.build(is);
	}

	public static SqlSession getSqlSession() {
		// 3. SqlSession
		return factory.openSession(false); // autoCommit 여부
		
//		 try {
//            sqlSession = 
//                new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource)).openSession(false);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
		
	}
}