일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자바의 정석 기초편 ch8
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch11
- 스프링 고급 - 스프링 aop
- 타임리프 - 기본기능
- 게시글 목록 api
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch7
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch12
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch3
- 스프링 입문(무료)
- 스프링 mvc2 - 타임리프
- 스프링 mvc1 - 서블릿
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch2
- 스프링 db1 - 스프링과 문제 해결
- @Aspect
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch1
- 코드로 시작하는 자바 첫걸음
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch13
- 스프링 db2 - 데이터 접근 기술
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch6
- Today
- Total
나구리의 개발공부기록
웹 서버와 서블릿 컨테이너, 웹서버와 스프링 부트 소개, 톰캣 설치, 프로젝트 설정, WAR 빌드와 배포, 톰캣 설정(인텔리제이 무료/유료), 서블릿 컨테이너 초기화, 스프링 컨테이너 등록, 스프링 MVC 서블릿 컨테이너 초기화 지원 본문
웹 서버와 서블릿 컨테이너, 웹서버와 스프링 부트 소개, 톰캣 설치, 프로젝트 설정, WAR 빌드와 배포, 톰캣 설정(인텔리제이 무료/유료), 서블릿 컨테이너 초기화, 스프링 컨테이너 등록, 스프링 MVC 서블릿 컨테이너 초기화 지원
소소한나구리 2024. 11. 18. 18:12출처 : 인프런 - 스프링 부트 - 핵심 원리와 활용(유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 웹 서버와 스프링 부트 소개
1) 외장 서버 VS 내장 서버
(1) 전통적인 방식
- 과거에는 자바로 웹 애플리케이션을 개발할 때 먼저 서버에 톰캣 같은 WAS(웹 애플리케이션 서버)를 설치했음
- 그리고 WAS에서 동작하도록 서블릿 스펙에 맞추어 코드를 작성하고 WAR형식으로 빌드해서 war 파일을 만든 후 WAS에 전달하여 배포하는 방식으로 전체 개발 주기로 동작하였음
- 이런 방식은 WAS 기반 위에서 개발하고 실행해야하고 IDE같은 개발 환경에서도 WAS와 연동해서 실행되도록 복잡한 추가 설정이 필요함
(2) 최근 방식
- 최근에는 스프링 부트가 내장 톰캣을 포함하고 있어 애플리케이션 코드 안에 톰캣 같은 WAS가 라이브러리로 내장되어있음
- 개발자는 코드를 작성하고 JAR로 빌드한 다음 해당 JAR를 원하는 위치에서 실행하기만 하면 WAS도 함께 실행됨
- 개발자는 main()메서드만 실행하면되고 WAS 설치나 IDE 같은 개발 환경에서 WAS와 연동하는 복잡한 일은 수행하지 않아도 됨
2. 톰캣 설치 및 프로젝트 설정
1) 톰캣 설치
(1) 버전
- 자바 : 17이상, 스프링 3.0을 사용하여 자바 17이 최소 요구 버전임
- 톰캣 : Apache Tomcat 10 버전
(2) 톰캣 다운로드
- 링크
- Core에 있는 ZIP 다운로드 및 압축 해제
- 여러 추가 작업들을 할 것이기 때문에 관리하기 좋은 곳에 폴더를 만들어서 관리(지금은 홈에 was디렉토리를 만들어서 관리)
(3) 톰캣 실행 설정
- MAC, 리눅스 : 톰캣폴더/bin 폴더로 이동하여 chmod 755 * 로 권한을 주고 ./startup.sh로 실행, ./shutdown.sh로 종료
- MAC, 리눅스 사용자는 권한을 주지 않으면 permission denied라는 오류가 발생할 수 있음
- 윈도우 : 권한부여 없이 바로 톰캣폴더/bin으로 이동 후 startup.bat으로 실행, 종료는 shutdown.bat
(4) 실행 확인
- 터미널을 실행 후 톰캣폴더/bin으로 이동하여 권한을 부여하고 ./startup.sh로 톰캣을 실행
- 웹 브라우저에서 localhost:8080으로 접속하면 톰캣 웹페이지가 정상적으로 출력된 것을 확인하고 ./shutdown.sh를 입력해주면 정상 종료 됨
톰캣 실행
(base) jinagyeomi@namo apache-tomcat-10.1.33 % cd bin
(base) jinagyeomi@namo bin % chmod 755 *
(base) jinagyeomi@namo bin % ./startup.sh
Using CATALINA_BASE: /Users/jinagyeomi/was/apache-tomcat-10.1.33
Using CATALINA_HOME: /Users/jinagyeomi/was/apache-tomcat-10.1.33
Using CATALINA_TMPDIR: /Users/jinagyeomi/was/apache-tomcat-10.1.33/temp
Using JRE_HOME: /Users/jinagyeomi/.sdkman/candidates/java/current
Using CLASSPATH: /Users/jinagyeomi/was/apache-tomcat-10.1.33/bin/bootstrap.jar:/Users/jinagyeomi/was/apache-tomcat-10.1.33/bin/tomcat-juli.jar
Using CATALINA_OPTS:
Tomcat started.
톰캣 종료
(base) jinagyeomi@namo bin % ./shutdown.sh
Using CATALINA_BASE: /Users/jinagyeomi/was/apache-tomcat-10.1.33
Using CATALINA_HOME: /Users/jinagyeomi/was/apache-tomcat-10.1.33
Using CATALINA_TMPDIR: /Users/jinagyeomi/was/apache-tomcat-10.1.33/temp
Using JRE_HOME: /Users/jinagyeomi/.sdkman/candidates/java/current
Using CLASSPATH: /Users/jinagyeomi/was/apache-tomcat-10.1.33/bin/bootstrap.jar:/Users/jinagyeomi/was/apache-tomcat-10.1.33/bin/tomcat-juli.jar
Using CATALINA_OPTS:
(5-1) 잘 안될 때 해결방안
- localhost:8080에 접근이 되지 않으면 로그를 잘 확인해봐야하는데, 만약 java.net.BindException: Address already in use 와 같은 메시지가 보인다면 8080 포트를 이미 사용하고 있기 때문에 2가지 방법으로 해결해야함
(5-2) 프로세스 종료
- MAC OS: 터미널에서 sudo lsof -i: 8080으로 프로세스 ID(PID)조회 후 sudo kill -9 PID로 프로세스를 종료
- 윈도우: cmd에서 netstat -ano | findstr :포트번호로 PID를 조회 후 taskkill /f /pid PID 번호로 프로세스를 종료
(5-3) 톰캣 서버 포트 변경
- 만약 8080포트를 꼭 다른곳에서 사용해야하는 경우에는 톰캣 설정 파일인 server.xml의 Connector 태그에서 port를 수정하여 톰캣의 포트를 변경할 수 있음
- 톰캣폴더/conf/server.xml
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
2) 프로젝트 설정
(1) 프로젝트 - build.gradle확인
- 강의 샘플 프로젝트로 진행,
- 스프링 부트 프로젝트가아닌 순수한 자바로 서블릿을 사용하는 프로젝트
- plugins { id 'war' } : 톰캣 같은 웹 애플리케이션 서버(WAS)위에서 동작하는 WAR파일을 만들어주는 플러그인
- jakarta.servlet-api : 서블릿을 사용할 때 필요한 라이브러리
plugins {
id 'java'
id 'war'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
//서블릿
implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0'
}
tasks.named('test') {
useJUnitPlatform()
}
(1) 간단한 HTML 등록 - index.html
- /src/main 하위에 webapp 폴더를 생성하여 정적 HTML 생성
<!DOCTYPE html>
<html>
<body>index html</body>
</html>
(2) 서블릿 등록 - testServlet
- hello 패키지 하위에 servlet 패키지를 생성하여 작성
- /test로 요청이 오면 해당 서블릿이 실행되고 로그를 출력함
- 웹 브라우저로 요청하면 서블릿이 실행되고 화면에 test가 출력되어야 함(응답값이 test)
- 이 서블릿을 실행하려면 톰캣 같은 웹 애플리케이션 서버(WAS)에 이 코드를 배포해야함
package hello.servlet;
/**
* http://localhost:8080/test
*/
@WebServlet(urlPatterns = "/test")
public class TestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("TestServlet.service");
resp.getWriter().println("test");
}
}
3. WAR 빌드와 배포
1) WAR 빌드
(1) 프로젝트 빌드
- 프로젝트 폴더로 이동
- 프로젝트 빌드: MAC - ./gradlew build, 윈도우 - gradlew build
- WAR 파일 생성 확인: build/libs에 WAR파일이 생성되어있음
(2) WAR 압축 풀기 및 결과
- jar -xvf server-0.0.1-SNAPSHOT.war로 압축을 해제하면 WEB-INF, classes, lib같은 폴더와 우리가 만든 index.html등의 파일이 보임
2) JAR, WAR 간단 소개
(1) JAR
- 여러 클래스와 리소스를 묶어서 JAR(Java Archive)라는 압축 파일을 만들 수 있으며 이 파일은 JVM 위에서 직접 실행되거나 다른곳에서 사용하는 라이브러리로 제공됨
- 직접 실행하는 경우 main() 메서드가 필요하고, MANIFEST.MF파일에 실행할 메인 메서드가 있는 클래스를 지정해 두어야 함
- 쉽게 이야기해서 Jar는 클래스와 관련 리소스를 압축한 단순한 파일로 필요한 경우 이 파일을 직접 실행할 수도 있고 다른 곳에서 라이브러리로 사용할 수도 있음
- 실행 예) java -jar abc.jar
(2) WAR
- Web Application Archive 라는 이름에서 알 수 있듯 웹 애플리케이션 서버에 배포할 때 사용하는 파일임
- 즉 JAR가 JVM위에서 실행 된다면 WAR는 웹 애플리케이션 서버 위에서 실행됨
- 웹 애플리케이션 서버 위에서 실행되고 HTML 같은 정적 리소스와 클래스 파일을 모두 함께 포함하기 때문에 JAR와 비교했을 때 구조가 더 복잡하며 WAR의 구조를 지켜야함
(3) WAR 구조
- WEB-INF 폴더 하위는 자바 클래스(classes폴더)와 라이브러리(lib폴더), 그리고 설정 정보(web.xml, 생략가능)가 들어가는 곳임
- WEB-INF를 제외한 나머지 영역은 HTML, CSS 같은 정적 리소스가 사용되는 영역임
3) WAR 배포
(1) 톰캣 서버에 배포
- 톰캣 서버 종료
- 톰캣폴더/webapps 하위를 모두 삭제(기본 예제 프로그램 등이 깔려 있음)
- 빌드된 server-0.0.1-SNAPSHOT.war를 복사하여 톰캣폴더/webapps 하위에 붙여 넣기
- 해당 파일을 ROOT.war로 이름을 변경(대문자로 이름 변경)
- 톰캣 서버를 실행하면 ROOT.war가 자동으로 압축이 풀리면서 실행됨
(2) 실행 결과 확인
- 톰캣 서버를 실행 후 localhost:8080과 localhost:8080/test로 접속해보면 만들어둔 index.html페이지와 서블릿이 정상 동작하는 것을 확인할 수 있음
- 톰캣폴더/log/catalina.out 파일을 열어보면 실행 로그를 확인할 수 있음
- 만일 진행이 잘 되지 않으면 해당 로그파일을 열어서 문제를 확인하고 war파일을 다시 배포
- 실제 서버에서는 이렇게 사용하면 되지만 개발 단계에서는 war파일을 만들고 서버에 복사해서 배포하는 과정이 너무 번잡한데, 각 IDE에서는 이부분을 편리하게 자동화 해줌
4. 톰캣 설정
** 참고
- 이클립스 설정은 강의에서 다루지 않으므로 '이클립스 gradle 톰캣' 이라는 키워드로 검색
1) 인텔리J 유료 버전
- 인텔리J 유료 버전은 톰캣 지원이 포함되어있음
(1) 상단 메뉴 -> Run -> Edit Configurations
- Edit Configurations 화면에서 왼쪽 상단 + 모양 클릭
- tomcat Server 검색 후 local 선택
- Server 탭에서 Application server의 CONFIGURE... 클릭
- TomcatHome의 폴더모양 클릭하여 설치한 톰캣 폴더를 선택
- Deployment탭에서 + 모양 -> Artifact -> 아무거나 클릭(보통 exploded 선택)
- Application context:에 적혀있는 내용 모두 삭제
- RUN 하면 설정이 완료되어 웹 애플리케이션을 실행할 수 있음
** 참고
- 마찬가지로 기존에 톰캣 서버가 띄워져있으면 에러가 발생하니 꼭 기존에 띄워놨던 서버를 종료하고 실행할 것
2) 인텔리J 무료 버전
- 무료 버전은 조금 설정이 복잡함
- Tomcat runner 플러그인은 최신 인텔리J에서 작동하지 않으므로 Smart Tomcat 플러그인을 설치해야함
- 설치한 Smart Tomcat 플러그인은 유료버전에서도 사용할 수 있으나 유료에서는 내장으로 사용가능하니 굳이 중복으로 사용할 필요 없음
(1) gradle 설정 및 명령어 실행
- 아래의 설정 후 터미널에서 프로젝트로 이동하여 ./gradlew explodedWar를 입력
- build로 이동하면 exploded폴더가 생겨있고 해당 폴더로 들어가면 WEB-INF를 포함한 war파일이 풀려있는 모습을 확인할 수 있음
//war 풀기, 인텔리J 무료버전 필요
task explodedWar(type: Copy) {
into "$buildDir/exploded"
with war
}
(base) jinagyeomi@namo bin % cd ~/Desktop/dev/study/spring/8.springboot/server
(base) jinagyeomi@namo server % ./gradlew explodedWar
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details
BUILD SUCCESSFUL in 2s
2 actionable tasks: 1 executed, 1 up-to-date
(base) jinagyeomi@namo server % cd build/exploded
(base) jinagyeomi@namo exploded % ls
META-INF WEB-INF index.html
(2) Smart Tomcat 설정
- Smart Tomcat 플러그인을 설치 후 IDE 재실행
- 완전히 새로운 tomcat 서버를 생성 후 bin폴더로 이동하여 chmod 755 * 로 권한을 설정
- 기존 서버를 재사용하면 설정이 꼬일 수 있어서 다운 받았던 압축파일을 다시 풀어서 생성해야함
(3) 상단 메뉴 -> Run -> Edit Configurations
- Edit Configurations 화면에서 왼쪽 상단 + 모양 클릭
- Smart Tomcat 선택
- Configuration탭에서 Tomcat server: 의 CONFIGURE...를 클릭
- 이동한 페이지에서 + 클릭하여 새로 생성한 톰캣 서버를 선택 후 OK
- Tomcat server: 이름을 입력(폴더모양 클릭하면 이름이 자동으로 뜸)
- Deployment directory: 현재 프로젝트 폴더의 build/exploded로 지정
- Use classpath of module: server 선택
- Context path: / (슬래시 하나)입력
- OK 누르면 설정이 완료되어 웹을 띄울 수 있음
5. 서블릿 컨테이너 초기화
1) 서블릿 컨테이너 초기화 개발
- WAS를 실행하는 시점에 필요한 초기화 작업들이 있는데, 서비스에 필요한 필터와 서블릿을 등록하고 여기에 스프링을 사용한다면 스프링 컨테이너를 만들고 서블릿과 스프링을 연결하는 디스페처 서블릿도 등록해야함
- WAS가 제공하는 초기화 기능을 사용하면 WAS 실행 시점에 이러한 초기화 과정을 진행할 수 있음
- 과거에는 web.xml을 사용해서 초기화 했지만 지금은 스펙에서 자바 코드를 사용한 초기화도 지원함
(1) ServletContainerInitializer
- 서블릿이 제공하는 초기화 인터페이스로 서블릿 컨테이너를 초기화 하는 기능을 제공함
- 서블릿 컨테이너는 실행 시점에 초기화 메서드인 onStartup()을 호출해줌으로 여기서 애플리케이션에 필요한 기능들을 초기화 하거나 등록할 수 있음
- Set<Class<?>> c: 조금 더 유연한 초기화 기능을 제공하며 @HandlesTypes 애노테이션과 함께 사용함
- ServletContext ctx: 서블릿 컨테이너 자체의 기능을 제공하며 이 객체를 통해 필터나 서블릿을 등록할 수 있음
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
(2) MyContainerInitV1
- hello 하위에 container 패키지를 생성하여 작성
- 서블릿 컨테이너 초기화 인터페이스를 구현하여 실제로 동작하는지 출력문으로 확인
package hello.container;
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);
}
}
(3) 경로에 파일 생성
- WAS에 실행할 초기화 클래스를 알려주어야 함
- resources에 META-INF/services 디렉토리를 만들어서 jakarta.servlet.ServletContainerInitializer 파일을 생성
- 해당 파일에 hello.container.MyContainerInitV1 를 입력(서블릿 컨테이터 초기화 인터페이스를 구현한 구현체)
** 주의
- META-INF는 대문자
- services는 마지막에 s가 들어가는 것에 주의
(4) 실행
- WAS를 실행해보면 구현체 클래스에 입력한 출력문들이 출력되는 것을 확인할 수 있음
- 즉, WAS가 실행할 때 해당 초기화 클래스가 실행된다는 것을 확인할 수 있음
MyContainerInitV1 onStartup
MyContainerInitV1 c = null
MyContainerInitV1 ctx = org.apache.catalina.core.ApplicationContextFacade@36f633e4
2) 프로그래밍 방식으로 서블릿 등록
- 서블릿은 @WebServlet 애노테이션을 이용하는 방식과 프로그래밍 방식 2가지 방법으로 등록할 수 있음
(1) HelloServlet
- HTTP응답으로 Hello servlet!이 출력되는 서블릿
package hello.servlet;
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!");
}
}
3) 애플리케이션 초기화
- 서블릿 컨테이너는 조금 더 유연한 초기화 기능을 지원하는데 여기서는 이것을 애플리케이션 초기화로 작성
(1) AppInit
- 애플리케이션 초기화를 진행하려면 먼저 인터페이스를 만들어야함
- 내용과 형식은 상관없고 인터페이스는 꼭 필요함
package hello.container;
public interface AppInit {
void onStartup(ServletContext servletContext);
}
(2) AppInitV1Servlet
- 프로그래밍 방식으로 HelloServlet 서블릿을 서블릿 컨테이너에 직접 등록
- HTTP로 /hello-servlet을 호출하면 HelloServlet 서블릿이 실행됨
package hello.container;
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"); // 매핑
}
}
** 참고 - 프로그래밍 방식을 사용하는 이유
- @WebServlet을 사용하면 애노테이션 하나로 서블릿을 편리하게 등록할 수 있지만 하드코딩 된 것처럼 동작하므로 유연하게 변경하는 것이 어려움
- 반면 프로그래밍 방식은 코딩을 더 많이 해야하고 불편하지만 경로를 상황에 따라 바꿔서 외부 설정을 읽도록 등록하거나 특정 조건에 따라 분기를 타게하거나, 서블릿을 직접 생성하므로 생성자에 필요한 정보를 넘기는 등의 무한한 유연성을 제공함
- 예제에서는 단순화를 위해 이런부분을 사용하지 않았지만 이러한 부분들 때문에 프로그래밍 방식으로 서블릿을 등록하는 경우가 존재함
(3) MyContainerInitV2
- @HandlesTypes 애노테이션에 애플리케이션 초기화 인터페이스를 지정
- 서블릿 컨테이너 초기화(ServletContainerInitializer)는 파라미터로 넘어오는 Set<Class<?>> c에 애플리케이션 초기화 인터페이스의 구현체들을 모두 찾아서 클래스 정보로 전달함
- 여기에서는 지정한 AppInit 인터페이스를 구현한 AppInitV1Servlet.class 정보가 전달되며 객체 인스턴스를 전달하는 것이아니라 정보를 전달하기 때문에 실행하려면 객체를 생성해서 사용해야함
- AppInit appInit = (AppInit) appInitClass.getDeclaredConstructor().newInstance(): 리플렉션을 사용하여 객체를 생성하며 해당 코드는 new AppInitV1Servlet()과 같다고 생각하면 됨
- appInit.onStartup(ctx): 애플리케이션 초기화 코드를 직접 실행하면서 서블릿 컨테이너 정보가 담긴 ctx도 함께 전달함
package hello.container;
@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);
// class hello.container.AppInitV1Servlet
for (Class<?> appInitClass : c) {
try {
// new AppInitV1Servlet()과 같은 코드
AppInit appInit = (AppInit) appInitClass.getDeclaredConstructor().newInstance();
appInit.onStartup(ctx);
} catch (Exception e) { // 여러가지 예외가 많이 뜨지만 예제이므로 Exception 으로 한번에 해결
throw new RuntimeException(e);
}
}
}
}
(4) MyContainerInitV2 등록 및 실행
- jakarta.servlet.ServletContainerInitializer 파일에 hello.container.MyContainerInitV2 입력
- WAS를 실행해보면 실행 로그에 MyContainerInitV2 c에 AppInitV1Servlet이 담겨있어 애플리케이션 초기화가 정상 진행된 것을 로그로 확인할 수 있음
- 매핑한 /hello-servlet으로 접속하면 HelloServlet이 동작하여 응답 메시지가 브라우저에 출력되며, HelloServlet이 실행되는 로그도 확인할 수 있음
MyContainerInitV2 onStartup
MyContainerInitV2 c = [class hello.container.AppInitV1Servlet]
MyContainerInitV2 ctx = org.apache.catalina.core.ApplicationContextFacade@48456baf
AppInitV1Servlet onStartup
[2024-11-18 04:41:56,647] Artifact Gradle : hello : server-0.0.1-SNAPSHOT.war (exploded): Artifact is deployed successfully
[2024-11-18 04:41:56,647] Artifact Gradle : hello : server-0.0.1-SNAPSHOT.war (exploded): Deploy took 173 milliseconds
HelloServlet.service
(5) 초기화 실행 순서
- 1. 서블릿 컨테이너 초기화 실행
- resources/META-INF/services/ jakarta.servlet.ServletContainerInitializer
- 2. 애플리케이션 초기화 실행
- @HandlesTypes(AppInit.class)
4) 서블릿 컨테이너 초기화만 있어도 될 것 같은데 애플리케이션 초기화라는 개념을 만든 이유
(1) 편리함
- 서블릿 컨테이너를 초기화하려면 ServletContainerInitializer 인터페이스를 구현한 코드를 만들어야 하고 추가로resources/META-INF/services/ jakarta.servlet.ServletContainerInitializer 파일에 해당 코드를 직접 지정해주어야 함
- 애플리케이션 초기화는 특정 인터페이스만 구현하면 됨
(2) 의존성
- 애플리케이션 초기화는 서블릿 컨테이너에 상관없이 원하는 모양으로 인터페이스를 만들 수 있어 애플리케이션 초기화 코드가 서블릿 컨테이너에 대한 의존을 줄일 수 있음
- 특히 ServletContext ctx가 필요없는 애플리케이션 초기화 코드라면 의존을 완전히 제거할 수도 있음
6. 스프링 컨테이너 등록
1) WAS와 스프링을 통합
(1) build.gradle - 스프링 관련 라이브러리 추가
- spring-webmvc 라이브러리를 추가하면 스프링 MVC 뿐만 아니라 spring-core를 포함한 스프링 핵심 라이브러리들도 함께 포함됨
//스프링 MVC 추가
implementation 'org.springframework:spring-webmvc:6.0.4'
(2) HelloController
- spring패키지를 생성 후 작성
- @RestController를 사용하는 간단한 스프링 컨트롤러
- HTTP 응답으로 hello spring!!이 응답됨
package hello.spring;
@RestController
public class HelloController {
@GetMapping("/hello-spring")
public String hello() {
System.out.println("HelloController.hello");
return "Hello Spring!!";
}
}
(3) HelloConfig
- 컴포넌트 스캔을 사용하지 않고 직접 수동으로 빈을 등록
package hello.spring;
@Configuration
public class HelloConfig {
@Bean
public HelloController helloController() {
return new HelloController();
}
}
(4-1) AppInitV2Spring
- 애플리케이션 초기화를 사용하여 서블릿 컨테이너에 스프링 컨테이너를 생성하고 등록
- 앞에서 AppInit을 구현하면 애플리케이션 초기화 코드가 자동으로 실행되도록 MyContainerInitV2에 이미 작업을 해두었음
package hello.container;
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("/*");
}
}
(4-2) 스프링 컨테이너 생성
- AnnotationConfigWebApplicationContext가 스프링 컨테이너이며 이름 그대로 애노테이션 기반 설정과 웹 기능을 지원하는 스프링 컨테이너
- appContext.register(HelloConfig.class): 컨테이너에 스프링 설정을 추가
(4-3) 스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결
- new DispatcherServlet(appContext): 생성자로 스프링 컨테이너를 전달하면서 스프링 MVC가 제공하는 디스패처 서블릿을 생성
- 이렇게 생성하면 디스패처 서블릿에 스프링 컨테이너가 연결됨
- 이 디스패처 서블릿에 HTTP 요청이 오면 디스패처 서블릿은 해당 스프링 컨테이너에 들어있는 컨트롤러 빈들을 호출함
(4-4) 디스패처 서블릿을 서블릿 컨테이너에 등록
- servletContext.addServlet("dispatcherV2", dispatcher): 디스패처 서블릿을 서블릿 컨테이너에 등록
- /spring/* 요청이 디스패처 서블릿을 통하도록 설정
- /spring과 그하위 요청은 모두 해당 서블릿을 통과하게 됨
** 주의
- 서블릿을 등록할 때 이름은 원하는 이름을 등록해도 되지만 같은 이름으로 중복 등록하면 오류가 발생하므로 유의해야함
(5) WAS 실행
- 실행 후 http://localhost:8080/spring/hello-spring 경로로 접속하면 HelloController가 정상적으로 응답한 값이 출력되는 것을 볼 수 있음
- 다만 경로가 기존과는 다르게 /spring/hello-spring으로 접속하였는데, 디스패처 서블릿 실행을 /spring/* 패턴으로 호출했기 때문에 다음과 같이 동작함
- 1. dispatcherV2 디스패처 서블릿이 실행됨 - /spring
- 2. dispatcherV2 디스패처 서블릿이 스프링 컨트롤러를 찾아서 실행함 - /hello-spring
- 이때 서블릿을 찾아서 호출하는데 사용된 /spring을 제외한 /hello-spring가 매핑된 컨트롤러의 메서드를 찾아서 실행함(디스패처 서블릿에서 매핑했던 /spring/* 에서의 * 부분으로 스프링 컨트롤러를 찾음)
7. 스프링 MVC 서블릿 컨테이너 초기화 지원
1) 서블릿 컨테이너 초기화 과정 생략
- 서블릿 컨테이너 초기화 과정은 상당히 번거롭고 반복되는 작업인데, 스프링 MVC는 이러한 서블릿 컨테이너 초기화 작업을 이미 만들어 두었음
- 스프링 MVC를 사용하면 서블릿 컨테이너 초기화 과정은 생략하고 애플리케이션 초기화 코드만 작성하면됨
(1) WebApplicationInitializer
- 스프링이 지원하는 애플리케이션 초기화를 사용하려면 해당 인터페이스를 구현하면 됨
package org.springframework.web;
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
(2) AppInitV3SpringMvc
- WebApplicationInitializer 인터페이스를 구현하는 부분을 제외하고는 이전의 AppInitV2 Spring과 거의 유사함
- 디스패처 서블릿을 새로 만들어서 등록할 때 이름을 dispatcherV3로 변경하고, 모든 요청이 해당 서블릿을 통하도록 servlet.addMapping("/")으로 설정
package hello.container;
public class AppInitV3SpringMvc implements WebApplicationInitializer {
/**
* http://localhost:8080/hello-spring
*
* 스프링 MVC 제공 WebApplicationInitializer 활용
* spring-web
* META-INF/services/jakarta.servlet.ServletContainerInitializer * org.springframework.web.SpringServletContainerInitializer
*/
@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("/");
}
}
(3) WAS 실행
- WAS를 실행 후 localhost:8080/hello-spring으로 접속하면 정상적으로 서블릿과 애플리케이션이 초기화되어 HelloController의 응답값이 출력되는 것을 확인할 수 있음
(4) 등록된 서블릿들
- / = dispatcherV3
- /spring/* = dispatcherV2
- /hello-servlet = helloServlet
- /test = TestServlet
- 이렇게 여러가지 서블릿이 등록된 경우 더 구체적인 것이 먼저 실행됨
** 참고
- 여기서는 이해를 돕기 위해 디스패처 서블릿도 2개 만들고 스프링 컨테이너도 2개 만들었음
- 일반적으로는 스프링 컨테이너를 하나 만들고 디스패처 서블릿도 하나만 만들고 디스패처 서블릿의 경로 매핑도 /로하여 하나의 디스패처 서블릿을 통해서 모든 것을 처리하도록 함
2) 스프링 MVC가 제공하는 서블릿 컨테이너 초기화 분석
(1) spring-web 라이브러리 확인
- spring-web라이브러리를 열어보면 서블릿 컨테이너 초기화를 위한 등록파일을 확인할 수 있으며, 이곳에 서블릿 컨테이너 초기화 클래스가 등록되어있음
- /META-INF/services/jakarta.servlet.ServletContainerInitializer 파일에 org.springframework.web.SpringServletContainerInitializer가 등록되어있음
(2) SpringServletContainerInitializer 확인
- 등록된 서블릿 컨테이너 초기화 파일을 보면 직접 실습하면서 만든 컨테이너 초기화 코드와 유사함
- @HandlesTypes의 대상으로 WebApplicationInitializer가 등록되어있는 것을 확인할 수 있으며 해당 인터페이스는 앞서, AppInitV3SpringMvc를 생성할 때 구현했던 인터페이스임
- 아래 이미지의 초록색 영역이 스프링이 만들어서 제공하는 영역임
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
// ... 구현 코드 생략
}
(3) 정리
- 스프링 MVC도 우리가 지금까지 한 것처럼 서블릿 컨테이너 초기화 파일에 초기화 클래스를 등록해 두었고 WebApplicationInitializer 인터페이스를 애플리케이션 초기화 인터페이스로 지정해두고 이것을 생성해서 실행함
- 즉, 스프링 MVC를 사용하면 WebApplicationInitializer 인터페이스만 구현하면 편리하게 애플리케이션 초기화를 사용할 수 있음
- 지금까지 알아본 내용은 모두 서블릿 컨테이너 위에서 동작하는 방법이므로 항상 톰캣 같은 서블릿 컨테이너에 배포를 해야만 동작하는 방식임
- 과거에는 서블릿 컨테이너위에서 모든 것이 동작했지만, 스프링 부트와 내장 톰캣을 사용하면서 이런 부분이 바뀌기 시작하였음