스프링 웹 MVC-03.스프링 IoC 컨테이너, MVC 연동(ContextLoaderListener, DispatcherServlet)


스프링 IoC 컨테이너 연동

참고: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc

서블릿 애플리케이션에 스프링 연동하기

  • 서블릿에서 스프링이 제공하는 IoC 컨테이너 활용하는 방법
  • 스프링이 제공하는 서블릿 구현체 DispatcherServlet 사용하기

    앞선 포스팅에서 사용하던 MyListner -> 스프링이 제공하는 ContextLoaderListener 사용. (web.xml)

ContextLoaderListener?

  • 서블릿 리스너 구현체
  • ApplicationContext를 만들어 준다.
  • ApplicationContext를 서블릿 컨텍스트 라이프사이클에 따라 등록하고 소멸시켜준다.
  • 서블릿에서 IoC 컨테이너를 ServletContext를 통해 꺼내 사용할 수 있다.

조금 더 자세한 설명(ContextLoaderListener)

웹 어플리케이션에 등록된 서블릿들이 스프링 IoC 컨테이너(ApplicationContext)를 사용할 수 있게 만들고,
ApplicationContext를 서블릿 컨텍스트에 등록해준다.
서블릿이 종료될 시점에 ApplicationContext를 제거해 준다.
-> 서블릿 리스너가 할 수 있는 일. (스프링이 제공해주는 ApplicationContext를 연동해주는 리스너)

서블릿 컨텍스트 라이프사이클에 맞춰서 스프링이 제공해주는 ApplicationContext를 등록하고 소멸시켜준다. (앞서 살펴본 서블릿 생명주기)
서블릿 컨텍스트와 ApplicationContext를 연동해주는 핵심적인 클래스
ApplicationContext를 사용해야 한다는 것은 ApplicationContext를 생성해야 한다는 것이다.(스프링 설정파일이 필요하다.)
ContextLoaderListener 안에 들어가 보면 다양한 파라미터들(ContextLoader 상속 - 컨텍스트 설정파일의 위치, 생성할 애플리케이선 컨텍스트의 타입 지정 등.)

즉, ContextLoaderListener는 ApplicationContext을 서블릿 컨텍스트라는 모든 서블릿들이 사용할 수 있는 공용의 저장소에 등록한다.

코드 따라가며 이해(ContextLoaderListener)

pom.xml 추가

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope> <!-- 코딩하는 시점에는 쓸 수 있으나, 런타임시점(war,jar 패키징시) 클래스패스에서 빠짐.
      톰캣 컨테이너에서 제공해줄 것이기 때문에, 런타임 시에도 클래스패스에 가지고 있지 않아도 된다. 지정해준 것 -->
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.3.RELEASE</version>
    </dependency>

web.xml

<!-- 필터보다 앞에 --> 
  <context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </context-param>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>me.jaeuk.AppConfig</param-value>
  </context-param>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

me.jaeuk.AppConfig

@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class HelloService {
    public String getName(){
        return "service jaeuk";
    }
}

ContextLoaderListener는 ContextLoader 상속받고,

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

아까 말한 다양한 파라미터들을 설정되어 있는 걸 볼 수 있다.

public class ContextLoader {
	public static final String CONTEXT_ID_PARAM = "contextId";
	public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
	public static final String CONTEXT_CLASS_PARAM = "contextClass";
	public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
	public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";
...

또 ContextLoaderListener의 contextInitialized 메서드를 보면

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
...
	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
...

initWebApplicationContext(event.getServletContext());

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		...
		try {
			...
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
      // 서블릿 컨텍스트에 등록
      ...   

서블릿 컨텍스트에 ApplicationContext를 등록하는 것을 볼 수 있다.

결과적으로 이렇게 서블릿 컨텍스트에 등록이 되면
실제 doGet/Post() 같은 메서드에서 이런 방식으로 사용한다.

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet");

        ApplicationContext context = (ApplicationContext) getServletContext();
        HelloService helloService = context.getBean(HelloService.class);
        
        System.out.println(helloService.getName());
        resp.getWriter().write("Hello Servlet " + helloService.getName());
    } 

우리가 new 해서 사용하는 것이 아니라.. 스프링 IoC 컨테이너가 관리하는 빈으로 등록된 HelloService을 꺼내다가 쓰는 것이다.

스프링 MVC 연동

디스패처 서블릿 (둘 다 상속)

  1. 서블릿 웹 어플리케이션 컨텍스트 (controller, viewResolver, HandlerMapping) - 루트 웹 어플리케이션 컨텍스트 상속
  2. 루트 웹 어플리케이션 컨텍스트 (웹과 관련된 건 없다, service, repository)

서블릿 웹 어플리케이션 컨텍스트(1) 루트 웹 어플리케이션 컨텍스트(2)을 상속해서 사용이 가능하다.

컨트롤러는 디스패처 서블릿이 만드는 애플리케이션 컨텍스트에 등록이 되어야 하고,
서비스는 ContextLoaderListener가 만들어 주는 애플리케이션 컨텍스트에 등록이 되어야 한다.

실습

@Configuration
@ComponentScan(excludeFilters = @ComponentScan.Filter(Controller.class))
public class AppConfig {
}
@Configuration
@ComponentScan(useDefaultFilters = false, includeFilters = @ComponentScan.Filter(Controller.class))
public class WebConfig {
}
<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </context-param>
  
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>me.jaeuk.AppConfig</param-value>
  </context-param>
  
  <!-- ContextLoaderListener -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 디스패처 서블릿 -->
  <servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextClass</param-name>
      <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>me.jaeuk.WebConfig</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/app/*</url-pattern>
  </servlet-mapping>

</web-app>

아니면 굳이 우리 프로젝트에선 계층구조를 나눌 필요가 없다고 하면

디스패처 서블릿만 써도 된다.

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!-- 디스패처 서블릿 -->
  <servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextClass</param-name>
      <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>me.jaeuk.WebConfig</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/app/*</url-pattern>
  </servlet-mapping>

</web-app>
@Configuration
@ComponentScan
public class WebConfig {
}





© 2019. by jaeuk