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)한다.
- ApplicationContext를 스프링 컨테이너라고 한다.
- 설정 정보(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 : 스프링 설정 정보에서 사용
- 컴포넌트 스캔은 @Component뿐만 아니라 아래의 내용도 추가로 대상에 포함한다.(아래의 내용의 코드들을 보면 @Component를 포함하고 있다.)
- 탐색 위치
@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를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다. 여러 의존관계도 한번에 주입받을 수 있다.
- @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의 설정을 추가 설정하거나 커스터마이징할 수 있다.
- 예를 들어, 필요한 메소드를 오버라이드하여 인터셉터 추가, 뷰 리졸버 설정 등을 할 수 있다.
- @EnableWebMvc는 기본적인 Spring MVC 설정을 자동으로 활성화해준다.
- @EnableWebMvc
- DispatcherConfig.class 작성
- 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이 초기화될 때)
- 컴포넌트 스캔 시점
- AppConfig.class 작성
참고 자료
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