웹 서버와 서블릿 컨테이너
웹 서버와 스프링 부트 소개
- 웹 서버 전통적인 방식 vs 최근 방식
- 전통적인 방식 - 서버에 WAS를 설치하고 WAS에서 동작하도록 서블릿 스펙에 맞춰 코드 작성 -> WAR 형식으로 빌드 -> war 파일을 WAS에 전달해서 배포
- WAS 기반위에 개발하고 실행해야 하며 IDE 개발 환경에서도 WAS와 연동해야 하는 추가 설정이 필요하다.
- 최근 방식 - 스프링 부트가 내장 톰캣(WAS)을 포함 -> 코드 작성 -> JAR 빌드 후 실행하면 WAS도 함께 실행
- WAS 설치, IDE 개발 환경에서 WAS와 연동하는 일을 수행하지 않아도 된다.
- 전통적인 방식 - 서버에 WAS를 설치하고 WAS에서 동작하도록 서블릿 스펙에 맞춰 코드 작성 -> WAR 형식으로 빌드 -> war 파일을 WAS에 전달해서 배포
톰캣 설치
- 윈도우 사용자 톰캣 실행 / 종료
- 톰캣폴더/bin 폴더로 이동
- 실행 : startup.bat
- 종료 : shutdown.bat
- 톰캣 실행 로그 위치
- 톰캣폴더/logs/catalina.out
프로젝트 설정
<html>
<body>index html</body>
</html>
- 홈 화면 생성
- 홈 화면은 /src/main/webapp/index.html 위치에 만들어야 한다.
/**
* http://localhost:8080/test
*/
@WebServlet(urlPatterns = "/test")
public class TestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("TestServlet.service");
resp.getWriter().println("test");
}
}
- 서블릿 예제
- URL : http://localhost:8080/test로 요청이 오면 해당 서블릿이 실행된다.
- 요청이 오면 TestServlet.service를 출력하고 test를 응답하는 서블릿이다.
- 서블릿을 실행하기 위해서 톰캣같은 WAS에 해당 코드를 배포해야 한다.
WAR 빌드와 배포
- 프로젝트 빌드
- 프로젝트 폴더로 이동
- gradlew build 명령어
- build/libs/ ~SNAPSHOT.war 파일이 생성된다.
- JAR : 자바에서 여러 클래스와 리소스를 묶어서 만든 JAR(Java Archive)라는 압축한 파일이다.
- JVM 위에서 직접 실행되거나 다른 곳에서 사용하는 라이브러리로 제공된다.
- 직접 실행하는 경우 main() 메소드가 필요하고 MANIFEST.MF 파일에 실행할 메인 메소드가 있는 클래스를 지정해둬야 한다.
- ex) java -jar abc.jar
- WAR(Web Application Archive) : WAS에 배포할 때 사용하는 파일이다.
- WAR는 웹 애플리케이션 서버(WAS) 위에서 실행된다.
- WAS 위에서 실행되고 HTML 같은 정적 리소스와 클래스 파일 모두 포함하기에 JAR보다 구조가 복잡하다.
- WAR 구조
- WEB-INF
- classes : 실행 클래스 모음
- lib : 라이브러리 모음
- web.xml : 웹 서버 배치 설정 파일 (생략 가능)
- index.html : 정적 리소스
- WEB-INF
- WEB-INF 하위는 자바 클래스, 라이브러리, 설정 정보가 들어가는 곳이고 나머지 영역은 HTML, CSS 같은 정적 리소스가 사용되는 영역이다.
- WAR 배포
- 톰캣 서버 종료 => shutdown.sh
- 톰캣폴더/webapps 하위 파일 모두 삭제
- 기존에 있는 파일들은 임의로 들어있는 파일이기에 삭제해도 무관
- 빌드된 ~.war 파일을 복사해서 톰캣폴더/webapps 하위에 붙여넣기
- war 파일 이름을 ROOT.war로 변경
- ROOT는 대문자로 해야한다.
- 톰캣 서버 실행 => startup.sh
톰캣 설정 - IntelliJ 유료버전
- IntelliJ 유료버전 톰캣 설정 방법
- 상단바 메뉴 -> Run -> Run... -> Edit Configurations
- 왼쪽 상단 + 버튼 클릭 후 Tomcat Server -> Local 선택
- Configure.. -> 톰캣이 설치된 폴더 선택
- Deployment 메뉴 -> + 버튼 클릭 후 exploded로 끝나는 war 파일 선택
- 하단 Application context 박스 안의 내용 모두 제거
- 설정한 톰캣 선택 후 실행
서블릿 컨테이너 초기화1
- WAS를 실행하는 시점에 필요한 초기화 작업들이 있다.
- 서비스에 필요한 필터, 서블릿을 등록
- 스프링을 사용하는 경우 스프링 컨테이너를 만들고 서블릿과 연결하는 디스패처 서블릿을 등록
- WAS가 제공하는 초기화 기능을 사용하려면 WAS 실행 시점에 초기화 과정을 진행할 수 있다.
- 과거에는 web.xml을 사용해서 초기화했지만 서블릿 스펙에서 자바 코드를 사용한 초기화도 지원한다.
- 서블릿 컨테이너 초기화
- 서블릿은 ServletContainerInitializer라는 초기화 인터페이스를 제공한다.
- 서블릿 컨테이너는 실행 시점에 초기화 메소드인 onStartup()을 호출해준다.
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
- ServletContainerInitializer 인터페이스
- Set<Class<?>> c : 조금 더 유연한 초기화 기능을 제공한다. @HandlesTypes 어노테이션과 함께 사용한다.
- ServletContext ctx : 서블릿 컨테이너 자체의 기능을 제공, 해당 객체를 통해 필터나 서블릿을 등록할 수 있다.
public class MyContainerInitV1 implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
System.out.println("MyContainerInitV1.onStartup");
System.out.println("MyContainerInitV1 c = " + c);
System.out.println("MyContainerInitV1 ctx = " + ctx);
}
}
- 서블릿 컨테이너 초기화 인터페이스 구현 예제V1
hello.container.MyContainerInitV1
- WAS에게 실행할 초기화 클래스를 알려주는 작업
- resources/META-INF/services/jakarta.servlet.ServletContainerInitializer에 위의 내용 작성
- 내용에는 서블릿 컨테이너 초기화 인터페이스를 구현한 클래스의 패키지 경로를 포함해서 적어준다.
- WAS는 서버를 실행할 때 해당 내용을 기반으로 ServletContainerInitializer를 구현한 MyContainerInitV1 클래스의 onStartup() 메소드를 호출한다.
- resources/META-INF/services/jakarta.servlet.ServletContainerInitializer에 위의 내용 작성
서블릿 컨테이너 초기화2
- 서블릿을 등록하는 2가지 방법
- @WebServlet 어노테이션 사용
- 프로그래밍 방식
- 프로그래밍 방식으로 서블릿을 등록하는 이유
- @WebServlet 어노테이션을 사용하면 서블릿을 편리하게 등록할 수 있지만 하드코딩과 같아서 유연하게 변경하는 것이 어렵다.
- 프로그래밍 방식은 if문으로 분기하거나 상황에 따라 외부 설정을 읽어서 등록하거나, 생성자로부터 정보를 넘겨주는 등 유연성을 제공한다.
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("HelloServlet.service");
resp.getWriter().println("hello servlet!");
}
}
- 서블릿 등록
public interface AppInit {
void onStartup(ServletContext servletContext);
}
- 애플리케이션 초기화를 위한 인터페이스 예제
- 애플리케이션 초기화를 위해 내용과 형식은 상관없으며 인터페이스는 꼭 필요하다.
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");
}
}
- 프로그래밍 방식으로 서블릿을 서블릿 컨테이너에 직접 등록 예제
- 직접 개발한 애플리케이션 초기화 인터페이스인 AppInit을 구현
- HTTP로 /hello-servlet을 호출하면 HelloServlet 서블릿이 실행된다.
@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);
for (Class<?> appInitClass : c) {
try {
//new AppInitV1Servlet()과 같은 코드
AppInit appInit = (AppInit) appInitClass.getDeclaredConstructor().newInstance();
appInit.onStartup(ctx);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
- 서블릿 컨테이너 초기화 인터페이스 구현 예제V2
- 서블릿 컨테이너 초기화가 먼저 일어나고 애플리케이션 초기화 코드를 엮어서 호출해준다.
- 애플리케이션 초기화 과정
- @HandlesTypes 어노테이션에 애플리케이션 초기화 인터페이스를 지정한다.
- 이전에 만든 애플리케이션 초기화 인터페이스인 AppInit.class를 지정
- 서블릿 컨테이너 초기화는 파라미터로 넘어오는 Set<Class<?>> c에 애플리케이션 초기화 인터페이스의 구현체들을 모두 찾아서 클래스 정보로 전달한다.
- AppInit 인터페이스의 구현체인 AppInitV1Servlet.class 정보가 전달된다.
- 객체가 아닌 클래스 정보를 전달하기 때문에 실행하기 위해서 객체를 생성해서 사용해야 한다.
- appInitClass.getDeclaredConstructor().newInstance()
- 리플렉션을 사용해서 객체를 생성한다.
- 예제에서는 new AppInitV1Servlet() 코드와 동일하다
- appInit.onStartup(ctx)
- 애플리케이션 초기화 코드를 직접 실행하면서 서블릿 컨테이너 정보가 담긴 ctx도 함께 전달한다.
- @HandlesTypes 어노테이션에 애플리케이션 초기화 인터페이스를 지정한다.
- V1과 마찬가지로 서블릿 컨테이너에게 서블릿 컨테이너 초기화 인터페이스 구현체를 알려줘야 한다.
- resources/META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 패키지를 포함한 V2 작성
- 초기화 순서
- 서블릿 컨테이너 초기화 실행
- resources/META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 적힌 클래스를 실행
- 애플리케이션 초기화 실행
- @HandlesTypes가 있으면 지정한 인터페이스의 구현체를 다 찾아와서 서블릿 컨테이너에게 정보를 넘겨준다.
- 서블릿 컨테이너 초기화 실행
- 서블릿 컨테이너 초기화와 애플리케이션 초기화를 분리하는 이유
- 편리성 - 컨테이너 초기화는 ServletContainerInitializer 인터페이스를 구현해야하고 resources/META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 직접 등록해줘야하지만 애플리케이션 초기화는 특정 인터페이스만 구현하면 된다.
- 의존성 - 애플리케이션 초기화는 서블릿 컨테이너에 상관없이 의존되지 않도록 원하는 모양으로 인터페이스를 만들 수 있다.
서블릿 컨테이너 등록
- WAS와 스프링 통합하는 방법
- 스프링 컨테이너 만들기
- 스프링 MVC 컨트롤러를 스프링 컨테이너에 빈으로 등록하기
- 스프링MVC를 사용하는데 필요한 디스패처 서블릿을 서블릿 컨테이너에 등록하기
@RestController
public class HelloController {
@GetMapping("/hello-spring")
public String hello() {
System.out.println("HelloController.hello");
return "hello Spring";
}
}
@Configuration
public class HelloConfig {
@Bean
public HelloController helloController() {
return new HelloController();
}
}
- 스프링 MVC 컨트롤러 & Config 설정 클래스
public class AppInitV2Spring implements AppInit {
@Override
public void onStartup(ServletContext servletContext) {
System.out.println("AppInitV2Spring.onStartup");
//스프링 컨테이너 생성
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(HelloConfig.class);
//스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결
DispatcherServlet dispatcher = new DispatcherServlet(appContext);
//디스패처 서블릿을 서블릿 컨테이너에 등록
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcherV2", dispatcher);
// /spring/* 요청이 디스패처 서블릿을 통하도록 설정
servlet.addMapping("/spring/*");
}
}
- 애플리케이션 초기화 예제
- 이전에 만들어놓았던 AppInit 인터페이스를 구현함으로 애플리케이션 초기화 코드가 자동으로 실행된다.
- 스프링 컨테이너 생성
- AnnotationConfigWebApplicationContext : 어노테이션 기반 설정과 웹 기능을 지원하는 스프링 컨테이너
- appContext.register(HelloConfig.class) : HelloConfig를 기반으로 컨테이너에 설정을 추가한다.
- 스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결
- 디스패처 서블릿(Dispatcher Servlet) : 클라이언트 요청을 적절한 컨트롤러로 라우팅하고 처리된 결과를 뷰에 전달하여 사용자에게 응답을 생성하는 역할을 하는 것
- new DispatcherServlet(appContext) : 스프링 MVC가 제공하는 디스패처 서블릿을 생성하고 생성자에 스프링 컨테이너를 넣어줌으로 디스패처 서블릿과 스프링 컨테이너를 연결한다.
- 해당 디스패처 서블릿에 HTTP 요청이 오면 디스패처 서블릿은 연결된 스프링 컨테이너에 등록된 컨트롤러 빈들을 호출한다.
- 디스패처 서블릿을 서블릿 컨테이너에 등록
- servletContext.addServlet("dispatcherV2",dispatcher) : 디스패처 서블릿을 서블릿 컨테이너에 등록한다.
- 예제에서는 /spring/* 요청이 오면 디스패처 서블릿을 통하도록 설정했다.
- ex) /spring, /spring/hello-spring 등...
- 서블릿 등록 시 이름이 중복되는 경우 오류 발생
- 예제에서는 /spring/* 요청이 오면 디스패처 서블릿을 통하도록 설정했다.
- servletContext.addServlet("dispatcherV2",dispatcher) : 디스패처 서블릿을 서블릿 컨테이너에 등록한다.
- /spring/hello-spring 실행 과정
- /spring/* 패턴이기에 등록해놓은 dispatcherV2 디스패처 서블릿이 실행된다.
- dispatcherV2 디스패처 서블릿은 스프링 컨트롤러를 찾아서 실행한다.
- 서블릿을 찾아서 호출하는데 사용된 /spring을 제외한 /hello-spring이 매핑된 컨트롤러 HelloController의 메소드를 찾아서 실행한다.
스프링 MVC 서블릿 컨테이너 초기화 지원
- 기존 서블릿 컨테이너 초기화 과정
- ServletContainerInitializer 인터페이스 구현, 서블릿 컨테이너 초기화 코드 작성
- 애플리케이션 초기화를 위해 @HandlesTypes 어노테이션 적용
- /META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 서블릿 컨테이너 초기화 클래스(구현 클래스) 경로 등록
- 스프링 MVC에서 서블릿 컨테이너 초기화 작업을 제공해준다.
- 서블릿 컨테이너 초기화 작업은 생략하고 애플리케이션 초기화 코드만 작성하면 된다.
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
public class AppInitV3SpringMvc implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("AppInitV3SpringMvc.onStartup");
//스프링 컨테이너 생성
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(HelloConfig.class);
//스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결
DispatcherServlet dispatcher = new DispatcherServlet(appContext);
//디스패처 서블릿을 서블릿 컨테이너에 등록
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcherV3", dispatcher);
// 모든 요청이 디스패처 서블릿을 통하도록 설정
servlet.addMapping("/");
}
}
- 애플리케이션 초기화 예제
- V2와 같이 스프링 컨테이너 생성, 디스패처 서블릿과 연결, 서블릿 컨테이너에 등록을 했다.
- 디스패처 서블릿을 서블릿 컨테이너에 등록할 때 V2와 이름이 중복되지 않도록 해야한다.
- URL 매핑을 /로 함으로 모든 url 요청 시 dispatcherV3 디스패처 서블릿으로 통하도록 설정
- 예제에서 V2는 /spring/* 이고 V3는 /* 인 상황인데 /spring/*이 오면 더 구체적인 V2 디스패처 서블릿이 호출된다.
- V2와 같이 스프링 컨테이너 생성, 디스패처 서블릿과 연결, 서블릿 컨테이너에 등록을 했다.
- 일반적으로 스프링 컨테이너 하나, 디스패처 서블릿도 하나만 만들며 디스패처 서블릿의 경로도 /로 설정하여 하나의 디스패처 서블릿을 통해 모든 것을 처리하도록 한다.
- 스프링 MVC에서는 WebApplicationInitializer 인터페이스를 애플리케이션 초기화 인터페이스로 지정해두고 사용하기에 WebApplicationInitializer만 구현하면 편하게 애플리케이션 초기화를 할 수 있다.
정리
- WAR(Web Application Archive) : WAS에 배포할 때 사용하는 파일이다.
- WAR는 웹 애플리케이션 서버(WAS) 위에서 실행된다.
- WAS 위에서 실행되고 HTML 같은 정적 리소스와 클래스 파일 모두 포함하기에 JAR보다 구조가 복잡하다.
- JAR : 자바에서 여러 클래스와 리소스를 묶어서 만든 JAR(Java Archive)라는 압축한 파일이다.
- JVM 위에서 직접 실행되거나 다른 곳에서 사용하는 라이브러리로 제공된다.
- 직접 실행하는 경우 main() 메소드가 필요하고 MANIFEST.MF 파일에 실행할 메인 메소드가 있는 클래스를 지정해둬야 한다.
- 서블릿을 등록하는 2가지 방법
- @WebServlet 어노테이션 사용
- 프로그래밍 방식
- 디스패처 서블릿(Dispatcher Servlet) : 클라이언트 요청을 적절한 컨트롤러로 라우팅하고 처리된 결과를 뷰에 전달하여 사용자에게 응답을 생성하는 역할을 하는 것
- 서블릿 컨테이너 초기화가 먼저 일어나고 애플리케이션 초기화 코드를 엮어서 호출해준다.
- 서블릿 컨테이너 초기화 실행
- resources/META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 적힌 클래스를 실행
- 애플리케이션 초기화 실행
- @HandlesTypes가 있으면 지정한 인터페이스의 구현체를 다 찾아와서 서블릿 컨테이너에게 정보를 넘겨준다.
- 서블릿 컨테이너 초기화 실행
- 기존 서블릿 컨테이너 초기화 과정
- ServletContainerInitializer 인터페이스 구현, 서블릿 컨테이너 초기화 코드 작성
- /META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 서블릿 컨테이너 초기화 클래스(구현 클래스) 경로 등록
- 애플리케이션 초기화를 위해 @HandlesTypes 어노테이션으로 애플리케이션 초기화 인터페이스 지정
- 애플리케이션 초기화 인터페이스 구현
- ServletContainerInitializer 인터페이스 구현, 서블릿 컨테이너 초기화 코드 작성
- 스프링 MVC에서는 WebApplicationInitializer 인터페이스를 애플리케이션 초기화 인터페이스로 지정해두고 사용하기에 WebApplicationInitializer만 구현하면 편하게 애플리케이션 초기화를 할 수 있다.
- ServletContainerInitializer 인터페이스 구현, 서블릿 컨테이너 초기화 코드 작성, 경로 등록 등과 같은 별도의 서블릿 컨테이너 초기화 과정을 거치지 않아도 된다.
출처 : [인프런 김영한 스프링 부트 - 핵심 원리와 활용]
스프링 부트 - 핵심 원리와 활용 강의 | 김영한 - 인프런
김영한 | 실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., 백엔드 개발자를 위한 스프링 부트 끝판왕! 실무에 필요한 내용을 모두 담았습니다. [임베딩 영상] 김영한의 스
www.inflearn.com
'Spring > [인프런 김영한 스프링 부트 - 핵심 원리와 활용]' 카테고리의 다른 글
[인프런 김영한 스프링 부트 - 핵심 원리와 활용] 외부설정과 프로필1 (2) | 2025.01.22 |
---|---|
[인프런 김영한 스프링 부트 - 핵심 원리와 활용] 자동 구성 (Auto Configuration) (3) | 2025.01.01 |
[인프런 김영한 스프링 부트 - 핵심 원리와 활용] 스프링 부트 스타터와 라이브러리 관리 (0) | 2024.12.30 |
[인프런 김영한 스프링 부트 - 핵심 원리와 활용] 스프링 부트와 내장 톰캣 (1) | 2024.12.30 |
[인프런 김영한 스프링 부트 - 핵심 원리와 활용] 스프링 부트 소개 (1) | 2024.12.28 |