카테고리 없음

[Servlet] Spring은 왜 하나의 서블릿만 사용할까?

ckm7907 2024. 4. 3. 09:54

스프링을 사용하지 않고 Dynamic Web을 만들 때 의문이 발생했다.

왜 Servlet을 여러개 나누지 않는걸까? 주소를 나누면 되는 것이 아닌가?

@WebServlet("/main")
public class MainServlet extends HttpServlet

@WebServlet("/login")
public class LoginServlet extends HttpServlet

Servlet이 Spring의 Controller의 역할을 한다고 착각하고 있었다. 

Servlet은 Spring에서 FrontController 역할을 하고있다.

Servlet이 여러개 있으면 발생하는 문제점.

Servlet의 생명주기는 Web Container가 꺼질 때까지 반영구적이다.

즉, Servlet 객체는 한번 생기면 서버가 꺼질 때까지 존재한다는 것이다.

그렇게 되면 메모리를 낭비하는 것이다. JVM 의 heap 영역에 servlet이 계속 존재하는 것이다.

이것은 Servlet이 엄청 많아질 때 문제가 생긴다.

예를 들어 10만개의 기능(login, logout, board create,...)을 하나하나 Servlet으로 만들면 서버에 메모리는 남아나질 않을 것이다.

그래서 하나의 서블릿을 사용하고, switch 문으로 분기 시킨다.

하나의 서블릿을 사용하여 구현하기.

	  switch (paramMap.get("sign")) { 
		  case "registerMember":
			  // 
		  	break; 
		  case "login" : 
			  //
		  break; 
	  }

클라이언트에서 sign을 추가하여 보내면 servlet에서 switch 문으로 그에 맞는 코드를 실행한다.

let data = {sign:"logout"};

하지만 이렇게 구현하면 하나의 서블릿에 코드가 모두 적혀 가독성이 떨어지고, 결합도가 너무 클것이다.

그래서 Spring과 비슷하게 구현해 본다.

 

beans.xml 과 파싱을 사용해서 나누기.

MyContextListener.java

먼저 MyContextListener.java를 만든다.

왜냐하면 웹서블릿이 실행되고, Listener가 바로 실행하여 controller 맵(key : sign, value : controller)을 만들어 applicationScope 에 올려 놓아야 하기 때문이다.

또한 우리는 servlet을 하나만 올리지만, 2개 이상을 올려야한다면 controller 맵 두번 추가하는 동시성 이슈가 생길 수 있어, listner에서 context container가 생기자마자 controller 맵을 올린다.

XMLBeanFactory.java 를 구동시켜 나온 map을 servletContext  에 beans 라는 이름으로 올린다.

try {
			String path = sce.getServletContext().getRealPath("/WEB-INF/beans.xml").toString();
			
			XmlBeanFactory factory;
			factory = new XmlBeanFactory(path);
			
			beans = factory.getBeans();
			
			sce.getServletContext().setAttribute("beans",beans);
		} catch (Exception e) {
			e.printStackTrace();
		}
}

beans.xml

car 과 관련된 sign 이 오면 CarController로, login 과 관련된 sign이 오면 MemberController로 가도록 설정한다.

여기서 bean EL을 찾아 파싱할것이므로 bean 안에 id, class가 존재해야한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans>
	<bean id="carList" class="com.web.test.controller.CarController" />
	<bean id="carDetail" class="com.web.test.controller.CarController" />
	<bean id="carRegist" class="com.web.test.controller.CarController" />
	<bean id="carRegistForm" class="com.web.test.controller.CarController" />
	<bean id="carDelete" class="com.web.test.controller.CarController" />
	<bean id="carUpdate" class="com.web.test.controller.CarController" />
	<bean id="carUpdateForm" class="com.web.test.controller.CarController" />
	<bean id="loginForm" class="com.web.test.controller.MemberController" />
	<bean id="login" class="com.web.test.controller.MemberController" />
	<bean id="logout" class="com.web.test.controller.MemberController" />
</beans>

 

XMLBeanFactory.java

java가 제공하는 SAXParser을 사용했다.

여기서 path는 beans.xml의 위치이다.

parser.parse() 는 이벤트 발생장치이다.

  마치 html 에서 버튼이 눌리면 알아서 js 코드가 인식해서 실행하는 흐름과 같다.

  여기서 MyDefaultHandler가 실행되고, bean를 파싱해서 key :id , value : class로 설정해서 맵으로 만든다. 

public XmlBeanFactory(String path) throws Exception {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		SAXParser parser = factory.newSAXParser();
		parser.parse(path, new MyDefaultHandler());
	}

 

Controller들의 인터페이스를 만들어 형식을 지정한다.

public interface Controller {
	public void process(Map<String,String> paramMap,Map<String, String> returnMap, HttpServletRequest request, HttpServletResponse
			  response) throws ServletException, IOException, SQLException;
}

 

MainServlet.java

메인 서블릿(spring에서는 Dispatcher Servlet)이 Listner에서 올린 ServletContext 의 beans 맵에서 sign값과 매칭되는 Controller의 process 메소드를 실행시킨다.

이렇게 컨트롤러를 분기하는 것이다.

protected void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			beans.get(request.getParameter("sign")).process(null, null, request, response);
		} catch(Exception e) {
			e.printStackTrace();
			request.setAttribute("e", e);
			Forward.forward(request,response,"error.jsp");
		}
	}

 

WebContainer 실행 -> ServletContext 생성 -> Listener 구동 -> ServletContext에 beans 맵 올림 -> Servlet init
servlet 에서 beans 맵을 사용하여 controller 분기

 

스프링 하나의 서블릿만 사용하는 이유 정리

1. 중앙 집중식처리

모든 요청을 하나의 서블릿에서 처리함으로써 요청 처리 과정을 일관성 있게 관리할 수 있다.

2. 공통 로직의 재사용

공통적인 작업을 DispatcherServlet에서 처리함으로써 코드의 중복을 줄일 수 있다.

3. 프론트 컨트롤러 패턴 적용

DispatcherServlet 은 프론트 컨트롤러 패턴을 구현한 것으로, 이 패턴은 한 곳에서 받아서 처리하고, 그 결과를 클라이언트에게 반환하는 역할을 한다.