Servlet 상속 구조
Servlet의 상속구조를 살펴보면 가장 최상위 인터페이스인 Servlet에서부터 GenericServlet, HttpServlet로 내려와
우리가 사용하는 서블릿은 HttpServlet를 상속받아 사용한다.
우선, 가장 최상위 인터페이스인 Servlet의 공식문서를 살펴보면, 아래와 같은 메소드를 가지고있다.
서블릿의 생명주기에 관한 메소드인 init() -> service() -> destroy() 와,
기타 Servlet에 대한 정보, 초기화에 필요한 객체(이를테면 DB 커넥션등)들을 가져오는 ServletConfig()가 있다.
즉, 모든 Servlet은 init() -> service() -> destroy() 의 과정을 통해 실행된다는 것을 알수있다.
그런데, GenericServlet와 HttpServlet는 뭐가다를까?
둘다 Servlet을 구현한 Servlet구현체지만, 사용하는 목적이 다르다.
GenericServlet은 프로토콜에 상관없이 일반적인 Servlet클래스로 Service를 오버라이드하여 사용한다.
그러나 HttpServlet는 HTTP 프로토콜에 특화된 서블릿 구현체로 doGet,doPost등 HTTP 메소드에 따른 다양한 요청을 처리할수있는 기능을 제공한다.
이제 Servlet의 역할과 구조에 대해 알았으니, 직접 구현해보자!
Servlet 인터페이스와 HttpServlet
public interface Servlet {
public void init(ServletConfig config);
public void service(Request request,Response response);
public void destroy();
}
public abstract class HttpServlet implements Servlet{
String url;
protected void doGet(Request req , Response res) {}
protected void doPost(Request req , Response res) {}
@Override
public void service(Request req , Response res) {
if(req.getMethod()==HttpMethod.GET) {
doGet(req ,res);
}else if(req.getMethod()==HttpMethod.GET) {
doPost(req ,res);
}else {
res.setStatus(405);
}
}
public void init(ServletConfig config) {
System.out.println("Servlet init !!");
this.url = config.url;
}
public void destroy() {
url = null;
System.out.println("Servlet Destroyed !!");
}
}
최상위 인터페이스 Servlet의 구조는 간단하다. 서블릿의 생명주기 필수메소드인 init(),service(),destroy()와
그에 필요한 파라미터값들을 넣어준다.
그리고 HttpServlet에서는 초기화 init()와 파괴 destroy()를 구현해주었지만,
실제 tomcat 에서는 서블릿컨테이너가 자동으로 서블릿을 생성하여 초기화하고, 파괴하는등의 생명주기를 관리 해준다.
또한 HttpServlet에서 Service를 오버라이드하여 Request의 요청 Method를 구분하여 Get 요청이라면 doGet을, POST 요청이라면 doPost를 실행시키는 일종의 핸들러 역할을 수행하도록 구현해주었다.
이제 개발자는 특정 url에 따른 다양한 요청을 처리하기위해 서블릿을 생성한뒤, HttpServlet을 상속받아 doGet , doPost만 오버라이드해주면 되는것이다.
public class CustomServlet extends HttpServlet {
@Override
protected void doGet(Request req, Response res) {
res.setBody("This is Custom Servlet!!");
res.setStatus(200);
}
@Override
protected void doPost(Request req, Response res) {
res.setStatus(200);
res.setBody("This is Custom Servlet!!");
}
}
위처럼 커스텀 서블릿을 구현해준뒤, doGet 요청과 doPost에 따른 다른 처리과정을 오버라이드 해주었다.
이제, 특정 PORT에 클라이언트의 요청이 오면 해당 url과 요청에 맞는 서블릿이 실행되어 doGet() 메소드를 실행해주고,
Response값을 반환해줄것이다.
그런데 여기서 가장큰 의문점이 남아있을것이다.
클라이언트의 요청을 처리하려면 내가만든 Servlet 객체들이 모두 생성되어있어야하고,
특정 URL에 특정 Servlet이 매핑되어있어야 할텐데 이러한 작업은 누가해주는가?
바로여기서 우리가 사용해왔던 WAS, tomcat의 필요성을 가장 크게 체감할수있었다.
이러한 역할들을 서블릿 컨테이너에서 해주었던 것이다.
tomcat을 실행하면 콘솔창에 무수히 많은 servlet객체들이 생성되어 매핑되는것을 볼수있다.
톰캣은 web.xml의 <servlet> 태그를 읽어 서버를 실행하면 모든 Servlet객체들을 생성하고, 정의되어있는 해당 서블릿의 매핑URL과 Mapping 시켜 보관해놓은뒤,
사용자의 요청이 들어오면 mapping된 서블릿을 꺼내 init() -> service() -> destroy() 해주는 것이다!
이제 최종적으로 WAS의 역할을 수행할수있는 서블릿 컨테이너를 구현해주면 끝이다.
서블릿 컨테이너 구현하기
public class ServletMapper {
private static final Logger logger = LoggerUtil.getLogger(ServletMapper.class);
private HashMap<String, Servlet> servletContainer;
public ServletMapper() {
servletContainer = new HashMap<>();
}
// 서블릿 매핑 메소드
public void addMapping(String path, Servlet servlet) {
servletContainer.put(path, servlet);
// 필요한 경우 서블릿 초기화
// ServletConfig config = new ServletConfig(path);
// servlet.init(config);
logger.info("servlet Load. path : {}, servlet name : {}",path,servlet.getClass().getName());
}
// 서블릿 호출 메소드
public void service(String path, Request req, Response res) {
Servlet servlet = servletContainer.get(path);
if (servlet != null) {
servlet.service(req, res); // 매핑된 서블릿의 service 메소드 호출
} else {
// 매핑되지 않은 경로에 대한 처리
res.setStatus(404); // Not Found
}
}
}
원래는 xml파일에 서블릿들을 명시해놓고, xml File의 I/O작업을 통해 Servlet 매핑정보와 Servlet을 Load하여 생성해주지만, 아래의 어플리케이션 시작점인 Main()에 간단하게 2개의 커스텀 서블릿을 생성한뒤 Mapping 해주었다.
내가만든 WAS 프로그램이 실행되면 가장먼저 Servlet을 Load하여 Mapping한뒤 커넥터를 가져와 실행하는 것이다.
public class App {
private static ServletMapper servlet_Container;
public static void main(String[] args){
// Servlet Load
ServletMapper servlet_Container = new ServletMapper();
servlet_Container.addMapping("/custom",new CustomServlet());
servlet_Container.addMapping("/home",new HomeServlet());
// Connector Load
List<Connector> list = new ArrayList<Connector>();
Connector connector = new Connector();
connector.setServletContainer(servlet_Container);
list.add(connector);
Server server = new Server(list);
server.start();
}
}
그리고, 가장 중요한 서블릿 컨테이너는
private HashMap<String, Servlet> servletContainer;
형태로 Mapping 정보를 key, 해당 Servlet을 value로 보관해주었다.
추가로 mapping 하고싶다면, addMapping() 메소드를 통해 서블릿 컨테이너에 등록시켜주면 된다.
그리고 서블릿컨테이너의 service()를 통해 매핑된 서블릿을 가져와 HttpServlet에서 구현된 Service()를 실행시켜 준다.
이제 테스트를 해보자!
테스트 하기
가장먼저 Application을 실행시키면 서블릿을 로드하고, 포트에 커넥터가 생성되었다.
그리고, 브라우저에 /custom 요청을 보내면 custom 서블릿이 실행될것이다.
이번엔 /home 요청을 보내면
homeServlet이 실행되었다!!
이로써 기본적인 클라이언트의 요청을 받는 port를 생성하고, Http 메시지를 파싱한뒤 서블릿 컨테이너를 통해
클라이언트의 다양한 요청을 처리하는 기본적인 WAS의 기능을 모두 구현해주었다.
'Category > Project' 카테고리의 다른 글
JAVA TCP 소켓을 사용하여 HTTP통신이 가능한 WAS 직접 구현하기 -3 (0) | 2024.09.24 |
---|---|
JAVA TCP 소켓을 사용하여 HTTP통신이 가능한 WAS 직접 구현하기 -2 (0) | 2024.09.19 |
JAVA TCP 소켓을 사용하여 HTTP통신이 가능한 WAS 직접 구현하기 -1 (0) | 2024.09.12 |
[개인프로젝트] 15일차 - 배열 탐색 (0) | 2024.03.02 |
[개인프로젝트] 13일차 - 제이쿼리,자바스크립트 (0) | 2024.02.28 |