ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2. 웹 서버와 서블릿 컨테이너
    Spring-Boot/스프링부트 - 핵심 원리와 활용 2023. 4. 14. 11:30

    1. 웹 서버와 스프링 부트 소개

    * 외장 서버 VS 내장 서버 *

    * 전통적인 방식 *

    - 과거에 자바로 웹 애플리케이션을 개발할 때는 먼저 서버에 톰캣 같은 WAS(웹 애플리케이션 서버)를 설치했다.

    - 그리고 WAS에서 동작하도록 서블릿 스펙에 맞추어 코드를 작성하고 WAR 형식으로 빌드해서 war 파일을 만들었다.

    - 이렇게 만들어진 war 파일을 WAS에 전달해서 배포하는 방식으로 전체 개발 주기가 동작했다.

    - 이런 방식은 WAS 기반위에서 개발하고 실행해야 한다. IDE 같은 개발 환경에서도 WAS와 연동해서 실행하도록 복잡한 추가 설정이 필요하다.

     

    * 최근 방식 *

    - 최근에는 스프링 부트가 내장 톰캣을 포함하고 있다.

    - 애플리케이션 코드 안에 톰캣 같은 WAS가 라이브러리로 내장되어 있다는 뜻이다.

    - 개발자는 코드를 작성하고 JAR로 빌드한 다음 해당 JAR를 원하는 위치에서 실행하기만 하면 WAS도 함께 실행된다.

    - 쉽게 이야기해서 개발자는 'main()'  메서드만 실행하면 되고, WAS 설치나 IDE 같은 개발 환경에서 WAS와 연동하는 복잡한 일을 수행하지 않아도 된다.

     

    2. 톰캣 설치

    ` java.net.BindException: Address already in use` : 포트 이미 사용중

     

    * [MAC OS] *

        ㆍ`sudo lsof -i : 8080` : 프로세스 ID(PID) 조회

        ㆍ`sudo kill -9 PID` : 프로세스 종료

    * [원도우] *

        ㆍcmd를 열고 아래 명령어 순차적으로 실행

        ㆍ`netstat -ano | findstr : 포트번호` : 현재 포트를 사용중인 프로세스 찾기

        ㆍ`taskkill /f /pid 프로세스번호` : 프로세스 강제 종료하기

     

    3. 프로젝트 설정

    4. WAR 빌드와 배포

    JAR, WAR 간단 소개

    * JAR 소개 *

    - 자바는 여러 클래스와 리소스를 묶어서 'JAR'(Java Archive)라고 하는 압축 파일을 만들 수 있다.

    - 이 파일은 JVM 위에서 직접 실행되거나 또는 다른 곳에서 사용하는 라이브러리로 제공된다.

    - 직접 실행하는 경우 `main()` 메서드가 필요하고, `MANIFEST.MF` 파일에 실행할 메인 메서드가 있는 클래스를 지정해두어야 한다.

     

    - 실행 예) `java -jar abc.jar`

        ㆍJar는 쉽게 이야기해서 클래스와 관련 리소스를 압축한 단순한 파일이다.

        ㆍ필요한 경우 이 파일을 직접 실행할 수도 있고, 다른 곳에서 라이브러리로 사용할 수도 있다.

     

    * WAR 소개 *

    - WAR(Web Application Archive)라는 이름에서 알 수 있듯 WAR 파일은 웹 애플리케이션 서버(WAS)에 배포할 때 사용하는 파일이다.

    - JAR 파일이 JVM 위에서 실행된다면, WAR는 웹 애플리케이션 서버 위에서 실행된다.

    - 웹 애플리케이션 서버 위에서 실행되고, HTML 같은 정적 리소스와 클래스 파일을 모두 함께 포함하기 때문에 JAR와 비교해서 구조가 더 복잡하다.

    - 그리고 WAR 구조를 지켜야 한다.

     

    * WAR 구조 *

    - `WEB-INF`

        ㆍ`classes` : 실행 클래스 모음

        ㆍ`lib` : 라이브러리 모음

        ㆍ`web.xml` : 웹 서버 배치 설정 파일(생략 가능)

    - `index.html` : 정적 리소스

     

    - `WEB-INF` 폴더 하위는 자바 클래스와 라이브러리, 그리고 설정 정보가 들어가는 곳이다.

    - `WEB-INF`를 제외한 나머지 영역은 HTML, CSS 같은 정적 리소스가 사용되는 영역이다.

     

    WAR 배포

    - 1. 톰캣 서버를 종료한다.

    - 2. `톰캣폴더/webapps` 하위를 모두 삭제한다.

    - 3. 빌드된 `server-0.01-SNAPSHOT.war` 를 복사한다.

    - 4. `톰캣폴더/webapps` 하위에 붙여넣는다.

        ㆍ`톰캣폴더/webapps/server-0.0.1-SNAPSHOT.war`

    - 5. 이름을 변경한다.

    - 6. 톰캣 서버를 실행한다.

     

    * 주의! *

    - `ROOT.war` 에서 `ROOT`는 대문자를 사용해야 한다.

     

    * [MAC OS] *

        ㆍ`톰캣폴더/bin` 폴더

              º 실행: `./startup.sh`

              º 종료: `./shutdown.sh'

    * [원도우] *

        ㆍ`톰캣폴더/bin` 폴더

              º 실행: `startup.bat`

              º 종료: `shutdown.bat'

     

    * 참고 *

    - 진행이 잘되지 않으면 `톰캣폴더/logs/catalina.out` 로그를 확인하자

     

    * 실제 서버에서는 이렇게 사용하면 되지만, 개발 단계에서는 `war`파일을 만들고, 이것을 서버에 복사해서 배포하는 과정이 너무 번잡하다. 인텔리 J나 이클립스 같은 IDE는 이 부분을 편리하게 자동화해준다.

     

    5. 톰캣 설정 - 인텔리J 유료 버전

    6. 톰캣 설정 - 인텔리J 무료 버전

    7. 서블릿 컨테이너 초기화1

    - WAS를 실행하는 시점에 필요한 초기화 작업들이 있다. 서비스에 필요한 필터와 서블릿을 등록하고, 여기에 스프링을 사용한다면 스프링 컨테이너를 만들고, 서블릿과 스프링을 연결하는 디스페처 서블릿도 등록해야 한다.

    - WAS가 제공하는 초기화 기능을 사용하면, WAS 실행 시점에 이러한 초기화 과정을 진행할 수 있다.

    - 과거에는 `web.xml`을 사용해서 초기화했지만, 지금은 서블릿 스펙에서 자바 코드를 사용한 초기화도 지원한다.

     

    * 서블릿 컨테이너와 스프링 컨테이너 *

     

    서블릿 컨테이너 초기화 개발

    - 서블릿은 `ServletContainerInitializer` 라는 초기화 인터페이스를 제공한다.

    - 이름 그대로 서블릿 컨테이너를 초기화하는 기능을 제공한다.

    - 서블릿 컨테이너는 실행 시점에 초기화 메서드인 `onStartup()`을 호출해준다.

    - 여기서 애플리케이션에 필요한 기능들을 초기화 한거나 등록할 수 있다.

     

    * ServletContainerInitializer *

    ``` java
    public interface ServletContainerInitializer {
        public void onStartup(Set<Class<?>> c, ServletContext ctx) throw ServletException;
    }

    - `Set<Class<?>> c` : 조금 더 유연한 초기화 기능을 제공한다. `@HandlesTypes` 애노테이션과 함께 사용한다.

    - `ServletContext ctx` : 서블릿 컨테이너 자체의 기능을 제공한다. 이 객체를 통해 필터나 서블릿을 등록할 수 있다

     

    8. 서블릿 컨테이너 초기화2

    * 서블릿을 등록하는 2가지 방법 *

    - `@WebServlet` 어노테이션

    - 프로그래밍 방식

     

    * 애플리케이션 초기화 *

    - 서블릿 컨테이너는 조금 더 유연한 초기화 기능을 지원한다

     

    - 애플리케이션 초기화를 진행하려면 먼저 인터페이스를 만들어야 한다. 내용과 형식은 상관없고, 인터페이스는 꼭 필요하다.

     

    * 참고 - 프로그래밍 방식을 사용하는 이유 *

    - `@WebServlet` 을 사용하면 애노테이션 하나로 서블릿을 편리하게 등록할 수 있다.

    - 하지만 애노테이션 방식을 사용하면 유연하게 변경하는 것이 어렵다.

    - 마치 하드코딩 된 것처럼 동작한다.

    - 아래 참고 예시를 보면 `/test` 경로를 변경하고 싶으면 코드를 직접 변경해야 바꿀 수 있다.

    @WebServlet(urlPatterns = "/test")
    public class TestServlet extends HttpServlet {}

    - 반면에 프로그래밍 방식은 코딩을 더 많이 해야하고 불편하지만 무한한 유연성을 제공한다.

    - 예를 들어서

        ㆍ`/hello-servlet` 경로를 상황에 따라서 바꾸어 외부 설정을 읽어서 등록할 수 있다.

        ㆍ서블릿 자체도 특정 조건에 따라서 `if` 문으로 분기해서 등록하거나 뺄 수 있다.

        ㆍ서블릿을 내가 직접 생성하기 때문에 생성자에 필요한 정보를 넘길 수 있다.

    - 예제에서는 단순화를 위해 이런 부분들을 사용하지 않았지만 프로그래밍 방식이 왜 필요하지? 의 이유이다.

     

     

    * 애플리케이션 초기화 과정 *

    1. `@HandlesTypes` 애노테이션에 애플리케이션 초기화 인터페이스를 지정한다.

        ㆍ여기서 앞서 만든 `AppInit.class` 인터페이스를 지정했다.

    2. 서블릿 컨테이너 초기화(`ServletContainerInitializer`)는 파라미터로 넘어오는 `Set<Class<?>> c` 에 애플리케이션 초기화 인터페이스의 구현체들을 모두 찾아서 클래스 정보로 전달한다.

        ㆍ여기서 `@HandlesTypes(AppInit.class)` 를 지정했으므로

            `AppInit.class` 의 구현체인 `AppInitV1Servlet.class` 정보가 전달된다.

        ㆍ참고로 객체 인스턴스가 아니라 클래스 정보를 전달하기 때문에 실행하려면 객체를 생성해서 사용해야한다.

    3. `appInitClass.getDeclaredConstrutor().newInstance()`

        ㆍ리플렉션을 사용해서 객체를 생성한다. 참고로 이 코드는 `new AppInitV1Servlet()` 과 같다 생각하면 된다.

    4. `appInit.onStartup(ctx)`

        ㆍ애플리케이션 초기화 코드를 직접 실행하면서 서블릿 컨테이너 정보가 담긴 `ctx`도 함께 전달한다.

     

    정리

    - 초기화는 다음 순서로 진행된다.

    1. 서블릿 컨테이너 초기화 실행

        ㆍ`resources/META_INF/services/jakarta.servlet.ServletContainerInitializer`

    2. 애플리케이션 초기화 실행

        ㆍ`@HandlesTypes(AppInit.class)

     

    * 참고 *

    - 서블릿 컨테이너 초기화만 있어도 될 거 같은데, 왜 이렇게 복잡하게 애플리케이션 초기화하는 개념을 만들었을까?

    * 편리함 *

    - 서블릿 컨테이너 초기화 하려면 `ServletContainerInitializer` 인터페이스를 구현한 코드를 만들어야 한다.

    - 여기에 추가로 `META-INF/services/jakarta.servlet.ServletContainerInitializer` 파일에 해당 코드를 직접 지정해주어야 한다.

    -애플리케이션 초기화는 특정 인터페이스만 구현하면 된다.

    * 의존성 *

    - 애플리케이션 초기화는 서블릿 컨테이너에 상관없이 원하는 모양으로 인터페이스를 만들 수 있다.

    - 이를 통해 애플리케이션 초기화 코드가 서블릿 컨테이너에 대한 의존을 줄일 수 있다.

    - 특히 `ServletContext ctx`가 필요없는 애플리케이션 초기화 코드라면 의존을 완전히 제거할 수 도 있다.

     

    9. 스프링 컨테이너 등록

    * 스프링 컨테이너 생성 *

    - `AnnotationConfigWebApplicationContext` 가 바로 스프링 컨테이너이다

        ㆍ`AnnotationConfigWebApplicationContext` 부모를 따라가 보면 `ApplicationContext` 인터페이스를 확인할 수 있다.

        ㆍ이 구현체는 이름 그대로 애노테이션 기반 설정과 웹 기능을 지원하는 스프링 컨테이너로 이해하면 된다.

    - `appContext.register(HelloConfig.class)`

        ㆍ컨테이너에 스프링 설정을 추가한다.

     

    * 스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결 *

    - `new DispatcherServlet(appContext)`

    - 스프링 MVC가 제공하는 디스패처 서블릿을 생성하고, 생성자에 앞서 만든 스프링 컨테이너를 전달, 디스패처 서블릿에 스프링 컨테이너를 연결.

    - 이 디스패처 서블릿에 HTTP 요청이 오면 디스패처 서블릿은 해당 스프링 컨테이너에 들어있는 컨트롤러 빈들을 호출한다.

     

    * 디스패처 서블릿을 서블릿 컨테이너에 등록 *

    - `servletContext.addServlet("dispatcherV2", dispatcher)`

        ㆍ디스패처 서블릿을 서블릿 컨테이너에 등록한다.

    - `/spring/ * ` 요청이 디스패처 서블릿을 통하도록 설정

        ㆍ`/spring/*` 이렇게 경로를 지정하면 `/spring` 과 그 하위 요청은 모두 해당 서블릿을 통하게 된다.

     

    10. 스프링 MVC 서블릿 컨테이너 초기화 지원

    - `WebApplicationInitializer` 는 스프링이 이미 만들어둔 애플리케이션 초기화 인터페이스이다.

     

    * 참고 *

    - 일반적으로 스프링 컨테이너를 하나 만들고, 디스패처 서블릿도 하나만 만든다. 그리고 디스패처 서블릿의 경로 매핑도 `/` 로 해서 하나의 디스패치 서블릿을 통해서 모든 것을 처리하도록 한다.

     

    * SpringServletContainerInitializer *

    @HandlesTypes({WebApplicationInitializer.class})
    public class SpringServletContainerInitializer implements ServletContainerInitializer {}

    - 코드를 보면 앞서 만든 서블릿 컨테이너 초기화 코드와 비슷하다.

    - `@HandlesTypes` 의 대상이 `WebApplicationInitializer` 이다. 그리고 이 인터페이스의 구현체를 생성하고 실행하는 것을 확인할 수 있다.

    * 정리 *

    - 스프링MVC도 우리가 지금까지 한 것처럼 서블릿 컨테이너 초기화 파일에 초기화 클래스를 등록해두었다.

    - 그리고 `WebApplicationInitializer` 인터페이스를 애플리케이션 초기화 인터페이스로 지정해두고, 이것을 생성해서 실행한다.

    - 따라서 스프링 MVC를 사용한다면 `WebApplicationInitializer` 인터페이스만 구현하면 편리하게 애플리케이션 초기화를 사용할 수 있다. 

     

    11. 정리

    - 지금까지 알아본 내용은 모두 서블릿 컨테이너 위에서 동작하는 방법이다.

    - 따라서 항상 톰캣 같은 서블릿 컨테이너에 배포를 해야만 동작하는 방식이다.

    - 과거에는 서블릿 컨테이너 위에서 모든 것이 동작했지만, 스프링 부트와 내장 톰캣을 사용하면서 이런 부분이 바뀌기 시작했다.

    댓글

Designed by Tistory.