** 해당 강의는 인프런 김영한 님의 스프링 부트 - 핵심 원리를 시청하고 작성한 게시글입니다.
지난 벨로그 포스팅 때에는 서블릿 컨테이너 초기화 과정에 대하여 알아보았다. 이제 애플리케이션 초기화 과정을 알아보자. 무슨 말인지 이해가 잘 안 되고, 과정이 복잡하더라도 난 꼭 알아야겠다. 이해해야겠다!
애플리케이션 초기화 과정을 알아보자.
1. 먼저 HttpServlet을 상속받는 HelloServlet 클래스를 만들어 준다.
package hello.servlet;
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 HelloServlet extends HttpServlet {
//서블릿 초기화
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("helloServlet.service");
resp.getWriter().println("hello servlet!");
}
}
2. AppInit 인터페이스를 생성한다.
package hello.container;
import jakarta.servlet.ServletContext;
public interface AppInit {
//appInit이 하는 역할?
//애플리케이션 초기화를 진행하려면 먼저 인터페이스를 만들어야 한다.
void onStartup(ServletContext servletContext);
//ServletContext 그 자체. 필터 등록 등도 가능하다.
}
3. AppInit을 구현한 AppInitV1Servlet 클래스를 생성한다.
package hello.container;
import hello.servlet.HelloServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
public class AppInitV1Servlet implements AppInit{
//인터페이스를 다시 구현해 준다.
@Override
public void onStartup(ServletContext servletContext) {
System.out.println("AppInitV1Servlet.onStartup");
//순수 서블릿 코드 등록
ServletRegistration.Dynamic helloServlet =
servletContext.addServlet("helloServlet", new HelloServlet());
//이 부분에서 맵핑해 줄 수 있음
helloServlet.addMapping("/hello-servlet");
}
}
4. ServletContainerInitializer 를 구현한 MyContainerInitV2 클래스를 생성한다.
package hello.container;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HandlesTypes;
import java.util.Objects;
import java.util.Set;
@HandlesTypes(AppInit.class)
public class MyContainerInitV2 implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
System.out.println("MyContainerInitV2.onStartup");
System.out.println("MyContainerInitV2 c =" + c);
System.out.println("MyContainerInitV2 ctx =" + ctx);
}
}
5. 그럼 이제 서블릿을 등록 후 여기까지의 값을 출력해 보자!
해당 url을 호출했을 때,
콘솔에서 이런 값을 찍고 있다. c에는 값이 잘 넘어왔고, 즉, 초기화는 되었다는 말인데 AppInitV1Servlet을 찍고 있다. AppInitV1Servlet은 우리가 초반에 인터페이스 AppInit을 구현해 만든 클래스이다.
6. for문을 돌려, 저 안에 있는 것들을 끄집어내 보자! (AppInit을 구현한 클래스가 여러개일 수 있으므로)
package hello.container;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HandlesTypes;
import java.util.Objects;
import java.util.Set;
@HandlesTypes(AppInit.class)
public class MyContainerInitV2 implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
System.out.println("MyContainerInitV2.onStartup");
System.out.println("MyContainerInitV2 c =" + c);
System.out.println("MyContainerInitV2 ctx =" + ctx);
//class hello.container.AppInitV1Servlet
for (Class<?> appInitClass : c) {
try {
//new AppInitV1Servlet() 과 같은 코드
AppInit appInit = (AppInit) appInitClass.getDeclaredConstructor().newInstance();
appInit.onStartup(ctx);
} catch (Exception e){
throw new RuntimeException(e);
}
}
}
}
다시 애플리케이션 초기화 과정을 정리해 보자.
1. @HandlesTypes 어노테이션에 애플리케이션 초기화 인터페이스를 지정한다.
2. 서블릿 컨테이너 초기화 (ServletContainerInitializer)는 파라미터로 넘어오는 'Set<Class<?>> c'에 애플리케이션 초기화 인터페이스의 구현체들을 모두 찾아서 클래스 정보로 전달한다.
3. 'appInitClass.getDeclaredConstructor().newInstance()' 를 사용해 객체를 생성한다. 참고로 이 코드는 new AppInitV1Servlet()과 같다고 생각하면 된다.
4. 'appInit.onStartup(ctx)' 애플리케이션 초기화 코드를 직접 실행하면서 서블릿 컨테이너 정보가 담긴 ctx도 함께 전달한다.
그럼 또 하나의 의문점이 생긴다. 서블릿 컨테이너 초기화만 있어도 될 것 같은데, 왜 애플리케이션 초기화라는 개념까지 만들었을까?