지난 포스트에서 서블릿이 뭔지 알아보았습니다. 이번 포스트에서는 서블릿 프로젝트를 생성하고 작성하는 방법에 대해서 알아보겠습니다.
서블릿이 동작할 때는 요청을 웹 서버가 받아서 웹 애플리케이션 서버(WAS)에 위임을하고, WAS가 서블릿을 호출해서 요청에 대한 작업을 처리한다고 했습니다.
이때 WAS로 가장 많이 사용되는 것들 중 하나가 톰캣 컨테이너(이하 톰캣)
입니다. 톰캣을 사용하기 위해서는 다운로드가 필요한데요. 아파치 톰캣 공식 페이지에서 다운로드 하실 수 있습니다.
왼쪽의 Download 메뉴를 보시면 Tomcat 10
이 있는데, 이 Tomcat 10에서 32-bit/64bit Windows Service Installer
를 다운받고 설치합니다.
기본 설정으로 진행하다가 아래와 같은 화면이 나타납니다.
여기서 HTTP 포트 번호는 8090
(또는 8080 써도 됨), 어드민 계정 유저 명과 비밀번호는 기억할만한 것으로 설정하고 넘겨줍니다.
그 다음 화면에서는 jdk의 위치를 경로를 지정해주고 next합니다.
그 다음엔 설치 경로인데요. 기본으로 사용해도 좋고, 원하는 위치로 바꿔도 좋습니다. 이 설정까지 마친 후 install
을 하시면 톰캣이 설치가 됩니다.
Windows 11, JAVA 17, Tomcat 10, IntelliJ 환경에서 실습이 진행되었습니다.
기존에는 gradle init, spring initailzr 또는 empty project
를 이용해서 프로젝트를 생성했었는데요. 이번엔 서블릿을 사용하기 위해서 조금 다른 방식으로 프로젝트를 생성해볼까 합니다.
New Project
를 선택하시고 Jakarta EE
를 선택합니다.
Jakarta EE
는 기존Java EE
가 오라클에서 이클립스 재단으로 이관됨에 따라 바뀐 명칭입니다. Java EE추가적으로
Java EE
는 자바로 서버측 개발을 하기 위한 플랫폼입니다. 기존에서는javax.
라는 패키지명을 사용했으나 Jakarta EE로 명칭이 바뀐 후 패키지명도jakarta.
로 변경되었습니다.
이후 프로젝트명(Name), 프로젝트 위치(Location), Group는 기본값 그대로 두고 Template, Application server, Language, Build system
은 사진과 동일하게 맞춰주세요. (Artifact는 프로젝트 명과 동일하게, JDK는 설치된 JDK 사용하시면 됩니다.)
이전에 톰캣을 설치했기 때문에
Application Server
에 톰캣이 자동으로 설정되어있을 수도 있는데 추후에 수동 설정법을 알아보기 위해서 여기서는 No인 상태로 설정합니다.
Next를 누르면 Dependencies 설정창이 나오는데요. 여기서 Jakarta EE 10, Servlet(6.0.0)
을 선택해주고 Create를 눌러서 프로젝트를 생성해줍니다.
그러면 프로젝트가 열리면서 3개의 기본 파일이 등장하게 됩니다.
pom.xml: Maven 구성 정보가 포함된 Project Object Model입니다. 프로젝트에 필요한 종속성과 플러그인에 대한 정보도 포함하고 있습니다.
HelloServlet.java: HttpServlet
을 상속받은 서블릿 자바 클래스입니다.
//HelloServlet.java package com.example.helloservlet; import java.io.*; import jakarta.servlet.http.*; import jakarta.servlet.annotation.*; @WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet { private String message; public void init() { message = "Hello World!"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html"); // Hello PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h1>" + message + "</h1>"); out.println("</body></html>"); } public void destroy() { } }
/hello-servlet
이라는 GET HTTP 요청을 받으면Hello World!
라는 문구를 웹 브라우저에 렌더링하는 코드입니다. 지난번에 본 생명주기 메소드인init(), doGet(), destroy()
의 모습도 보입니다.
Hello World!
라는 문구와 /hello-servlet
으로 이동하기 위한 링크가 있습니다.이제 이 서블릿 프로젝트에 위에서 설치한 톰캣 컨테이너를 애플리케이션 서버로 설정해보겠습니다. File -> Setting -> Build, Execution, Deployment -> Application Servers
에 들어간 후 +
를 클릭해서 위에서 설치한 톰캣을 연결해줍니다. Apply를 누르면 애플리케이션 서버로 톰캣이 연결이 됩니다.
이제 생성한 애플리케이션 서버로 run을 할 수 있도록 run을 구성해주어야합니다.인텔리제이 상단을 보시면 Current File
이라고 적힌 부분이 있습니다. 이곳을 클릭하면 Edit Configuration
마찬가지로 +
를 누릅니다. 구성 방법이 많은데 내리다 보면 Tomcat Sever
가 있고 여기서 Local
을 선택합니다. Remote
같은 경우에는 원격 컴퓨터에서 접속하기 위한 구성입니다.Local을 선택하고나면 여러가지 기본설정을 해주는데요. 여기서 바로 Apply하지 마시고, 아래있는 FIX
를 눌러주세요.그리고 프로젝트명:war exploded
를 선택해줍니다. 다시 Server 탭으로 돌아오면 아래 사진처럼 URL이 설정된 것을 볼 수 있습니다. 잘 설정되었다면 Apply 및 Ok를 통해 설정을 마쳐주세요.
진짜로 잘 설정되었는지 확인해봐야겠죠?다시 상단 메뉴에서 Run을 누르거나 Shift + F10
을 누릅니다. 그러면 서버 로그가 이렇게 착착착 뜨다가 서블릿 페이지가 하나 열리게 됩니다.전부 잘 작동한다면 서버 설정 및 run 설정까지 무사히 마친 것 입니다.
서버 종료시에는
Ctrl + F2
를 두 번 입력하거나, 정지 표시, 해골 표시를 눌러서 종료해주세요.
자 그러면 서블릿을 작성할 모든 준비를 마쳤으니 실제로 한 번 작성해보겠습니다. 프로젝트 파일에서 src\main\java\com\example\helloservlet
위치에 FirstServlet.class
를 하나 생성해줍니다. (클래스 이름은 자유!) 이 클래스의 내용은 다음과 같이 작성해주세요.
package com.example.helloservlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FirstServlet extends HttpServlet {
@Override
public void init() {
System.out.println("init() method called");
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("doGet() method called");
}
@Override
public void destroy() {
System.out.println("destroy() method called");
}
}
위 코드처럼 사용자 정의 서블릿은 HttpServlet
을 상속 받은 후 생명 주기 메소드들을 오버라이딩해서 사용하는 형태로 작성합니다.
브라우저에서 서블릿의 이름으로 요청을 하기 위해서는 패키지명을 포함한 서블릿 클래스 이름을 전부 적어서 요청을 해야합니다. 그런데 우리가 작성한 서블릿의 패키지 명까지 더한 이름은 com.example.helloservlet.FirstServlet
입니다. 이 이름을 URL에 넣어 요청하면 다음과 같은 긴 주소가 탄생합니다.
http://ip주소:포트번호/프로젝트명/com.example.helloservlet.FirstServlet
이렇게 URL을 사용한다면 입력하기도 힘들고 프로젝트명, 패키지, 클래스 이름이 URL에 그대로 노출이 되기 때문에 보안성이 좋다고 할 수도 없습니다. 따라서 현재는 서블릿 매핑
이라는 기능을 이용해서 별명을 붙인 후 해당 별명을 통해 요청을 하게 됩니다. URL도 다음과 같이 간단해지죠.
http://ip주소:포트번호/서블릿_매핑_이름
서블릿 매핑
을 수행하는 방법에는 .xml
을 이용하는 방식과 Annotaion을 이용하는 방식 두 가지 방식이 있습니다.
서블릿 매핑이 무엇이고 왜 사용하는지 이해하셨다면 서블릿 매핑을 해보겠습니다. 서블릿 매핑은 src\main\webapp\WEB-INF\web.xml
에서 설정합니다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- 여기에 매핑 내용 작성 -->
</web-app>
web.xml
의 <web-app>
태그 내부에 서블릿 매핑을 작성하게 되는데요. 먼저 매핑을 작성한 뒤 설명을 드리려고 합니다. 다음 코드대로 작성해주세요.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<servlet>
<servlet-name>firstmapping</servlet-name>
<servlet-class>com.example.helloservlet.FirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>firstmapping</servlet-name>
<url-pattern>/first</url-pattern>
</servlet-mapping>
</web-app>
<servlet>
: 요청하는 매핑 이름에 대해서 실제로 실행될 서블릿 클래스를 설정합니다.
<servlet-name>
: 서블릿 이름을 실제 서블릿 클래스에 연결합니다. 일종의 식별자와 같기 때문에 매핑하려는 <servlet-name>
과 반드시 동일하게 name을 붙여주어야합니다.
<servlet-mapping>
: 매핑 이름으로 요청하는 경우에 <servlet>
태그에서 <servlet-name>
이 동일한 태그와 연결됩니다.
<url-pattern>
: URL을 통해 요청하는 매핑 이름을 지정합니다. 프로젝트 명 뒤에 오는 패키지명-클래스이름을 대체하는 이름입니다. 반드시 /(슬래시)
로 시작해야합니다.
이렇게 설정하고 run
으로 톰캣 서버를 실행한 뒤 http://localhost:8090/HelloServlet_war_exploded/first
로 접속합니다. 그러면 빈화면이 뜨는데요. 당연히 화면에 렌더될 내용을 프로그래밍하지 않았으므로 빈 화면이 뜰 것이고, 서버 로그를 확인해야합니다. 우리가 작성했던 서블릿 코드대로 생명 주기에 따라 init()
과 요청 과정에서 doGet()
이 호출되었음을 볼 수 있습니다. (destory()
는 소멸 과정이 없어서 호출되지는 않았습니다.)
추가적으로 다수의 서블릿을 매핑할 때는
<servlet>, <servlet-mapping>
을 각각 분리해서 작성합니다.
다른 창을 하나 더 띄워서 http://localhost:8090/HelloServlet_war_exploded/first
로 접속하면 이전과는 다르게 init()
없이 doGet()
만이 호출됩니다.이는 서블릿이 동작할 때 처음 한 번 톰캣 서버의 메모리에 있는 지 확인하고 메모리에 해당 서블릿이 없다면 init()
을 수행합니다. 그래서 두 번 째 접속하는 경우에 톰캣 메모리에 이미 서블릿이 등록되어있기 때문에 init()
호출 없이 바로 doGet()
이 호출된 것 입니다.
만약 첫 번째 수행 후 destroy()
를 호출해서 메모리에서 제거를 했다면, 다시 init()
이 호출되었을 것 입니다.
이 결과로 인해 우리는 서블릿이 메모리에 등록된 서블릿을 재사용해서 효율 좋은 동작을 함을 알 수 있습니다.
Maven이 입지를 잃어가는 이유 중 하나는 xml의 작성 방법이 복잡하고 가독성이 떨어지기 때문입니다. 마찬가지로 xml을 이용한 매핑 방법은 작성법이 까다롭기도 하면서 여러 서블릿을 매핑하는 경우 가독성이 떨어진다는 문제가 발생하기도 합니다.
그래서 Tomcat 7 이후로는 어노테이션을 사용해서 매핑을 할 수 있도록 지원하고 있습니다.
이전에 했던 xml의 서블릿 매핑은 삭제하거나
주석 처리 <!-- -->
를 해주세요.
서블릿 매핑을 하고자하는 클래스 위에 @WebSevlet("/매핑이름")
어노테이션을 붙이기만 하면 끝입니다. 정말 간단하죠?
package com.example.helloservlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/first") //어노테이션 서블릿 매핑
public class FirstServlet extends HttpServlet {
@Override
public void init() {
System.out.println("init() method called");
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("doGet() method called");
}
@Override
public void destroy() {
System.out.println("destroy() method called");
}
}
마찬가지로 톰캣 서버를 실행하고 http://localhost:8090/HelloServlet_war_exploded/first
로 접속하면 xml 서블릿 매핑과 동일한 결과를 얻을 수 있습니다.
추가적으로, 서블릿 매핑에 사용된 이름은 중복해서 사용할 수 없음에 주의해주세요.
서블릿 포스팅이 계속 된다면, 앞으로는 가독성도 좋고 작성하기 편한 어노테이션을 이용해서 서블릿 매핑을 할 예정입니다.