Now Loading ...
-
(Spring) - 스프링이 제공하는 트랜잭션 AOP
서비스 계층
애플리케이션 구조
여러가지 애플리케이션 구조가 있지만, 가장 단순하면서 많이 사용하는 방법은 역할에 따라 3가지 계층으로 나누는 것이다.
프레젠테이션 계층
UI와 관련된 처리, 웹 요청과 응답, 사용자 요청 검증을 담당
주 사용 기술: 서블릿과 HTTP 같은 웹 기술, 스프링 MVC
서비스 계층
비즈니스 로직을 담당
주 사용 기술: 가급적 특정 기술에 의존하지 않고, 순수 자바 코드로 작성
데이터 접근 계층
실제 데이터베이스에 접근을 담당
주 사용 기술: JDBC, JPA, File, Redis, Mongo
서비스 계층 (이전 글 참고)
서비스 계층은 가급적 특정 구현 기술에 직접 의존해서는 안된다.
서비스 계층에서 트랜잭션을 사용하기 위해 JDBC 기술에 의존하던 문제
-> 트랜잭션 매니저를 통해서 해결했다.
서비스 계층은 가급적 핵심 비즈니스 로직만 구현해야 한다.
아직 서비스 계층에서 비즈니스 로직 뿐만 아니라 트랜잭션을 처리하는 기술 로직이 함께 포함되어 있는 문제가 남아있다.
-> 스프링 AOP를 통해 프록시를 도입하면 해결할 수 있다.
트랜잭션 AOP
트랜잭션 프록시
프록시를 사용하면 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리할 수 있다.
트랜잭션 프록시 코드 예시
public class TransactionProxy {
private MemberService target;
public void logic() {
TransactionStatus status = transactionManager.getTransaction(..);
try {
target.logic(); //실제 대상 호출
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
}
}
트랜잭션 프록시 적용 후 서비스 코드 예시
public class Service {
public void logic() {
bizLogic(fromId, toId, money); // 비즈니스 로직
}
}
스프링이 제공하는 AOP 기능을 사용하면 프록시를 편리하게 적용할 수 있다.
스프링 AOP를 사용하려면 어드바이저, 포인트컷, 어드바이스가 필요하다.
트랜잭션 AOP
스프링 AOP를 직접 사용해서 트랜잭션을 처리해도 되지만, 스프링은 트랜잭션 AOP를 처리하기 위한 모든 기능을 제공한다.
스프링은 트랜잭션 AOP 처리를 위해 다음 클래스를 제공한다.
어드바이저: BeanFactoryTransactionAttributeSourceAdvisor
포인트컷: TransactionAttributeSourcePointcut
어드바이스: TransactionInterceptor
트랜잭션 처리가 필요한 곳에 @Transactional 어노테이션만 붙여주면, 스프링의 트랜잭션 AOP는 이 애노테이션을 인식해서 트랜잭션 프록시를 적용해준다.
트랜잭션 AOP 예시
이전 글 참고
MemberRepositoryV4 클래스
@Repository
public class MemberRepositoryV4 implements MemberRepository {
// MemberRepositoryV2와 동일
}
MemberServiceV4 클래스
@Service
public class MemberServiceV4 {
private final MemberRepository memberRepository;
public MemberServiceV4(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Transactional
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
bizLogic(fromId, toId, money); // 비즈니스 로직
}
private void bizLogic(String fromId, String toId, int money) throws SQLException {
Member fromMember = memberRepository.findById(fromId);
Member toMember = memberRepository.findById(toId);
memberRepository.update(fromId, fromMember.getMoney() - money);
memberRepository.update(toId, toMember.getMoney() + money);
}
}
서비스 계층에서 아직 JDBC 기술인 SQLException에 의존하지만, 이 부분은 일단 넘어가자.
그래도 간단하게 말하자면, SQLException은 체크 예외인데, 리포지토리 계층에서 체크 예외인 SQLException을 언체크(런타임) 예외로 바꿔서 던지면 된다.
참고 자료
“김영한 스프링 DB 1편”
https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html
-
(Spring) - 스프링이 제공하는 트랜잭션 기술
이전 글의 트랜잭션 예시에서 여러 문제가 있었는데, 스프링은 이 문제들을 해결할 수 있는 다양한 방법과 기술들을 제공한다. 지금부터 스프링을 사용해서 이러한 문제들을 하나씩 해결해보자.
트랜잭션 추상화와 리소스 동기화(공유)
문제 1 : 서비스 계층이 트랜잭션을 사용하기 위해 JDBC 기술에 의존하던 문제
-> 해결 : 트랜잭션 추상화
문제 2 : 리포지토리 계층에서 Connection을 인자로 받는 메소드와 받지 않는 메소드를 중복해서 만들어야 하던 문제
-> 해결 : 리소스 동기화(공유)
스프링은 트랜잭션 기능을 추상화하는 PlatformTransactionManager 인터페이스를 제공하며, 커넥션을 동기화(공유)해주는 TransactionSynchronizationManager 클래스를 제공한다.
해당 글에서는 PlatformTransactionManager 인터페이스와 구현체를 포함해서 “트랜잭션 매니저”, TransactionSynchronizationManager 클래스를 “트랜잭션 동기화 매니저”라고 이야기하겠다.
트랜잭션 매니저는 내부에서 트랜잭션 동기화 매니저를 사용한다.
PlatformTransactionManager
package org.springframework.transaction;
public interface PlatformTransactionManager extends TransactionManager {
// 트랜잭션을 시작
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
// 트랜잭션을 커밋
void commit(TransactionStatus status) throws TransactionException;
// 트랜잭션을 롤백
void rollback(TransactionStatus status) throws TransactionException;
}
서비스 계층에서는 특정 트랜잭션 기술에 직접 의존하는 것이 아니라, PlatformTransactionManager 인터페이스에 의존한다.
데이터 접근 기술에 따른 PlatformTransactionManager 인터페이스의 구현체는 대부분 만들어져 있다.
TransactionSynchronizationManager
package org.springframework.transaction.support;
public abstract class TransactionSynchronizationManager {
...
}
트랜잭션 동기화 매니저는 쓰레드 로컬(ThreadLocal)을 사용해서 커넥션을 동기화(공유)해준다. 쓰레드 로컬을 사용하기 때문에 멀티쓰레드 상황에 안전하게 커넥션을 동기화할 수 있다.
쓰레드 로컬을 사용하면 각각의 쓰레드마다 별도의 저장소가 부여된다. 따라서 해당 쓰레드만 해당 데이터에 접근할 수 있다.
트랜잭션 매니저와 트랜잭션 동기화 매니저의 동작 방식 - DataSourceTransactionManager 예시
1.서비스 계층에서 transactionManager.getTransaction()을 호출해서 트랜잭션을 시작한다.
2.트랜잭션 매니저는 내부에서 데이터소스를 사용해서 커넥션을 생성한다.
3.트랜잭션 매니저는 커넥션을 수동 커밋 모드로 변경해서 실제 데이터베이스 트랜잭션을 시작한다.
4.트랜잭션 매니저는 커넥션을 트랜잭션 동기화 매니저에 보관한다.
5.트랜잭션 동기화 매니저는 쓰레드 로컬에 커넥션을 보관한다.
6.서비스는 비즈니스 로직을 실행하면서 리포지토리 계층의 메서드들을 호출한다.
7.리포지토리 계층에서 DataSourceUtils.getConnection()을 호출해서 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다. (트랜잭션 동기화 매니저가 관리하는 커넥션이 없는 경우 새로운 커넥션을 생성해서 반환)
8.획득한 커넥션을 사용해서 SQL을 데이터베이스에 전달해서 실행한다.
9.비즈니스 로직이 끝난 후 transactionManager.commit()나 transactionManager.rollback()를 호출하여 트랜잭션을 종료한다.
10.트랜잭션을 종료하려면 동기화된 커넥션이 필요한데, 트랜잭션 매니저는 트랜잭션 동기화 매니저를 통해 동기화된 커넥션을 획득한다.
11.트랜잭션 매니저는 획득한 커넥션을 통해 데이터베이스에 트랜잭션을 커밋하거나 롤백한다.
12.트랜잭션 매니저는 전체 리소스를 정리한다. (트랜잭션 동기화 매니저 정리, 커넥션을 자동 커밋 모드로 변경한 후 커넥션 풀에 반환 등)
트랜잭션 매니저 예시
이전 글 참고
MemberRepositoryV2 클래스 - JDBC 사용
@Repository
public class MemberRepositoryV2 implements MemberRepository {
private final DataSource dataSource;
public MemberRepositoryV2(DataSource dataSource) {
this.dataSource = dataSource;
}
private Connection getConnection() throws SQLException {
//트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 한다.
//트랜잭션 동기화 매니저가 관리하는 커넥션이 있으면 해당 커넥션을 반환한다.
//트랜잭션 동기화 매니저가 관리하는 커넥션이 없는 경우 새로운 커넥션을 생성해서 반환한다.
Connection con = DataSourceUtils.getConnection(dataSource);
return con;
}
private void close(Connection con, Statement stmt, ResultSet rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
//트랜잭션 동기화를 사용하려면 DataSourceUtils를 사용해야 한다.
//트랜잭션을 사용하기 위해 동기화된 커넥션은 커넥션을 닫지 않고 그대로 유지해준다.
//트랜잭션 동기화 매니저가 관리하는 커넥션이 없는 경우 해당 커넥션을 닫는다.
DataSourceUtils.releaseConnection(con, dataSource);
}
//save(Member member)...
//findById(String memberId)...
//update(String memberId, int money)....
//delete(String memberId)....
}
MemberServiceV2 클래스
@Service
public class MemberServiceV2 {
private final PlatformTransactionManager transactionManager;
private final MemberRepository memberRepository;
public MemberServiceV2(PlatformTransactionManager transactionManager, MemberRepository memberRepository) {
this.transactionManager = transactionManager;
this.memberRepository = memberRepository;
}
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
//트랜잭션 시작
//TransactionStatus status를 반환
// 현재 트랜잭션의 상태 정보가 포함되어 있다. 이후 트랜잭션을 커밋, 롤백할 때 필요하다.
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
bizLogic(fromId, toId, money); //비즈니스 로직
transactionManager.commit(status); //성공시 커밋
} catch (Exception e) {
transactionManager.rollback(status); //실패시 롤백
throw new IllegalStateException(e);
}
}
private void bizLogic(String fromId, String toId, int money) throws SQLException {
Member fromMember = memberRepository.findById(fromId);
Member toMember = memberRepository.findById(toId);
memberRepository.update(fromId, fromMember.getMoney() - money);
memberRepository.update(toId, toMember.getMoney() + money);
}
}
트랜잭션 템플릿
문제 3 : 서비스 계층에서 트랜잭션 적용 코드(트랜잭션 시작, 비즈니스 로직 실행, 성공 시 커밋, 실패 시 롤백)에 반복이 있던 문제
-> 해결 : 템플릿 콜백 패턴
스프링은 템플릿 콜백 패턴을 지원하는 TransactionTemplate 클래스를 제공한다.
템플릿 콜백 패턴이란 코드의 반복적인 구조와 변하지 않는 부분을 템플릿으로 만들고, 변화가 필요한 부분만을 콜백으로 분리하여 제공하는 설계 패턴이다.
TransactionTemplate
package org.springframework.transaction.support;
public class TransactionTemplate {
private PlatformTransactionManager transactionManager;
// 응답 값이 있을 때 사용
public <T> T execute(TransactionCallback<T> action) {..}
// 응답 값이 없을 때 사용
void executeWithoutResult(Consumer<TransactionStatus> action) {..}
...
}
트랜잭션 템플릿 예시
MemberRepositoryV3 클래스
@Repository
public class MemberRepositoryV3 implements MemberRepository {
// MemberRepositoryV2와 동일
}
MemberServiceV3 클래스
@Service
public class MemberServiceV3 {
private final TransactionTemplate txTemplate;
private final MemberRepository memberRepository;
// TransactionTemplate을 사용하려면 transactionManager가 필요하다.
// 생성자에서 transactionManager를 주입 받으면서 TransactionTemplate을 생성
public MemberServiceV3(PlatformTransactionManager transactionManager, MemberRepository memberRepository) {
this.txTemplate = new TransactionTemplate(transactionManager);
this.memberRepository = memberRepository;
}
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
txTemplate.executeWithoutResult((status) -> {
try {
bizLogic(fromId, toId, money); //비즈니스 로직
} catch (SQLException e) {
throw new IllegalStateException(e);
}
});
}
private void bizLogic(String fromId, String toId, int money) throws SQLException {
Member fromMember = memberRepository.findById(fromId);
Member toMember = memberRepository.findById(toId);
memberRepository.update(fromId, fromMember.getMoney() - money);
memberRepository.update(toId, toMember.getMoney() + money);
}
}
accountTransfer 메소드에서 예외를 처리하기 위해 try~catch가 들어갔는데, bizLogic() 메소드를 호출하면 SQLException 체크 예외를 넘겨준다. 해당 람다에서 체크 예외를 밖으로 던질 수 없기 때문에 언체크(런타임) 예외로 바꾸어 던지도록 예외를 전환했다.
참고 자료
“김영한 스프링 DB 1편”
-
-
(Spring) - 빈과 컴포넌트 스캔
Spring MVC 살펴보기, 스프링이 제공하는 ServletContainerInitializer 먼저 읽고 오자.
빈(Bean)
스프링에서 빈은 스프링 컨테이너(=IoC 컨테이너=DI 컨테이너)에 의해 관리되는 객체이다.
스프링 빈은 일반적인 객체와는 달리 스프링 프레임워크의 기능과 규칙에 따라 동작하는 특별한 객체로 볼 수 있다.
스프링 컨테이너는 이러한 빈들을 생성 및 관리하고, 필요에 따라 의존성을 주입하여 개발자가 객체의 생명주기와 관련된 복잡한 작업을 처리하도록 도와준다.
특징
스프링 빈은 개발자가 직접 생성하고 관리하는 것이 아니라, 스프링 컨테이너에 의해 생성되고 관리된다. 개발자는 빈의 설정을 정의하고, 스프링 컨테이너는 이를 바탕으로 빈을 생성하고 관리한다.
스프링 빈은 의존 객체를 직접 생성하고 주입할 필요 없이 의존성 주입(DI)을 통해 다른 빈들과의 관계를 설정할 수 있다. 의존성 주입은 xml 설정, 자바 설정(@Configuration과 @Bean), @Autowired 어노테이션 등을 통해 이루어진다.
스프링 빈은 기본적으로 Singleton 스코프를 가지며, 다양한 스코프(Prototype, Request, Session 등)를 설정할 수 있다. 스코프는 빈을 생성하기 위한 방법을 말하며, Singleton 스코프는 스프링 컨테이너당 하나의 인스턴스만 존재함을 의미한다.
스프링 빈 등록 방법
xml 설정
자바 설정(@Configuration과 @Bean)
어노테이션(@Component, @Service, @Controller 등) - 컴포넌트 스캔
자바 설정 간단한 예시
public class MemoryMemberRepository implements MemberRepository
{
private static Map<Long, Member> store = new HashMap<>();
@Override
public void save(Member member)
{ store.put(member.getId(), member); }
}
public class MemberServiceImpl implements MemberService
{
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository)
{ this.memberRepository = memberRepository; }
@Override
public void join(Member member)
{ memberRepository.save(member); }
}
@Configuration
public class AppConfig
{
@Bean
public MemberRepository memberRepository()
{
System.out.println("memberRepository() 호출");
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService()
{
return new MemberServiceImpl(memberRepository());
}
}
public class Example
{
public static void main(String[] args)
{
ApplicationContext ac
= new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
MemberService memberService
= ac.getBean("memberService", MemberService.class);
Member member = new Member("A", 20);
memberService.join(member);
}
}
ApplicationContext를 스프링 컨테이너라고 한다.
스프링 컨테이너는 AppConfig를 설정 정보로 사용하며, 여기서 @Bean이 붙은 모든 메소드를 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다. (@Bean이 붙은 메소드명 = 스프링 빈의 이름)
스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
설정 정보(AppConfig.class)에서 @Configuration 없이 @Bean만 적용하면 어떻게 될까?
@Bean만 사용해도 스프링 빈으로 등록되지만, 싱글톤을 보장하지 않는다. memberService() 메소드의 memberRepository() 메소드처럼, 의존관계 주입이 필요해서 메소드를 직접 호출할 때 싱글톤을 보장하지 않는다. 쉽게 얘기해서, “memberRepository() 호출” 메시지가 2번 출력될 수 있다는 말이다.
반면, @Configuration을 적용하면 @Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환한다. 즉, 싱글톤이 보장된다. 쉽게 얘기해서, “memberRepository() 호출” 메시지는 1번만 출력된다는 말이다.
-> 스프링 설정 정보는 항상 @Configuration을 사용하자.
컴포넌트 스캔
컴포넌트 스캔은 xml 설정 정보(‘bean’ 태그)나 자바 설정 정보(@Configuration과 @Bean)가 없어도 자동으로 스프링 빈을 등록할 수 있는 기능이다.
컴포넌트 스캔을 사용하려면 설정 정보에 @ComponentScan을 붙여주면 된다.(기존의 자바 설정 정보와는 다르게 @Bean으로 등록한 클래스가 없다.)
@ComponentScan은 탐색 위치에 @Component 어노테이션이 붙은 모든 클래스를 스캔해서 스프링 빈으로 등록한다.
기본 스캔 대상
컴포넌트 스캔은 @Component뿐만 아니라 아래의 내용도 추가로 대상에 포함한다.(아래의 내용의 코드들을 보면 @Component를 포함하고 있다.)
@Controller : 스프링 MVC 컨트롤러에서 사용
@Service : 스프링 비즈니스 로직에서 사용
@Repository : 스프링 데이터 접근 계층에서 사용
@Configuration : 스프링 설정 정보에서 사용
탐색 위치
@ComponentScan(basePackages = "hello.core", )
basePackages : 탐색할 패키지의 시작 위치(src/main/java 이후의 위치)를 지정하며, 해당 패키지를 포함해서 하위 패키지를 모두 탐색한다. 여러 개의 시작 위치를 지정할 수도 있다.
basePackageClasses : 지정한 클래스가 속한 패키지를 탐색 시작 위치로 지정한다. 하위 패키지도 모두 탐색한다.
패키지 위치를 지정하지 않으면, @ComponentScan이 붙은 설정 정보 클래스가 속한 패키지가 시작 위치가 된다. 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 방법이 자주 쓰인다.
컴포넌트 스캔 간단한 예시 (위의 자바 설정 예시 수정)
@Component
public class MemoryMemberRepository implements MemberRepository
{ ... }
@Component
public class MemberServiceImpl implements MemberService
{
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository)
{ this.memberRepository = memberRepository; }
...
}
@Configuration
@ComponentScan
public class AppConfig
{ }
public class Example
{
public static void main(String[] args)
{
ApplicationContext ac
= new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
MemberService memberService
= ac.getBean("memberService", MemberService.class);
Member member = new Member("A", 20);
memberService.join(member);
}
}
@Autowired
@Autowired는 의존관계를 자동으로 주입해준다.
생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다. 여러 의존관계도 한번에 주입받을 수 있다.
Spring MVC의 컴포넌트 스캔
SpringServletContainerInitializer(WebApplicationInitializer 직접 구현)를 바탕으로 두 가지 예시를 살펴보자.
DispatcherServlet WebApplicationContext만 사용하는 경우
Root WebApplicationContext와 DispatcherServlet WebApplicationContext를 사용하는 경우
단일 WebApplicationContext의 경우
DispatcherConfig.class 작성
@Configuration
@ComponentScan
@EnableWebMvc
public class DispatcherConfig implements WebMvcConfigurer
{
// 필요한 메소드 Override
// ex1) 뷰 리졸버 구성
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
registry.viewResolver(resolver);
}
// ex2) 메시지 컨버터 구성
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter());
}
}
WebApplicationInitializer 구현
public class MyWebApplicationInitializer implements WebApplicationInitializer
{
@Override
public void onStartup(ServletContext servletContext)
{
AnnotationConfigWebApplicationContext dispatcherContext
= new AnnotationConfigWebApplicationContext();
dispatcherContext.register(DispatcherConfig.class);
ServletRegistration.Dynamic dispatcher
= servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.addMapping("/");
dispatcher.setLoadOnStartup(1);
}
}
@EnableWebMvc
@EnableWebMvc는 기본적인 Spring MVC 설정을 자동으로 활성화해준다.
예를 들어, 스프링 컨테이너를 생성할 때 입력으로 받는 설정 클래스에는 HandlerMapping, HandlerAdapter 등의 빈이 등록되어 있어야 하는데, 설정 클래스에 @EnableWebMvc를 붙여주면 해당 빈들을 자동으로 추가해주므로 개발자가 직접 빈으로 등록할 필요가 없다.
@EnableWebMvc를 사용할 때, WebMvcConfigurer 인터페이스를 구현하여 Spring MVC의 설정을 추가 설정하거나 커스터마이징할 수 있다.
예를 들어, 필요한 메소드를 오버라이드하여 인터셉터 추가, 뷰 리졸버 설정 등을 할 수 있다.
Root WebApplicationContext가 추가된 경우
AppConfig.class 작성
@Configuration
@ComponentScan(excludeFilters={@Filter(org.springframework.stereotype.Controller.class)})
public class AppConfig
{ }
DispatcherConfig.class 작성
@Configuration
@ComponentScan(basePackageClasses = AppConfig.class, useDefaultFilters=false, includeFilters={@Filter(org.springframework.stereotype.Controller.class)})
@EnableWebMvc
public class DispatcherConfig implements WebMvcConfigurer
{ }
WebApplicationInitializer 구현
public class MyWebAppInitializer implements WebApplicationInitializer
{
@Override
public void onStartup(ServletContext container)
{
// Root WebApplicationContext
AnnotationConfigWebApplicationContext rootContext
= new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
container.addListener(new ContextLoaderListener(rootContext));
// DispatcherServlet의 WebApplicationContext
AnnotationConfigWebApplicationContext dispatcherContext
= new AnnotationConfigWebApplicationContext();
dispatcherContext.register(DispatcherConfig.class);
ServletRegistration.Dynamic dispatcher
= container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.addMapping("/");
dispatcher.setLoadOnStartup(1);
}
}
컴포넌트 스캔 시점
Root WebApplicationContext가 초기화될 때 AppConfig.class의 컴포넌트 스캔이 실행된다. (ContextLoaderListener의 contextInitialized 메소드가 호출될 때)
DispatcherServlet의 WebApplicationContext가 초기화될 때 DispatcherConfig.class의 컴포넌트 스캔이 실행된다. (DispatcherServlet이 초기화될 때)
참고 자료
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html
https://stackoverflow.com/questions/28293400/spring-root-and-servlet-context-with-java-config
https://catsbi.oopy.io/c760561d-50fd-4874-aa01-17feb45fe980
https://ittrue.tistory.com/229#google_vignette
https://steady-coding.tistory.com/594
https://castleone.tistory.com/2
https://mangkyu.tistory.com/75
https://mangkyu.tistory.com/176
https://galid1.tistory.com/532
-
(Spring) - 스프링이 제공하는 ServletContainerInitializer
web.xml을 대체하는 ServletContainerInitializer 먼저 읽고 오자.
스프링은 ServletContainerInitializer 인터페이스를 구현한 클래스로 SpringServletContainerInitializer를 제공하며, 개발자가 실제 구현해야 하는 것은 WebApplicationInitializer이다.
SpringServletContainerInitializer
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer
{
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses,
ServletContext servletContext)
throws ServletException
{
...
WebApplicationInitializer initializer = (WebApplicationInitializer) var4.next();
initializer.onStartup(servletContext);
...
}
}
SpringServletContainerInitializer는 WebApplicationInitializer 구현체를 인스턴스화하고 ServletContext를 위임하는 역할을 한다.
즉, 서블릿 컨테이너가 생성한 ServletContext를 SpringServletContainerInitializer가 받고, ServletContext를 초기화하는 실제 작업은 WebApplicationInitializer 구현체가 수행하는 것이다.
동작 매커니즘
spring-web 모듈 JAR가 클래스패스에 있으면, SpringServletContainerInitializer는 서블릿 컨테이너가 시작될 때 로드되고 인스턴스화되며 onStartup 메소드가 호출된다.
JAR 서비스 API의 ServiceLoader.load(Class) 메소드가 spring-web 모듈의 /META-INF/services/jakarta.servlet.ServletContainerInitializer 파일을 감지함으로써 발생하는 것이다.
onStartup 메소드는 WebApplicationInitializer 구현체들을 찾아서 초기화하는 역할을 한다.
만약 클래스패스에서 WebApplicationInitializer 구현체를 찾을 수 없다면, onStartup 메서드는 아무 작업도 수행하지 않는다.
하나 이상의 WebApplicationInitializer 타입이 감지되면, 이 구현체들은 인스턴스화된다. 그런 다음, 각 WebApplicationInitializer 인스턴스들의 onStartup(ServletContext) 메서드가 호출된다.
WebApplicationInitializer
public interface WebApplicationInitializer
{
void onStartup(ServletContext servletContext) throws ServletException;
}
WebApplicationInitializer는 ServletContext를 프로그래밍 방식으로 구성하기 위해 구현해야 하는 인터페이스이다.
WebApplicationInitializer 인터페이스를 구현한 클래스는 서블릿 컨테이너 실행 시 SpringServletContainerInitializer에 의해 자동으로 감지되고 실행된다.
SpringServletContainerInitializer, WebApplicationInitializer 구현에서 Spring MVC와 무조건적으로 결합해야 할 필요는 없다. WebApplicationInitializer에는 Spring MVC 요소 이외에 서블릿, 필터, 리스너, 각종 필터를 등록 할 수 있다.
ApplicationContext 설정 및 DispatcherServlet 등록 예시
ApplicationContext 설정 및 DispatcherServlet 등록을 위해서 두 가지 방식을 사용할 수 있다.
WebApplicationInitializer 인터페이스를 직접 구현(implements)
WebApplicationInitializer 인터페이스를 구현한 추상 클래스인 AbstractAnnotationConfigDispatcherServletInitializer를 구현(extends)
WebApplicationInitializer 직접 구현
public class MyWebAppInitializer implements WebApplicationInitializer
{
@Override
public void onStartup(ServletContext servletContext)
{
// Root WebApplicationContext 생성
AnnotationConfigWebApplicationContext rootContext
= new AnnotationConfigWebApplicationContext();
// Root WebApplicationContext 설정 클래스 등록
rootContext.register(AppConfig.class);
// ContextLoaderListener를 등록하여 Root WebApplicationContext를 초기화
servletContext.addListener(new ContextLoaderListener(rootContext));
// DispatcherServlet의 WebApplicationContext 생성
AnnotationConfigWebApplicationContext dispatcherContext
= new AnnotationConfigWebApplicationContext();
// DispatcherServlet WebApplicationContext 설정 클래스 등록
dispatcherContext.register(DispatcherConfig.class);
// DispatcherServlet 등록 및 매핑
ServletRegistration.Dynamic dispatcher
= servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.addMapping("/");
dispatcher.setLoadOnStartup(1); // DispatcherServlet이 웹 애플리케이션 시작 시 로드되도록 설정
}
}
=> Root WebApplicationContext와 DispatcherServlet의 WebApplicationContext의 초기화 시점
Root WebApplicationContext
ContextLoaderListener에 의해 초기화되는데, ContextLoaderListener의 contextInitialized 메소드가 호출되면서 초기화된다.
또한, Root WebApplicationContext가 초기화될 때 AppConfig.class의 컴포넌트 스캔이 실행된다.
DispatcherServlet의 WebApplicationContext
DispatcherServlet에 의해 초기화되는데, DispatcherServlet이 초기화될 때 initWebApplicationContext 메소드가 호출되면서 초기화된다.
또한, DispatcherServlet의 WebApplicationContext가 초기화될 때 DispatcherConfig.class의 컴포넌트 스캔이 실행된다.
ContextLoaderListener
ContextLoaderListener는 스프링의 Root WebApplicationContext를 시작하고 종료하는 데 사용되는 부트스트랩 리스너이다. Root WebApplicationContext를 리스너로 관리해야 ServletContext의 종료 이벤트를 받았을 때 자원을 적절히 반환할 수 있다.
ContextLoaderListener는 ServletContextListener를 구현하고, ContextLoader를 상속한다. ContextLoaderListener는 ServletContextListener를 구현하여 ServletConetxt의 시작 및 종료 이벤트를 처리할 수 있다.
AbstractAnnotationConfigDispatcherServletInitializer 구현
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
@Override
protected Class<?>[] getRootConfigClasses()
{
// Root WebApplicationContext 설정 클래스 등록
return new Class<?>[] { AppConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses()
{
// DispatcherServlet WebApplicationContext 설정 클래스 등록
return new Class<?>[] { DispatcherConfig.class };
}
@Override
protected String[] getServletMappings()
{
// DispatcherServlet 매핑
return new String[] { "/" };
}
}
참고 자료
https://tomcat.apache.org/tomcat-7.0-doc/servletapi/javax/servlet/ServletContainerInitializer.html
https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/ServletContext.html
https://tomcat.apache.org/tomcat-7.0-doc/servletapi/javax/servlet/ServletRegistration.Dynamic.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/SpringServletContainerInitializer.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/WebApplicationInitializer.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/ContextLoaderListener.html
https://kimcoder.tistory.com/511
https://recordsoflife.tistory.com/490
https://escapefromcoding.tistory.com/174
https://offbyone.tistory.com/215
https://nhs0912.tistory.com/81
https://blog.naver.com/kitepc/221314687808
https://velog.io/@rolroralra/Chapter0.-Servlet-컨테이너-Spring-컨테이너
-
(Tomcat) - web.xml을 대체하는 ServletContainerInitializer (+ServletContextListener)
web.xml과 ServletContainerInitializer
web.xml : 서블릿 컨테이너가 웹 애플리케이션을 초기화할 때 필요한 정보를 제공한다.
ServletContainerInitializer : 서블릿 컨테이너가 시작될 때 ServletContainerInitializer를 구현한 클래스를 실행하여 웹 애플리케이션을 초기화한다.
ServletContext는 서블릿 컨테이너라고 볼 수 있다. 따라서 web.xml이나 ServletContainerInitializer의 핵심은 결국 ServletContext 객체를 초기화하기 위함이다.
ServletContainerInitializer
public interface ServletContainerInitializer
{
void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}
Servlet 3.0부터 ServletContainerInitializer의 등장으로 기존의 web.xml 기반 대신 코드 기반으로 서블릿 컨테이너를 초기화하는 작업(ServletContext 객체 초기화)이 가능해졌다.
ServletContainerInitializer 인터페이스를 구현한 클래스가 있으면, ServletContainerInitializer를 구현한 클래스는 서블릿 컨테이너가 시작될 때 로드되고 인스턴스화되며 onStartup 메소드가 호출된다.
ServletContainerInitializer 구현
ServletContainerInitializer 인터페이스를 구현하는 클래스를 만든 후, /META-INF/services/jakarta.servlet.ServletContainerInitializer 파일을 생성하고 ServletContainerInitializer 인터페이스를 구현한 클래스의 이름을 해당 파일에 적어준다.
ServletContainerInitializer 인터페이스를 구현하는 클래스에 @HandlesTypes 어노테이션으로 클래스를 지정해주면, 서블릿 컨테이너가 시작될 때 지정된 클래스를 찾아서 ServletContainerInitializer 인터페이스를 구현하는 클래스의 onStartup() 메소드에 지정된 클래스와 ServletContext 객체를 인자로 넣어준다.
ServletContext 객체 초기화
서블릿 컨테이너가 시작될 때 수행되는 웹 애플리케이션을 초기화하는 작업인 “ServletContext 객체 초기화”에 대해서 web.xml 방식과 ServletContainerInitializer 방식의 예시를 살펴보자.
web.xml 방식
서블릿 컨테이너가 시작되고 웹 애플리케이션이 초기화될 때, 서블릿 컨테이너는 web.xml을 읽은 후 ServletContext 객체를 초기화한다.
ServletContainerInitializer 방식
서블릿 컨테이너가 시작되고 웹 애플리케이션이 초기화될 때, 서블릿 컨테이너는 ServletContainerInitializer 인터페이스를 구현한 클래스를 찾은 후 해당 클래스를 로딩하고 인스턴스화한다. 이후 해당 클래스의 onStartup() 메소드를 호출하여 ServletContext 객체를 초기화한다.
ServletContext 객체 초기화 1 - ServletContext 초기화 파라미터 설정, 서블릿 등록 등
web.xml 방식
웹 애플리케이션 구조
/src
/main
/java
/com
/example
ExampleServlet.java
/webapp
/WEB-INF
web.xml
서블릿 클래스 생성
public class ExampleServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
System.out.println("service 메소드 호출");
}
}
web.xml 작성
<!-- ServletContext 초기화 파라미터 설정 -->
<context-param>
<param-name>contextParamName_example</param-name>
<param-value>contextParamValue_example</param-value>
</context-param>
<!-- 서블릿 등록 -->
<servlet>
<servlet-name>exampleServlet</servlet-name>
<servlet-class>com.example.ExampleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>exampleServlet</servlet-name>
<url-pattern>/example</url-pattern>
</servlet-mapping>
<!-- 필터, 리스너 등 웹 애플리케이션 구성 요소 관련 -->
...
ServletContainerInitializer 방식
웹 애플리케이션 구조
/src
/main
/java
/com
/example
ExampleServlet.java
MyServletContainerInitializer.java
MyAppInitializer.java
MyAppInitializerV1.java
/resources
/META-INF
/services
jakarta.servlet.ServletContainerInitializer
서블릿 클래스 생성
public class ExampleServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
System.out.println("service 메소드 호출");
}
}
ServletContainerInitializer 구현 클래스 생성
@HandlesTypes(MyAppInitializer.class)
public class MyServletContainerInitializer implements ServletContainerInitializer
{
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException
{
for (Class<?> myAppInitClass : classes) {
try {
MyAppInitializer initializer = (MyAppInitializer) myAppInitClass.getDeclaredConstructor.newInstance();
initializer.onStartup(servletContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
MyAppInitializer 인터페이스 작성 및 구현
public interface MyAppInitializer
{
void onStartUp(ServletContext servletContext);
}
public class MyAppInitializerV1 implements MyAppInitializer
{
@Override
public void onStartUp(ServletContext servletContext)
{
// setInitParamater와 setAtturibute는 용도와 동작 방식 측면에서 여러 차이가 있음
// setInitParameter()
// - 설정 후 변경 불가 -> 이미 해당 초기화 파라미터가 설정되있으면 실패함
// - 값은 문자열 형식
// - 설정은 주로 초기화 시에 한 번만 수행
// - 웹 애플리케이션의 전역 설정에 주로 사용
// - web.xml의 context-param 태그와 유사함 -> 읽기 전용
// setAttribute()
// - 설정, 변경, 제거 가능
// - 값은 객체 형식
// - 런타임 동안 동적으로 관리
// - 애플리케이션 컴포넌트 간 데이터 공유 및 상태 저장에 주로 사용
// ServletContext 초기화 파라미터 설정
servletContext.setInitParameter("contextParamName_example", "contextParamValue_example");
// 서블릿 등록
ServletRegistration.Dynamic exampleServlet = servletContext.addServlet("exampleServlet", new ExampleServlet());
exampleServlet.addMapping("/example");
}
}
/META-INF/services/jakarta.servlet.ServletContainerInitializer 파일 생성 후 아래 내용 작성
com.example.MyServletContainerInitializer
=> ServletContainerInitializer, web.xml의 핵심은 결국 ServletContext 객체를 초기화하는 것
ServletContainerInitializer 방식의 코드(MyAppInitializerV1.class)에서 서블릿을 등록하는 부분인 servletContext.addServlet()를 보면, ServletContext 객체에 서블릿을 등록하는 형태로 이루어진다. 즉, 서블릿을 ServletContext 객체에 등록하는 것이 서블릿 등록의 본질이라고 볼 수 있다.마찬가지로, ServletContext 초기화 파라미터 설정은 물론, 필터나 리스너 등록 등의 작업도 결국은 ServletContext 객체에 등록하는 작업이다.
web.xml 방식에서도 서블릿, 필터, 리스너, ServletContext 초기화 파라미터 등의 설정을 작성해두면, 해당 설정들이 ServletContext 객체에 등록된다. 따라서, ServletContainerInitializer와 web.xml은 ServletContext 객체를 초기화하는 작업을 수행하는 방식이 다를 뿐, 결과적으로 두 방식 모두 ServletContext 객체를 초기화한다.
ServletContext 객체 초기화 2 - 서블릿에 대한 추가 설정
web.xml 방식
web.xml 작성
<servlet>
<servlet-name>exampleServlet</servlet-name>
<servlet-class>com.example.ExampleServlet</servlet-class>
<!-- 서블릿 초기화 파라미터 설정 -->
<init-param>
<param-name>servletParamName_example</param-name>
<param-value>servletParamValue_example</param-value>
</init-param>
<!-- 서블릿 초기화 시점 설정 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 서블릿 매핑 -->
<servlet-mapping>
<servlet-name>exampleServlet</servlet-name>
<url-pattern>/example</url-pattern>
</servlet-mapping>
ServletContainerInitializer 방식
MyAppInitializer 구현
public class MyAppInitializerV1 implements MyAppInitializer
{
@Override
public void onStartUp(ServletContext servletContext)
{
ServletRegistration.Dynamic exampleServlet = servletContext.addServlet("exampleServlet", new ExampleServlet());
// 서블릿 초기화 파라미터 설정
exampleServlet.setInitParameter("servletParamName_example", "servletParamValue_example");
// 서블릿 초기화 시점 설정
exampleServlet.setLoadOnStartup(1);
// 서블릿 매핑
exampleServlet.addMapping("/example");
}
}
=> 서블릿 초기화 파라미터 설정, 서블릿 매핑 등의 서블릿에 대한 추가 설정 작업도 ServletContext 객체를 초기화하는 작업의 일부로 간주
ServletContainerInitializer 방식의 코드(MyAppInitializerV1.class)를 보면, ServletContext 객체에 서블릿을 등록할 때 addServlet() 메소드가 사용되고 이 메소드는 ServletRegistration.Dynamic 객체를 반환한다. 즉, ServletRegistration.Dynamic 객체는 ServletContext를 통해 반환되며, 반환된 ServletRegistration.Dynamic 객체를 통해 서블릿에 대한 추가 설정을 수행할 수 있다.
ServletContext의 getServletRegistration() 메소드를 사용하여 등록된 서블릿의 정보를 가져올 수 있다.
3. @WebServlet 어노테이션 방식
서블릿 클래스 생성(@WebServlet)
@WebServlet(name = "exampleServlet",
urlPatterns = "/example",
initParams = {@WebInitParam(name = "servletParamName_example", value = "servletParamValue_example")},
loadOnStartup = 1)
public class ExampleServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
System.out.println("service 메소드 호출");
}
}
@WebServlet을 사용하면 web.xml이나 ServletContainerInitializer 없이 서블릿 등록 및 매핑, 서블릿 초기화 파라미터 설정 등 서블릿에 대한 설정을 할 수 있다.
@WebServlet은 개별 서블릿에 대한 간편한 설정을 제공하지만, 리스너 설정이나 ServletContext 객체의 초기화 파라미터 설정 등 복잡한 설정이나 웹 애플리케이션 전체 설정이 필요한 경우 web.xml이나 ServletContainerInitializer가 필요하다.
ServletContextListener
public interface ServletContextListener extends EventListener
{
default void contextInitialized(ServletContextEvent sce)
{
// 웹 애플리케이션이 초기화(시작)될 때 호출
// 필터나 서블릿이 초기화되기 전에 호출됨
}
default void contextDestroyed(ServletContextEvent sce)
{
// 웹 애플리케이션이 종료될 때 호출
// 모든 필터와 서블릿이 파괴된 후에 호출됨
}
}
ServletContextListener 인터페이스를 구현한 클래스는 웹 애플리케이션의 ServletContext가 변경될 때 알림을 받는다.
ServletContextListener는 ServletContext의 시작 및 종료 이벤트를 처리할 수 있으며, 웹 애플리케이션의 초기화(시작) 및 종료 시에 특정 작업을 수행하기 위해 사용된다.
ServletContextListener는 주로 웹 애플리케이션 초기화 및 종료 시 필요한 설정 작업을 수행한다. 데이터베이스 연결을 설정하거나, 로깅을 구성하는 등의 다양한 작업을 포함한다.
ServletContextListener 예시
ServletContextListener를 구현한 클래스는 web.xml 설정, @WebListener 어노테이션, ServletContext의 addListener() 메소드를 통해 등록한다.
web.xml 방식
ServletContextListener 인터페이스 구현 클래스 생성
public class MyServletContextListener implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
// JDBC 드라이버 로드
Class.forName("com.mysql.cj.jdbc.Driver");
// 데이터베이스 연결 설정
String dbUrl = sce.getServletContext().getInitParameter("DB_URL");
Connection connection = DriverManager.getConnection(dbUrl, "username", "password");
// ServletContext에 연결 객체 저장
sce.getServletContext().setAttribute("DBConnection", connection);
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
connection.close();
}
}
web.xml 작성
<listener>
<listener-class>com.example.MyServletContextListener</listener-class>
</listener>
어노테이션 방식
ServletContextListener 인터페이스 구현 클래스 생성(@WebListener)
@WebListener
public class MyServletContextListener implements ServletContextListener
{
// 위와 동일
}
addListener() 방식 - ServletContainerInitializer
ServletContextListener 인터페이스 구현 클래스 생성
public class MyServletContextListener implements ServletContextListener
{
// 위와 동일
}
MyAppInitializer 구현
public class MyAppInitializerV1 implements MyAppInitializer
{
@Override
public void onStartUp(ServletContext servletContext)
{
// ServletContext를 통해 리스너 등록
servletContext.addListener(new MyServletContextListener());
}
}
ServletContainerInitializer와 ServletContextListener
ServletContainerInitializer VS ServletContextListener
목적
ServletContainerInitializer : 서블릿 및 필터 등록 및 매핑, 리스너 등록 등
ServletContextListener : 데이터베이스 연결 설정 및 해제, 로그 설정 등
호출 시점
ServletContainerInitializer : 서블릿 컨테이너 시작 시 호출
ServletContextListener : 웹 애플리케이션 초기화(시작) 및 종료 시 호출
ServletContainerInitializer의 onStartUp 메소드와 ServletContextListener의 contextInitialized 메소드 호출 순서
서블릿 컨테이너가 시작되면 서블릿 컨테이너는 ServletContainerInitializer 구현 클래스의 onStartUp(ServletContext servletContext) 메소드를 호출
onStartUp 메소드에 전달되는 ServletContext 객체는 기본적인 초기화가 완료된 상태이며, onStartUp 메소드를 통해 추가적인 초기화 작업(서블릿, 필터, 리스너 등록 등)을 수행한다.
onStartUp 메소드의 실행이 끝난 후, 서블릿 컨테이너는 등록된 모든 ServletContextListener 구현 클래스의 contextInitialized 메소드를 호출
즉, ServletContextListener의 contextInitialized 메소드가 호출되는 시점은 ServletContainerInitializer나 web.xml을 통해 ServletContext 객체가 초기화된 직후이다.
참고 자료
https://tomcat.apache.org/tomcat-7.0-doc/servletapi/javax/servlet/ServletContainerInitializer.html
https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/ServletContext.html
https://tomcat.apache.org/tomcat-7.0-doc/servletapi/javax/servlet/ServletRegistration.Dynamic.html
https://tomcat.apache.org/tomcat-8.5-doc/servletapi/javax/servlet/ServletContextListener.html
https://docs.oracle.com/javaee%2F6%2Fapi%2F%2F/javax/servlet/ServletContextListener.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/SpringServletContainerInitializer.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/WebApplicationInitializer.html
https://kimcoder.tistory.com/511
https://recordsoflife.tistory.com/490
https://escapefromcoding.tistory.com/174
https://offbyone.tistory.com/215
https://nhs0912.tistory.com/81
https://blog.naver.com/kitepc/221314687808
https://velog.io/@rolroralra/Chapter0.-Servlet-컨테이너-Spring-컨테이너
https://amy-it.tistory.com/86
https://erjuer.tistory.com/20
https://scshim.tistory.com/399
-
(Tomcat) - Servlet Container (feat. ServletContext, ServletConfig)
서블릿 컨테이너와 웹 애플리케이션
서블릿 컨테이너는 웹 애플리케이션을 실행하고 관리하는 데 사용된다.
서블릿 컨테이너는 여러 개의 웹 애플리케이션을 호스팅할 수 있으며, 웹 애플리케이션은 서블릿 컨테이너 내에서 독립적으로 실행된다.
ServletContext
public interface ServletContext {
...
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
...
URL getResource(String var1) throws MalformedURLException;
InputStream getResourceAsStream(String var1);
...
Object getAttribute(String var1);
void setAttribute(String var1, Object var2);
void removeAttribute(String var1);
...
RequestDispatcher getRequestDispatcher(String var1);
...
void log(String var1);
void log(String var1, Throwable var2);
...
}
ServletContext 인터페이스는 서블릿이 서블릿 컨테이너와 통신하는 데 사용되는 메소드들을 정의한다.
ServletContext 인터페이스를 구현한 클래스는 ApplicationContext(Facade) 클래스이다. (org.apache.catalina.core.ApplicationContext)
ServletContext를 서블릿 컨테이너라고 볼 수 있다.
ServletContext 객체는 서블릿 컨테이너가 시작될 때 서블릿 컨테이너에 의해 각 웹 애플리케이션마다 한 개 생성된다. 서블릿 컨테이너가 종료될 때 소멸된다.
ServletContext 객체는 웹 애플리케이션 전체의 자원 및 설정에 대한 정보를 제공한다.
서블릿은 ServletContext 객체를 통해 서블릿 컨테이너와 통신하여 다양한 작업(파일 접근, 자원 바인딩, 로그 파일 쓰기 등)을 수행할 수 있다.
서블릿이 초기화될 때 서블릿 컨테이너는 해당 서블릿에 대한 초기화 정보를 담은 ServletConfig 객체를 생성하여 서블릿에게 제공하는데, 서블릿은 ServletConfig의 getServletContext() 메소드를 통해 ServletContext 객체를 얻을 수 있다.
ServletConfig
public interface ServletConfig {
String getServletName();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
ServletContext getServletContext();
}
ServletConfig 인터페이스는 서블릿 컨테이너가 서블릿을 초기화할 때 서블릿에게 전달하는 정보에 대한 메소드들을 정의한다.
ServletContext 인터페이스를 구현한 클래스는 StandardWrapper(Facade) 클래스이다. (org.apache.catalina.core.StandardWrapper)
ServletConfig 객체는 서블릿 컨테이너가 서블릿을 초기화할 때 서블릿 컨테이너에 의해 각 서블릿마다 한 개 생성된다. 서블릿이 소멸될 때 함께 소멸된다.
ServletConfig 객체는 ServletContext에 대한 참조와 서블릿에 대한 초기화 매개변수 등을 제공한다.
서블릿 초기화 시점
서블릿 컨테이너가 실행될 때 초기화 (‘load-on-startup’ 속성이 설정된 경우)
최초 HTTP 요청이 들어올 때 초기화 (‘load-on-startup’ 속성이 설정되지 않은 경우)
서블릿 초기화 과정
서블릿 컨테이너가 서블릿 클래스를 로드
서블릿 컨테이너가 서블릿 클래스의 인스턴스를 생성
서블릿 컨테이너가 ServletConfig 객체를 생성
서블릿 컨테이너가 서블릿 인스턴스의 init(ServletConfig config) 메소드를 호출
서블릿의 초기화 메소드 (GenericServlet)
// HttpServlet 클래스의 부모 클래스인 GenericServlet 클래스
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable
{
...
private transient ServletConfig config;
// 서블릿 컨테이너가 먼저 호출하는 메소드
public void init(ServletConfig config) throws ServletException
{
// 전달받은 ServletConfig 객체를 현재 서블릿 인스턴스의 인스턴스 변수 config에 저장
this.config = config;
// init() 메소드 호출
this.init();
}
// 기본 구현으로 아무 작업도 하지않으며, 서블릿에서 필요에 따라 Override
public void init() throws ServletException { }
...
public ServletConfig getServletConfig()
{ return this.config; }
public ServletContext getServletContext()
{
// ServletConfig의 getServletContext() 메소드
return this.getServletConfig().getServletContext();
}
...
}
위와 같이 GenericServlet 클래스를 보면, 서블릿의 초기화 메소드는 init(ServletConfig config), init() 두 가지가 있다.
서블릿 컨테이너는 서블릿을 초기화할 때 init(ServletConfig config) 메소드를 먼저 호출한다.
서블릿에서 초기화 메소드 Override
두 가지 초기화 메소드를 모두 Override 하지 않음
서블릿 컨테이너가 서블릿을 초기화할 때, GenericServlet의 init(ServletConfig config)을 먼저 호출하고 해당 메소드에서 GenericServlet의 init()을 호출함
init()을 Override
서블릿 컨테이너가 서블릿을 초기화할 때, GenericServlet의 init(ServletConfig config)을 먼저 호출하고 해당 메소드에서 서블릿이 Override한 init()을 호출함
init(ServletConfig config)를 Override
서블릿 컨테이너가 서블릿을 초기화할 때, 서블릿에서 Override한 init(ServletConfig config)을 호출함
이 경우, init(ServletConfig config)에서 super.init(config)를 작성하지 않으면 해당 서블릿의 인스턴스에서는 ServletConfig 객체가 GenericServlet의 private 변수 config에 저장되지 않으며, ServletConfig 객체가 변수에 저장되지 않으면 서블릿에서는 ServletContext 객체에 접근할 수가 없다.
ServletContext, ServletConfig 예시
서블릿에서 초기화 메소드를 Override하지 않았을 때, ServletConfig, ServletContext 객체를 가져오고 사용하기
public class ExampleServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// ServletConfig 객체 가져오기
// GenericServlet의 getServletConfig() 메소드
SerlvetConfig config = getServletConfig();
// ServletContext 객체 가져오기
// GenericServlet의 getServletContext() 메소드
ServletContext context = getServletContext();
// 서블릿의 초기화 매개변수 가져오기
String configParamValue =
config.getInitParameter("configParamName");
// 웹 애플리케이션의 초기화 매개변수 가져오기
String contextParamValue =
context.getInitParameter("contextParamName");
}
}
참고 자료
https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/ServletContext.html
https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/ServletConfig.html
https://javaee.github.io/javaee-spec/javadocs/javax/servlet/Servlet.html
https://javaee.github.io/javaee-spec/javadocs/javax/servlet/ServletConfig.html
https://javaee.github.io/javaee-spec/javadocs/javax/servlet/ServletContext.html
https://vibeee.tistory.com/140
https://ckddn9496.tistory.com/47
https://velog.io/@cocodori/ServletContext-ServletConfig
https://kgvovc.tistory.com/38
https://blog.naver.com/crint/90068104505
https://codevang.tistory.com/193
-
(Tomcat) - Tomcat의 요청 처리 과정
Tomcat의 요청 처리 과정 (필터 X)
Client가 Web Server로 HTTP Request 보냄
정적 콘텐츠의 경우 Web Server에서 처리, 동적 콘텐츠의 경우 Web Server가 Servlet Container로 요청 전달 - (1)
요청을 받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성
Servlet Container는 주로 web.xml 또는 어노테이션 또는 ServletContainerInitializer 인터페이스의 구현체를 통해서 요청한 URL에 맞는 서블릿을 찾음 - (2)
Servlet Container에 해당 서블릿 인스턴스가 없는 경우
Servlet Container가 서블릿 인스턴스를 생성하고 서블릿의 init() 메소드 호출
Servlet Container에 해당 서블릿 인스턴스가 있는 경우
서블릿 인스턴스를 다시 생성하지 않고 재사용 (싱글톤 패턴)
Servlet Container는 요청을 처리할 Thread를 Thread Pool에서 가져옴 (멀티 스레드 환경)
Thread는 서블릿의 service() 메소드 호출 - (3)
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
6-1. service() 메소드에서는 요청에 따라 doGET() 또는 doPost() 메소드 호출
6-2. 응답 값을 HttpServletResponse 객체에 담음
Servlet Container는 응답 값을 HTTP Response로 바꾼 후 Web Server로 전송
HttpServletRequest, HttpServletResponse 객체를 소멸시키고 Thread를 종료
Tomcat의 요청 처리 과정 (필터 O)
Client가 Web Server로 HTTP Request 보냄
정적 콘텐츠의 경우 Web Server에서 처리, 동적 콘텐츠의 경우 Web Server가 Servlet Container로 요청 전달 - (1)
요청을 받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성
Servlet Container는 주로 web.xml 또는 어노테이션 또는 ServletContainerInitializer 인터페이스의 구현체를 통해서 요청한 URL에 맞는 필터와 서블릿을 찾음 - (2)
필터
Tomcat이 시작될 때 Servlet Container는 모든 필터 인스턴스를 생성(싱글톤 패턴)하고 init() 메소드 호출
Servlet Container에 해당 서블릿 인스턴스가 없는 경우
Servlet Container가 서블릿 인스턴스를 생성하고 서블릿의 init() 메소드 호출
Servlet Container에 해당 서블릿 인스턴스가 있는 경우
서블릿 인스턴스를 다시 생성하지 않고 재사용 (싱글톤 패턴)
Servlet Container는 FilterChain을 생성 - (3)
FilterChain은 필터들과 서블릿의 연결고리라고 볼 수 있음
Servlet Container는 요청을 처리할 Thread를 Thread Pool에서 가져옴 (멀티 스레드 환경)
Thread는 첫 번째 필터의 doFilter() 메소드 호출 - (4)
@Override
public void doFilter(ServletRequest request,ServletResponse response,
FilterChain chain) throws ServletException, IOException {
...
chain.doFilter(request, response);
}
7-1. chain.doFilter(request, response)에서는 다음 필터가 있으면 필터를 호출하고, 다음 필터가 없으면 서블릿 service() 메소드 호출
7-2. service() 메소드에서는 요청에 따라 doGET() 또는 doPost() 메소드 호출
7-3. 서블릿의 service() 메소드가 끝나면 이전 필터로 돌아가고, 반복해서 첫 번째 필터까지 돌아감
7-4. 응답 값을 HttpServletResponse 객체에 담음
Servlet Container는 응답 값을 HTTP Response로 바꾼 후 Web Server로 전송
HttpServletRequest, HttpServletResponse 객체를 소멸시키고 Thread를 종료
참고 자료
https://ko.wikipedia.org/wiki/웹_컨테이너
https://ko.wikipedia.org/wiki/아파치_톰캣
https://itwiki.kr/w/아파치_톰켓
https://docs.spring.io/spring-security/reference/servlet/architecture.html
https://www.inflearn.com/questions/1132952/servlet%EC%97%90-%EB%8C%80%ED%95%B4-%EC%A0%9C-%EC%83%9D%EA%B0%81%EC%9D%84-%ED%95%9C%EB%B2%88-%EC%A0%95%EB%A6%AC%ED%95%B4%EB%B4%A4%EC%8A%B5%EB%8B%88%EB%8B%A4
https://stackoverflow.com/questions/3386254/servlets-filters-and-threads-in-tomcat
https://d-memory.tistory.com/37
https://taes-k.github.io/2020/02/16/servlet-container-spring-container/
https://forsaken.tistory.com/entry/Servlet-JSP-%EC%84%9C%EB%B8%94%EB%A6%BF-filter
https://velog.io/@bey1548/Servlet-Filter
https://curiousjinan.tistory.com/entry/spring-filterchain-dofilter
https://steady-coding.tistory.com/599
https://tecoble.techcourse.co.kr/post/2021-05-23-servlet-servletcontainer/
https://github.com/mangdo/TIL/blob/main/Spring/Controller/HttpServletRequest.md
https://velog.io/@zini9188/Spring-Security-Filter%EC%99%80-FilterChain
https://cbw1030.tistory.com/215
https://velog.io/@yoho98/%EC%84%9C%EB%B8%94%EB%A6%BFServlet%EA%B3%BC-%EC%84%9C%EB%B8%94%EB%A6%BF-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88Servlet-Container-y88kny7g
https://forsaken.tistory.com/entry/Servlet-JSP-%EC%84%9C%EB%B8%94%EB%A6%BF-filter
-
(Spring) - Spring 살펴보기 (+WebApplicationContext)
Spring MVC와 DispatcherServlet
Spring MVC는 프론트 컨트롤러 패턴을 중심으로 설계되었으며, DispatcherServlet은 Spring MVC에서 프론트 컨트롤러 기능을 담당하는 서블릿이다.
DispatcherServlet은 모든 HTTP 요청을 받아서 공통 기능을 처리한다. 즉, 핸들러(컨트롤러)에서 공통으로 처리해야 하는 부분을 DispatcherServlet에서 핸들러(컨트롤러) 호출 전에 먼저 처리한다.(수문장 역할)
클라이언트로부터 요청이 오면 DispatcherServlet이 직접 모든 작업을 수행하는 것이 아니라, 요청을 적절한 구성 요소(delegate componenets = special beans)로 전달하여 작업을 위임한다.
위임(delegation)
위임은 한 객체가 자신이 직접 작업을 수행하지 않고, 다른 객체가 그 작업을 수행하도록 맡기는 설계 패턴을 의미한다.
간단히 말해서, A 객체가 B 객체를 가지고 있고, A 객체에서 B 객체의 메서드를 호출하여 B 객체가 작업을 수행하면, 이는 A 객체가 B 객체에게 작업을 위임하는 것이다.
특별한 빈(special bean)
특별한 빈(special bean)이란 스프링이 관리하는 객체인 빈(bean) 중에서, 스프링 프레임워크에서 정해진 역할이나 기능을 수행하기 위해 구현된 빈(bean)을 말한다.
ex) HandlerMapping, HandlerAdapter, viewResolver, HandlerExceptionResolver 등
ApplicationContext, WebApplicationContext, AnnotationConfigWebApplicationContext
ApplicationContext
ApplicationContext 인터페이스를 구현한 객체(ex) AnnotationConfigWebApplicationContext)를 스프링이 관리하는 빈들이 담겨 있는 컨테이너(스프링 컨테이너 = IoC 컨테이너 = DI 컨테이너)라고 한다.
스프링 컨테이너는 빈의 생명주기 관리, 빈의 의존성 주입(DI) 등을 담당한다.
ApplicationContext 인터페이스를 구현한 클래스는 여러 종류가 있다.
WebApplicationContext
DispatcherServlet은 자체 설정을 위해 WebApplicationContext를 필요로 한다.
WebApplicationContext는 ApplicationContext에 getServletContext() 메소드 등이 추가된 인터페이스로, 자신이 속한 ServletContext와 서블릿에 대한 링크를 가지고 있다.
AnnotationConfigWebApplicationContext
AnnotationConfigWebApplicationContext는 WebApplicationContext 인터페이스를 구현한 클래스이다.
AnnotationConfigWebApplicationContext 클래스를 사용할 때 어노테이션 기반의 자바 코드 설정을 넘기면 된다. 자바 코드로된 설정에서는 @Configuration, @Bean 등의 어노테이션을 사용하여 빈을 정의하고 구성한다.
XmlWebApplicationContext는 어노테이션 기반의 자바 코드 설정이 아닌, xml 설정 파일을 사용한다.
AnnotationConfigWebApplicationContext 예시
@Configuration
public class AppConfig
{
@Bean
public MemberRepository memberRepository()
{ return new MemoryMemberRepository(); }
@Bean
public MemberService memberService()
{ return new MemberServiceImpl(memberRepository()); }
}
public class Example
{
public static void main(String[] args)
{
// ApplicationContext ac
// = new AnnotationConfigWebApplicationContext();
// AnnotationConfigWebApplicationContext : 어노테이션 기반 자바 코드 사용
AnnotationConfigWebApplicationContext ac
= new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh(); // 컨텍스트를 초기화하여 등록된 클래스들이 처리되도록 함
// WebApplicationInitializer를 사용한다면 refresh() 메소드를 직접 호출할 필요가 없음
MemberService memberService
= ac.getBean("memberService", MemberService.class);
Member member = new Member("A", 20);
memberService.join(member);
}
}
위 코드에서, AnnotationConfigWebApplicationContext 객체를 스프링 컨테이너라고 한다.
Context Hierarchy(계층 구조)
대부분의 웹 애플리케이션은 하나의 웹 애플리케이션에서 단일 WebApplicationContext를 사용하는 것으로 충분하다. 그러나, 더 복잡한 경우에는 Context 계층 구조를 사용할 수 있다.
Context 계층 구조란, 하나의 Root WebApplicationContext가 여러 DispatcherServlet(또는 다른 서블릿)과 공유되고, 각 서블릿마다 자체적인 자식 WebApplicationContext을 가지는 구조이다.
Context 계층 구조
Root WebApplicationContext
Root WebApplicationContext는 데이터 저장소, 비즈니스 서비스와 같이 여러 서블릿에서 공유해야 하는 인프라 빈을 포함
Root WebApplicationContext의 빈은 자식 WebApplicationContext에서 상속됨
Servlet WebApplicationContext (자식 WebApplicationContext)
각 서블릿에 특화된 빈을 포함
Root WebApplicationContext의 빈을 상속함
필요에 따라 Root WebApplicationContext의 빈을 재정의 가능
참고 자료
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/special-bean-types.html
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/context-hierarchy.html
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/container-config.html
https://www.inflearn.com/questions/226425/servletcontext%EC%99%80-webapplicationcontext%EC%9D%98-%EA%B4%80%EA%B3%84-%EC%A7%88%EB%AC%B8
https://blog.naver.com/kitepc/221314687808
https://escapefromcoding.tistory.com/174
https://mangkyu.tistory.com/18
https://kimcoder.tistory.com/511
https://recordsoflife.tistory.com/490
https://blog.naver.com/PostView.nhn?blogId=inho1213&logNo=220516464519&parentCategoryNo=&categoryNo=19&viewDate=&isShowPopularPosts=false&from=postView
https://live-everyday.tistory.com/164
https://velog.io/@ruinak_4127/Context
https://dev-wnstjd.tistory.com/440
https://devlogofchris.tistory.com/71
https://unordinarydays.tistory.com/131
-
(Spring) - Spring MVC 요청 처리 과정 (+Interceptor)
Spring MVC 요청 처리 과정 (인터셉터 X)
Client가 Web Server로 HTTP Request 보냄
정적 콘텐츠의 경우 Web Server에서 처리, 동적 콘텐츠의 경우 Web Server가 Servlet Container로 요청 전달 - (1)
요청을 받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성
Servlet Container는 요청을 처리할 Thread를 Thread Pool에서 가져옴
Thread는 DispatcherServlet의 doDispatch() 메소드 호출(doDispatch() 메소드를 호출하기까지의 과정은 생략) - (2)
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
...
// 5-1. 핸들러 조회 - (3)
// 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러) 조회
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
...
// 5-2. 핸들러 어댑터 조회
// 핸들러를 실행할 수 있는 핸들러 어댑터 조회
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
...
// 5-3. 핸들러 어댑터 실행 - (4)
// 핸들러 어댑터가 핸들러 실행
// 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}
private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler,
@Nullable ModelAndView mv,
@Nullable Exception exception)
throws Exception
{
...
this.render(mv, request, response);
...
}
protected void render(ModelAndView mv,
HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
String viewName = mv.getViewName();
View view;
...
// 5-4. 뷰 찾기 - (5)
// 뷰 리졸버를 통해 뷰 찾기
// 뷰 리졸버는 View 반환
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
...
// 5-5. 뷰 렌더링
// HTML 등의 응답 생성
// 주로 템플릿 엔진을 통해 동적으로 생성
view.render(mv.getModelInternal(), request, response);
}
5-6. 응답 값을 HttpServletResponse 객체에 담음
Servlet Container는 응답 값을 HTTP Response로 바꾼 후 Web Server로 전송
HttpServletRequest, HttpServletResponse 객체를 소멸시키고 Thread를 종료
Spring MVC 요청 처리 과정 (인터셉터 O)
Client가 Web Server로 HTTP Request 보냄
정적 콘텐츠의 경우 Web Server에서 처리, 동적 콘텐츠의 경우 Web Server가 Servlet Container로 요청 전달 - (1)
요청을 받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성
Servlet Container는 요청을 처리할 Thread를 Thread Pool에서 가져옴
Thread는 DispatcherServlet의 doDispatch() 메소드 호출(doDispatch() 메소드를 호출하기까지의 과정은 생략) - (2)
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
...
// 5-1. 핸들러 조회 - (3)
// 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러) 조회
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
...
// 5-2. 핸들러 어댑터 조회
// 핸들러를 실행할 수 있는 핸들러 어댑터 조회
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
...
// 5-3. 인터셉터 호출(핸들러 어댑터 호출 전) - (4)
// applyPreHandle() 메소드에서 preHandle() 메소드 호출
// preHandle()의 반환 값이 true : 다음으로 진행함
// preHandle()의 반환 값이 false : 다음으로 진행하지 않음
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
...
// 5-4. 핸들러 어댑터 실행 - (5)
// 핸들러 어댑터가 핸들러 실행
// 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
// 5-5. 인터셉터 호출(핸들러 어댑터 호출 후) - (6)
// applyPostHandle() 메소드에서 postHandle() 메소드 호출
mappedHandler.applyPostHandle(processedRequest, response, mv);
...
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
}
private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler,
@Nullable ModelAndView mv,
@Nullable Exception exception)
throws Exception
{
...
this.render(mv, request, response);
...
// 5-8. 인터셉터 호출(뷰 렌더링 후) - (8)
// triggerAfterCompletion() 메소드에서 afterCompletion() 메소드 호출
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
protected void render(ModelAndView mv,
HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
String viewName = mv.getViewName();
View view;
...
// 5-6. 뷰 찾기 - (7)
// 뷰 리졸버를 통해 뷰 찾기
// 뷰 리졸버는 View 반환
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
...
// 5-7. 뷰 렌더링
// HTML 등의 응답 생성
// 주로 템플릿 엔진을 통해 동적으로 생성
view.render(mv.getModelInternal(), request, response);
}
5-9. 응답 값을 HttpServletResponse 객체에 담음
Servlet Container는 응답 값을 HTTP Response로 바꾼 후 Web Server로 전송
HttpServletRequest, HttpServletResponse 객체를 소멸시키고 Thread를 종료
참고 자료
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/context-hierarchy.html#page-title
https://codevang.tistory.com/248
https://gowoonsori.com/blog/spring/architecture/
https://velog.io/@suhongkim98/DispatcherServlet%EA%B3%BC-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%83%9D%EC%84%B1-%EA%B3%BC%EC%A0%95
https://jaeseo.tistory.com/entry/DispatcherServlet-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95%EA%B3%BC-Special-Bean-Types
https://jeonyoungho.github.io/posts/Spring-MVC-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95/
https://jaeseo.tistory.com/entry/DispatcherServlet-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95%EA%B3%BC-Special-Bean-Types
https://yoonbing9.tistory.com/80
https://velog.io/@seculoper235/3
https://appleg1226.tistory.com/31
-
(Tomcat) - Tomcat 살펴보기
Tomcat 설치 (Ubuntu)
다운로드
~$ wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.89/bin/apache-tomcat-9.0.89.tar.gz
tar.gz 압축 해제
~$ tar -zxvf apache-tomcat-9.0.89.tar.gz
다운로드 완료
Tomcat 시작
~/apache-tomcat-9.0.89/bin$ ./startup.sh
http://localhost:8080 에서 확인
Tomcat 중지
~/apache-tomcat-9.0.89/bin$ ./shutdown.sh
Tomcat 디렉토리 구조
bin : Tomcat의 시작, 중지 및 기타 관리 스크립트가 포함된 디렉토리
주요 파일
startup.sh : Tomcat 시작 스크립트
shutdown.sh : Tomcat 중지 스크립트
catalina.sh : 주요 제어 스크립트
conf : Tomcat의 설정 파일들이 포함된 디렉토리
주요 파일
server.xml : Tomcat의 전반적인 설정 파일
web.xml : 모든 웹 애플리케이션에 대한 전역 설정 파일
context.xml : 개별 웹 애플리케이션에 대한 설정 파일
lib : Tomcat을 작동하는 데 필요한 라이브러리들(.jar)이 포함된 디렉토리
logs : Tomcat의 로그 파일들이 저장되는 디렉토리
주요 파일
catalina.out : Tomcat의 표준 출력 및 표준 오류 로그 파일
temp : Tomcat이 실행 중 사용하는 임시 파일들(ex) 업로드, 캐시 파일)이 저장되는 디렉토리
webapps : 배포된 웹 애플리케이션들이 위치하는 디렉토리
work : JSP 파일을 서블릿 형태로 변환한 .java 파일과 이를 컴파일한 결과인 .class 파일이 저장되는 디렉토리
JSP는 처음 요청될 때 서블릿(.java)으로 변환되고, 이후 재사용을 위해 컴파일된 서블릿(.class)이 저장된다.
webapps 디렉토리
각 웹 애플리케이션은 webapps 디렉토리 내에 하나의 디렉토리로 존재하며, 각 웹 애플리케이션은 특정한 디렉토리 구조를 따른다.
war 파일을 webapps 디렉토리에 배치(배포)하면, Tomcat은 자동으로 war 파일의 압축을 푼다.
webapps 디렉토리 내 웹 애플리케이션 디렉토리(war 파일 압축 푼 결과)의 구조
WEB-INF : 웹 애플리케이션의 핵심 설정 파일과 자원을 포함하는 디렉토리
클라이언트가 직접 접근할 수 없다.
주요 하위 디렉토리와 파일
web.xml : 웹 애플리케이션의 서블릿, 필터, 리스너, 초기화 매개변수 등의 설정 파일
classes 디렉토리 : 웹 애플리케이션의 컴파일된 파일(.class)들이 위치하는 디렉토리
lib 디렉토리 : 웹 애플리케이션이 사용하는 외부 라이브러리 파일(.jar)들이 위치하는 디렉토리
META-INF : JAR 파일 및 웹 애플리케이션 메타데이터를 포함하는 디렉토리
기타 정적 자원 디렉토리 : 정적 파일(css, js 등)들을 포함하는 디렉토리
클라이언트가 직접 접근할 수 있다.
conf/web.xml과 webapps/WEB-INF/web.xml
공통점 : 웹 애플리케이션의 설정을 정의
서블릿, 필터, 리스너, 초기화 매개변수 등을 정의한다.
차이점 : 적용 범위와 목적
conf/web.xml : Tomcat에서 실행되는 모든 웹 애플리케이션에 적용되는 기본 설정
각 개별 웹 애플리케이션의 web.xml 파일에 정의되지 않은 설정은 여기에서 정의된 기본 설정을 따른다.
기본 서블릿이나 필터 설정을 제공하여, 모든 애플리케이션이 공통적으로 사용해야 하는 서블릿 또는 필터를 정의할 수 있다.
공통적인 보안 설정이나 JNDI 리소스 등의 설정을 정의할 수 있다.
webapps/WEB-INF/web.xml : 개별 웹 애플리케이션에만 적용되는 설정
conf/web.xml에서 정의된 전역 설정을 덮어쓰거나 추가한다.
예를 들어, 특정 서블릿이나 필터가 동일한 이름으로 두 파일에 모두 정의되어 있으면, webapps/WEB-INF/web.xml에 정의된 설정이 우선이다.
참고 자료
https://tomcat.apache.org/tomcat-5.5-doc/appdev/deployment.html
https://tomcat.apache.org/download-90.cgi
https://stackoverflow.com/questions/70216/whats-the-purpose-of-meta-inf
https://www.youtube.com/watch?v=WdBAto3IQOg&list=PLqaSEyuwXkSoeqnsxz0gYWZMihw519Kfr&index=39
https://www.youtube.com/watch?v=K84mSiC_q6I&list=PLqaSEyuwXkSoeqnsxz0gYWZMihw519Kfr&index=40
https://www.youtube.com/watch?v=aP4Lw3SfffQ&list=PLqaSEyuwXkSoeqnsxz0gYWZMihw519Kfr&index=6
https://lifesteps.tistory.com/84
https://jake-seo-dev.tistory.com/436
https://velog.io/@xangj0ng/Linux-Ubuntu-Tomcat-%EC%84%A4%EC%B9%98
https://jokerkwu.tistory.com/117
https://xzio.tistory.com/1345
https://infoinhere.tistory.com/85
https://yangbox.tistory.com/16
Touch background to close