-
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. 정리
- 지금까지 알아본 내용은 모두 서블릿 컨테이너 위에서 동작하는 방법이다.
- 따라서 항상 톰캣 같은 서블릿 컨테이너에 배포를 해야만 동작하는 방식이다.
- 과거에는 서블릿 컨테이너 위에서 모든 것이 동작했지만, 스프링 부트와 내장 톰캣을 사용하면서 이런 부분이 바뀌기 시작했다.
'Spring-Boot > 스프링부트 - 핵심 원리와 활용' 카테고리의 다른 글
6. 외부설정과 프로필1 (0) 2023.05.24 5. 자동 구성(Auto Configuration) (0) 2023.05.18 4. 스프링 부트 스타터와 라이브러리 관리 (0) 2023.05.15 3. 스프링 부트와 내장 톰캣 (0) 2023.05.15 1. 스프링 부트 소개 (0) 2023.04.14