Spring/[인프런 김영한 스프링 부트 - 핵심 원리와 활용]

[인프런 김영한 스프링 부트 - 핵심 원리와 활용] 웹 서버와 서블릿 컨테이너

h2boom 2024. 12. 29. 16:23

웹 서버와 서블릿 컨테이너

웹 서버와 스프링 부트 소개

  • 웹 서버 전통적인 방식 vs 최근 방식
    • 전통적인 방식 - 서버에 WAS를 설치하고 WAS에서 동작하도록 서블릿 스펙에 맞춰 코드 작성 -> WAR 형식으로 빌드 -> war 파일을 WAS에 전달해서 배포
      • WAS 기반위에 개발하고 실행해야 하며 IDE 개발 환경에서도 WAS와 연동해야 하는 추가 설정이 필요하다.
    • 최근 방식 - 스프링 부트가 내장 톰캣(WAS)을 포함 -> 코드 작성 -> JAR 빌드 후 실행하면 WAS도 함께 실행
      • WAS 설치, IDE 개발 환경에서 WAS와 연동하는 일을 수행하지 않아도 된다.


톰캣 설치

  • 윈도우 사용자 톰캣 실행 / 종료
    1. 톰캣폴더/bin 폴더로 이동
    2. 실행 : startup.bat
    3. 종료 : 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 빌드와 배포

  • 프로젝트 빌드
    1. 프로젝트 폴더로 이동
    2. gradlew build 명령어
    3. 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 하위는 자바 클래스, 라이브러리, 설정 정보가 들어가는 곳이고 나머지 영역은 HTML, CSS 같은 정적 리소스가 사용되는 영역이다.

 

  • WAR 배포
    1. 톰캣 서버 종료 => shutdown.sh
    2. 톰캣폴더/webapps 하위 파일 모두 삭제
      • 기존에 있는 파일들은 임의로 들어있는 파일이기에 삭제해도 무관
    3. 빌드된 ~.war 파일을 복사해서 톰캣폴더/webapps 하위에 붙여넣기
    4. war 파일 이름을 ROOT.war로 변경
      • ROOT는 대문자로 해야한다.
    5. 톰캣 서버 실행 => startup.sh

톰캣 설정 - IntelliJ 유료버전

  • IntelliJ 유료버전 톰캣 설정 방법
    1. 상단바 메뉴 -> Run -> Run... -> Edit Configurations
    2. 왼쪽 상단 + 버튼 클릭 후 Tomcat Server -> Local 선택
    3. Configure.. -> 톰캣이 설치된 폴더 선택
    4. Deployment 메뉴 -> + 버튼 클릭 후 exploded로 끝나는 war 파일 선택
    5. 하단 Application context 박스 안의 내용 모두 제거
    6. 설정한 톰캣 선택 후 실행

 

서블릿 컨테이너 초기화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() 메소드를 호출한다.


서블릿 컨테이너 초기화2

  • 서블릿을 등록하는 2가지 방법
    1. @WebServlet 어노테이션 사용
    2. 프로그래밍 방식
  • 프로그래밍 방식으로 서블릿을 등록하는 이유
    • @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
    • 서블릿 컨테이너 초기화가 먼저 일어나고 애플리케이션 초기화 코드를 엮어서 호출해준다. 
    • 애플리케이션 초기화 과정
      1. @HandlesTypes 어노테이션에 애플리케이션 초기화 인터페이스를 지정한다.
        • 이전에 만든 애플리케이션 초기화 인터페이스인 AppInit.class를 지정
      2. 서블릿 컨테이너 초기화는 파라미터로 넘어오는 Set<Class<?>> c에 애플리케이션 초기화 인터페이스의 구현체들을 모두 찾아서 클래스 정보로 전달한다.
        • AppInit 인터페이스의 구현체인 AppInitV1Servlet.class 정보가 전달된다.
        • 객체가 아닌 클래스 정보를 전달하기 때문에 실행하기 위해서 객체를 생성해서 사용해야 한다.
      3. appInitClass.getDeclaredConstructor().newInstance()
        • 리플렉션을 사용해서 객체를 생성한다.
        • 예제에서는 new AppInitV1Servlet() 코드와 동일하다
      4. appInit.onStartup(ctx)
        • 애플리케이션 초기화 코드를 직접 실행하면서 서블릿 컨테이너 정보가 담긴 ctx도 함께 전달한다.
  • 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/hello-spring 실행 과정
      1. /spring/* 패턴이기에 등록해놓은 dispatcherV2 디스패처 서블릿이 실행된다.
      2. dispatcherV2 디스패처 서블릿은 스프링 컨트롤러를 찾아서 실행한다.
      3. 서블릿을 찾아서 호출하는데 사용된 /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 디스패처 서블릿이 호출된다.

  • 일반적으로 스프링 컨테이너 하나, 디스패처 서블릿도 하나만 만들며 디스패처 서블릿의 경로도 /로 설정하여 하나의 디스패처 서블릿을 통해 모든 것을 처리하도록 한다.

 

  • 스프링 MVC에서는 WebApplicationInitializer 인터페이스를 애플리케이션 초기화 인터페이스로 지정해두고 사용하기에 WebApplicationInitializer만 구현하면 편하게 애플리케이션 초기화를 할 수 있다.

정리

  • WAR(Web Application Archive) : WAS에 배포할 때 사용하는 파일이다.
    • WAR는 웹 애플리케이션 서버(WAS) 위에서 실행된다.
    • WAS 위에서 실행되고 HTML 같은 정적 리소스와 클래스 파일 모두 포함하기에 JAR보다 구조가 복잡하다.
  • JAR : 자바에서 여러 클래스와 리소스를 묶어서 만든 JAR(Java Archive)라는 압축한 파일이다.
    • JVM 위에서 직접 실행되거나 다른 곳에서 사용하는 라이브러리로 제공된다.
    • 직접 실행하는 경우 main() 메소드가 필요하고 MANIFEST.MF 파일에 실행할 메인 메소드가 있는 클래스를 지정해둬야 한다.

 

  • 서블릿을 등록하는 2가지 방법
    1. @WebServlet 어노테이션 사용
    2. 프로그래밍 방식

 

  • 디스패처 서블릿(Dispatcher Servlet) : 클라이언트 요청을 적절한 컨트롤러로 라우팅하고 처리된 결과를 뷰에 전달하여 사용자에게 응답을 생성하는 역할을 하는 것

 

  • 서블릿 컨테이너 초기화가 먼저 일어나고 애플리케이션 초기화 코드를 엮어서 호출해준다. 
    • 서블릿 컨테이너 초기화 실행
      • resources/META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 적힌 클래스를 실행
    • 애플리케이션 초기화 실행
      • @HandlesTypes가 있으면 지정한 인터페이스의 구현체를 다 찾아와서 서블릿 컨테이너에게 정보를 넘겨준다.

 

  • 기존 서블릿 컨테이너 초기화 과정
    1. ServletContainerInitializer 인터페이스 구현, 서블릿 컨테이너 초기화 코드 작성
      • /META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 서블릿 컨테이너 초기화 클래스(구현 클래스) 경로 등록
    2. 애플리케이션 초기화를 위해 @HandlesTypes 어노테이션으로 애플리케이션 초기화 인터페이스 지정
    3. 애플리케이션 초기화 인터페이스 구현
  • 스프링 MVC에서는 WebApplicationInitializer 인터페이스를 애플리케이션 초기화 인터페이스로 지정해두고 사용하기에 WebApplicationInitializer만 구현하면 편하게 애플리케이션 초기화를 할 수 있다.
    • ServletContainerInitializer 인터페이스 구현, 서블릿 컨테이너 초기화 코드 작성, 경로 등록 등과 같은 별도의 서블릿 컨테이너 초기화 과정을 거치지 않아도 된다.

출처 : [인프런 김영한 스프링 부트 - 핵심 원리와 활용]

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard

 

스프링 부트 - 핵심 원리와 활용 강의 | 김영한 - 인프런

김영한 | 실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., 백엔드 개발자를 위한 스프링 부트 끝판왕! 실무에 필요한 내용을 모두 담았습니다.  [임베딩 영상] 김영한의 스

www.inflearn.com