[스프링 핵심 원리와 활용] #3. 웹 서버와 서블릿 컨테이너 #806
Develop-KIM
started this conversation in
동환
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
웹 서버와 스프링 부트 소개
외장 서버 VS 내장 서버
전통적인 방식
과거에 자바로 웹 애플리케이션을 개발할 때는 먼저 서버에 톰캣 같은 WAS(웹 애플리케이션 서버)를 설치했다.
그리고 WAS에서 동작하도록 서블릿 스펙에 맞추어 코드를 작성하고 WAR 형식으로 빌드해서 war 파일을 만들었다.
이렇게 만들어진 war 파일을 WAS에 전달해서 배포하는 방식으로 전체 개발 주기가 동작했다.
이런 방식은 WAS 기반 위에서 개발하고 실행해야 한다. IDE 같은 개발 환경에서도 WAS와 연동해서 실행되도록 복잡한 추가 설정이 필요하다.
최근 방식
최근에는 스프링 부트가 내장 톰캣을 포함하고 있다. 애플리케이션 코드 안에 톰캣 같은 WAS가 라이브러리로 내장되어 있다는 뜻이다.
개발자는 코드를 작성하고 JAR로 빌드한 다음에 해당 JAR를 원하는 위치에서 실행하기만 하면 WAS도 함께 실행된다.
쉽게 이야기해서 개발자는
main()메서드만 실행하면 되고, WAS 설치나 IDE 같은 개발 환경에서 WAS와 연동하는 복잡한 일은 수행하지 않아도 된다.서블릿 등록
TestServlet 등록
/test로 요청이 오면 이 서블릿이 실행된다.TestServlet.service를 로그에 출력한다.test를 응답한다. 웹 브라우저로 요청하면 이 서블릿이 실행되고 화면에test가 출력되어야 한다.이 서블릿을 실행하려면 톰캣 같은 웹 애플리케이션 서버(WAS)에 이 코드를 배포해야 한다.
WAR 빌드와 배포
JAR, WAR 간단 소개
JAR 소개
자바는 여러 클래스와 리소스를 묶어서
JAR(Java Archive)라고 하는 압축 파일을 만들 수 있다.이 파일은 JVM 위에서 직접 실행되거나 또는 다른 곳에서 사용하는 라이브러리로 제공된다.
직접 실행하는 경우
main()메서드가 필요하고,MANIFEST.MF파일에 실행할 메인 메서드가 있는 클래스를 지정해두어야 한다.실행 예)
java -jar abc.jarJar는 쉽게 이야기해서 클래스와 관련 리소스를 압축한 단순한 파일이다. 필요한 경우 이 파일을 직접 실행할 수도 있고,
다른 곳에서 라이브러리로 사용할 수도 있다.
WAR 소개
WAR(Web Application Archive)라는 이름에서 알 수 있듯 WAR 파일은 웹 애플리케이션 서버(WAS)에 배포할 때 사용하는 파일이다.
JAR 파일이 JVM 위에서 실행된다면, WAR는 웹 애플리케이션 서버 위에서 실행된다.
웹 애플리케이션 서버 위에서 실행되고, HTML 같은 정적 리소스와 클래스 파일을 모두 함께 포함하기 때문에 JAR와 비교해서 구조가 더 복잡하다.
그리고 WAR 구조를 지켜야 한다.
WAR 구조
WEB-INFclasses: 실행 클래스 모음lib: 라이브러리 모음web.xml: 웹 서버 배치 설정 파일(생략 가능)index.html: 정적 리소스WEB-INF폴더 하위는 자바 클래스와 라이브러리, 그리고 설정 정보가 들어가는 곳WEB-INF를 제외한 나머지 영역은 HTML, CSS 같은 정적 리소스가 사용되는 영역WAR 배포
./shutdown.sh톰캣폴더/webapps하위를 모두 삭제한다.server-0.0.1-SNAPSHOT.war를 복사한다.톰캣폴더/webapps하위에 붙여넣는다.톰캣폴더/webapps/server-0.0.1-SNAPSHOT.war톰캣폴더/webapps/ROOT.war./startup.sh서블릿 컨테이너 초기화
여기에 스프링을 사용한다면 스프링 컨테이너를 만들고, 서블릿과 스프링을 연결하는 디스페처 서블릿도 등록해야 한다.
web.xml을 사용해서 초기화했지만, 지금은 서블릿 스펙에서 자바 코드를 사용한 초기화도 지원한다.서블릿 컨테이너와 스프링 컨테이너

서블릿 컨테이너 초기화 개발
서블릿은
ServletContainerInitializer라는 초기화 인터페이스를 제공한다. 이름 그대로 서블릿 컨테이너를 초기화 하는 기능을 제공한다.서블릿 컨테이너는 실행 시점에 초기화 메서드인
onStartup()을 호출해준다. 여기서 애플리케이션에 필요한 기능들을 초기화 하거나 등록할 수 있다.ServletContainerInitializer
Set<Class<?>> c: 조금 더 유연한 초기화를 기능을 제공한다.@HandlesTypes애노테이션과 함께사용한다.ServletContext ctx: 서블릿 컨테이너 자체의 기능을 제공한다. 이 객체를 통해 필터나 서블릿을 등록할 수 있다.hello/container/MyContainerInitV1실행 결과 로그
WAS를 실행할 때 해당 초기화 클래스가 실행된 것을 확인할 수 있다.
서블릿 컨테이너 초기화
서블릿을 등록하는 2가지 방법
@WebServlet애노테이션HelloServlet
HelloServlet.servicehello servlet!애플리케이션 초기화
서블릿 컨테이너는 조금 더 유연한 초기화 기능을 지원한다.
AppInit
예제 진행을 위해서 여기서는
AppInit인터페이스를 만들자.AppInitV1Servlet
HelloServlet서블릿을 서블릿 컨테이너에 직접 등록한다./hello-servlet를 호출하면HelloServlet서블릿이 실행된다.예시
/hello-servlet경로를 상황에 따라서 바꾸어 외부 설정을 읽어서 등록할 수 있다.if문으로 분기해서 등록하거나 뺄 수 있다.MyContainerInitV2
애플리케이션 초기화 과정
@HandlesTypes애노테이션에 애플리케이션 초기화 인터페이스를 지정한다.AppInit.class인터페이스를 지정했다.ServletContainerInitializer)는 파라미터로 넘어오는Set<Class<?>> c에 애플리케이션 초기화 인터페이스의 구현체들을 모두 찾아서 클래스 정보로 전달한다.@HandlesTypes(AppInit.class)를 지정했으므로AppInit.class의 구현체인AppInitV1Servlet.class정보가 전달된다.appInitClass.getDeclaredConstructor().newInstance()new AppInitV1Servlet()과 같다 생각하면 된다.appInit.onStartup(ctx)ctx함께 전달한다.MyContainerInitV2 등록
MyContainerInitV2
를 실행하려면 서블릿 컨테이너에게 알려주어야 한다. 설정을 추가하자.resources/META-INF/services/jakarta.servlet.ServletContainerInitializer`hello.container.MyContainerInitV2추가실행 로그
초기화는 다음 순서로 진행된다.
resources/META-INF/services/jakarta.servlet.ServletContainerInitializer@HandlesTypes(AppInit.class)편리함
서블릿 컨테이너를 초기화 하려면
ServletContainerInitializer인터페이스를 구현한 코드를 만들어야 한다.여기에 추가로
META-INF/services/jakarta.servlet.ServletContainerInitializer파일에 해당 코드를 직접 지정해주어야 한다.애플리케이션 초기화는 특정 인터페이스만 구현하면 된다.
의존성
애플리케이션 초기화는 서블릿 컨테이너에 상관없이 원하는 모양으로 인터페이스를 만들 수 있다.
이를 통해 애플리케이션 초기화 코드가 서블릿 컨테이너에 대한 의존을 줄일 수 있다.
특히
ServletContext ctx가 필요없는 애플리케이션 초기화 코드라면 의존을 완전히 제거할 수도 있다.스프링 컨테이너 등록
WAS와 스프링을 통합
앞서 배운 서블릿 컨테이너 초기화와 애플리케이션 초기화를 활용하면 된다.
필요한 과정
서블릿 컨테이너와 스프링 컨테이너

라이브러리 추가
build.gradle-spring-webmvc추가dependencies { //서블릿 implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' //스프링 MVC 추가 implementation 'org.springframework:spring-webmvc:6.0.4' }spring-webmvc라이브러리를 추가하면 스프링 MVC 뿐만 아니라spring-core를 포함한 스프링 핵심 라이브러리들도 함께 포함된다.HelloController
hello spring!이라는 메시지를 반환한다.HelloConfig
이제 애플리케이션 초기화를 사용해서 서블릿 컨테이너에 스프링 컨테이너를 생성하고 등록하자.
AppInitV2Spring
AppInitV2Spring는AppInit을 구현했다.AppInit을 구현하면 애플리케이션 초기화 코드가 자동으로 실행된다.앞서
MyContainerInitV2에 관련 작업을 이미 해두었다.스프링 컨테이너 생성
AnnotationConfigWebApplicationContext가 바로 스프링 컨테이너이다.AnnotationConfigWebApplicationContext부모를 따라가 보면ApplicationContext인터페이스를 확인할 수 있다.appContext.register(HelloConfig.class)스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결
new DispatcherServlet(appContext)이렇게 하면 디스패처 서블릿에 스프링 컨테이너가 연결된다.
디스패처 서블릿을 서블릿 컨테이너에 등록
servletContext.addServlet("dispatcherV2", dispatcher)/spring/*요청이 디스패처 서블릿을 통하도록 설정/spring/*이렇게 경로를 지정하면/spring과 그 하위 요청은 모두 해당 서블릿을 통하게 된다./spring/hello-spring/spring/hello/go실행 과정 정리
/spring/hello-spring실행을
/spring/*패턴으로 호출했기 때문에 다음과 같이 동작한다.dispatcherV2디스패처 서블릿이 실행된다. (/spring)dispatcherV2디스패처 서블릿은 스프링 컨트롤러를 찾아서 실행한다. (/hello-spring)/spring을 제외한/hello-spring가 매핑된 컨트롤러HelloController)의 메서드를 찾아서 실행한다.스프링 MVC 서블릿 컨테이너 초기화 지원
지금까지의 과정을 생각해보면 서블릿 컨테이너를 초기화 하기 위해 다음과 같은 복잡한 과정을 진행했다.
ServletContainerInitializer인터페이스를 구현해서 서블릿 컨테이너 초기화 코드를 만들었다.@HandlesTypes애노테이션을 적용했다./META-INF/services/jakarta.servlet.ServletContainerInitializer파일에 서블릿 컨테이너 초기화 클래스 경로를 등록했다.서블릿 컨테이너 초기화 과정은 상당히 번거롭고 반복되는 작업이다.
스프링 MVC는 이러한 서블릿 컨테이너 초기화 작업을 이미 만들어두었다. 덕분에 개발자는 서블릿 컨테이너 초기화 과정은 생략하고,
애플리케이션 초기화 코드만 작성하면 된다. 스프링이 지원하는 애플리케이션 초기화를 사용하려면 다음 인터페이스를 구현하면 된다.
WebApplicationInitializer
AppInitV3SpringMvc
WebApplicationInitializer인터페이스를 구현한 부분을 제외하고는 이전의AppInitV2Spring과 거의 같은 코드이다.WebApplicationInitializer는 스프링이 이미 만들어둔 애플리케이션 초기화 인터페이스이다.dispatcherV2라고 했고, 여기서는dispatcherV3라고 해주었다.참고로 이름이 같은 서블릿을 등록하면 오류가 발생한다.
servlet.addMapping("/")코드를 통해 모든 요청이 해당 서블릿을 타도록 했다./hello-spring이 매핑된 컨트롤러 메서드가 호출된다.현재 등록된 서블릿 다음과 같다.
/=dispatcherV3/spring/*=dispatcherV2/hello-servlet=helloServlet/test=TestServlet참고
여기서는 이해를 돕기 위해 디스패처 서블릿도 2개 만들고, 스프링 컨테이너도 2개 만들었다.
일반적으로는 스프링 컨테이너를 하나 만들고, 디스패처 서블릿도 하나만 만든다.
그리고 디스패처 서블릿의 경로 매핑도
/로 해서 하나의 디스패처 서블릿을 통해서 모든 것을 처리하도록 한다.스프링 MVC가 제공하는 서블릿 컨테이너 초기화 분석
스프링은 어떻게
WebApplicationInitializer스프링도 결국 서블릿 컨테이너에서 요구하는 부분을 모두 구현해야 한다.인터페이스 하나로 애플리케이션 초기화가 가능하게 할까?
spring-web라이브러리를 열어보면 서블릿 컨테이너 초기화를 위한 등록 파일을 확인할 수 있다. 그리고 이곳에 서블릿 컨테이너 초기화 클래스가 등록되어 있다./META-INF/services/jakarta.servlet.ServletContainerInitializerorg.springframework.web.SpringServletContainerInitializer코드를 확인해보자.SpringServletContainerInitializer
@HandlesTypes의 대상이WebApplicationInitializer이다. 그리고 이 인터페이스의 구현체를 생성하고 실행하는 것을 확인할 수 있다.정리
스프링MVC도 우리가 지금까지 한 것 처럼 서블릿 컨테이너 초기화 파일에 초기화 클래스를 등록해두었다.
그리고
WebApplicationInitializer인터페이스를 애플리케이션 초기화 인터페이스로 지정해두고, 이것을 생성해서 실행한다.따라서 스프링 MVC를 사용한다면
WebApplicationInitializer인터페이스만 구현하면AppInitV3SpringMvc에서 본 것 처럼 편리하게 애플리케이션 초기화를 사용할 수 있다.Beta Was this translation helpful? Give feedback.
All reactions