- 1014

Yung·2022년 10월 14일
0

Java223bitcamp

목록 보기
21/26

https://joont92.github.io/spring/WebApplicationInitializer/

5단계 - @Bean 애노테이션을 사용할 때 메서드 이름을 객체 이름으로 사용할 수 있다.

- com.bitcamp.board.config.AppConfig 변경

객체가 리턴한 값을 transactionManager라는 이름으로 저장한다.
@Bean 애노테이션을 붙일 때 객체 이름을 지정하면
그 이름으로 리턴 값을 컨테이너에 보관한다.
이름을 지정하지 않으면 메서드 이름으로 보관한다.

@Bean("transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource ds) {}

@Bean
public PlatformTransactionManager transactionManager(DataSource ds) {}

083. Spring WebMVC 프레임워크 사용법II : 기타 설정

  • Spring WebMVC 프레임워크의 다양한 설정법

1단계 - 웹 애플리케이션의 context path를 / 로 지정한다.

- Eclipse IDE에서 처리.

<Context docBase="board-app-server" path="/app" reloadable="true" source="org.eclipse.jst.jee.server:board-app-server"/></Host>

<Context docBase="board-app-server" path="/" reloadable="true" source="org.eclipse.jst.jee.server:board-app-server"/></Host>

- 프론트 컨트롤러의 경로를 /service 대신 /app으로 변경

ContextLoaderListener는 서블릿 ServletContextListener를 구현한 클래스이고
웹 어플리케이션이 시작할때 가장먼저 시작된다.
contextInitialized이 메소드가 가장 먼저 호출된다 누가? 서블릿 컨테이너가(톰켓)
언제? 웹어플리케이션을 스타트시작될때
자원을 준비 : Spring IoC, Front Controller, Filter를 준비시킨다.

public class ContextLoaderListener implements ServletContextListener {
  @Override
  public void contextInitialized(ServletContextEvent sce) {}
  • ContextLoaderListener 변경
	  // 변경 전
      // 자바 코드로 서블릿 객체를 직접 생성하여 서버에 등록하기
      DispatcherServlet servlet = new DispatcherServlet(iocContainer);
      Dynamic config = ctx.addServlet("DispatcherServlet", servlet);
      config.addMapping("/service/*"); // /service/* 주소에서 실행되게 매핑한다.
      config.setMultipartConfig(
          new MultipartConfigElement(this.getClass().getAnnotation(MultipartConfig.class)));
      config.setLoadOnStartup(1); //1번째, 가장 먼저 시작하는 서블릿으로 지정

	  // 변경 후 
      // 자바 코드로 서블릿 객체를 직접 생성하여 서버에 등록하기
      DispatcherServlet servlet = new DispatcherServlet(iocContainer);
      Dynamic config = ctx.addServlet("app", servlet);
      config.addMapping("/app/*"); // /app/* 주소에서 실행되게 매핑한다.
      config.setMultipartConfig(
          new MultipartConfigElement(this.getClass().getAnnotation(MultipartConfig.class)));
      config.setLoadOnStartup(1);
      // 웹 애플리케이션을 시작할 때 프론트 컨트롤러를 자동 생성.
      //1번째, 가장 먼저 시작하는 서블릿으로 지정

- JSP의 링크 경로를 조정

ctx.getContextPath() : Project Path를 가져온다
project Paht경로를 contextPath이름으로 ServletContext에 저장한다
웹 애플리케이션의 루트 경로를 ServletContext 보관소에 저장한다.

      ServletContext ctx = sce.getServletContext();    
      ctx.setAttribute("contextPath", ctx.getContextPath());
    <li><a href="${contextPath}/app/board/list">게시판</a></li>
    <li><a href="${contextPath}/app/member/list">회원</a></li>
    
    html source
    <li><a href="/app/board/list">게시판</a></li>
    <li><a href="/app/member/list">회원</a></li>

2단계 - ServletContainerInitializer 사용법

- ServletContainerInitializer Interface

ServletContainer(예: Tocmat Server)에 대해서 Web app을 시작시키면 ServletContainer는 ServletContainerInitializer(Interface)를 찾아서 메소드를 호출하는데 onStartup()를 호출한다.

ServletContainer가 Web App을 시작시키면 ServletContainerInitializer 만들어서 onStartup()를 호출한다.

  1. *.jar/META-INF/services/javax.servlet.ServletContainerInitializer 파일을 찾는다.
  2. 이 파일이 등록된 클래스의 객체를 생성한다.
  3. 생성한 구현체에 대해 Web APp.이 시작되었음을 알리는 onStartup()을 호출한다.

onStartup()호출할때 넘겨주는 파라미터가 있는데 바로 구현체가 요구한 클래스 목록, servletContext 객체

onStartup(Set<Class<?>> c, ServletContext ctx)
Notifies this ServletContainerInitializer of the startup of the application represented by the given ServletContext.

spring-web-xx.jar

/META-INF/services/javax.servlet.ServletContainerInitializer

javax.servlet.ServletContainerInitializer
org.springframework.web.SrpingServletContainerInitializer
1. onStartup()을 호출할 때 WebApplicationInitializer 구현체를 모두 찾아 그 클래스 목록을 달라고 요구하고있다.

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {}
}

서블릿컨테이너는 웹어플리케이션이 시작되면 spring-web-xx.jar파일에서 /META-INF/services/javax.servlet.ServletContainerInitializer 파일을 찾고 org.springframework.web.SrpingServletContainerInitializer 클래스의 onStartup()를 호출한다.

서블릿컨테이너는 웹어플리케이션이 시작되면 WeApplicationInitializer 인터페이스 구현체를 찾아 목록을 만든다음,
ServletContainerInitializer 규칙에 의해 만든 SpringServletContainerInitializer 클래스의 객체를 생성하고 WebApplicationInitializer 인터페이스 구현체를 찾아 만든 목록을 파라미터로 onStartup()를 호출한다.

SpringServletContainerInitializer는 WebApplicationInitializer 구현체에 대해 인스턴스를 생성한 후 onStartup을 호출한다.

ServletContainerInitializer ServletContainerInitializer를 실행하기 전에 이 리스너들이 사용할 자원을 준비시키는 역할을 한다.
: ContextLoaderListener로는 실행순서를 제어할 수 없다. 다른 ServletContainerInitializer보다 먼저 실행되게끔 만든 규칙이 ServletContainerInitializer이다.
: ServletContainerInitializer는 특히, 모든 웹 애플리케이션에 공통으로 적용할 수 있다.

WeApplicationInitializer 인터페이스

웹어플리케이션이 시작되면, 서블릿 컨테이너가(톰켓)가 ServletContainerInitializer 인터페이스를 구현한 SpringServletContainerInitializer에 대해서 onStartup()을 호출한다.
SpringServletContainerInitializer는 WeApplicationInitializer의 구현체 개수만큼 onStartup()을 호출한다.

WeApplicationInitializer 인터페이스를 구현하게되면 얻는 이점:

Spring API  : WebapplicationInitializer <--- 구현체 : Spring API에 종속

Servlet API : ServletContainerInitializer <--- 구현체 : Servlet API에 종속 : 서블릿대신 다른 플랫폼으로 교체할때 코드를 싹다 버려야한다.

ServletContainerInitializer 구현할 수도 있고 WebapplicationInitializer 구현할 수 있다.

스프링은 현제 내부적으로 Servlet API를 사용하고있지만, 또다른 다른 기술로도 교체하기 쉽게끔 하고싶어한다.
그래서 개발자가 Spring API 기술로 구현하게되면, 다른 기술 플랫폼으로 전환할때 변경할 필요가 없다.

WebApplicationInitializer 구현

ContextLoaderListener를 AppWebApplicationInitializer로 마이그레이션

<<ServletContextListener>>ContextLoaderListener
: Spring Ioc 컨테이너를 준비
: DispatcherServlet 준비
: 기타 필터 준비

migration(이사, 이전)

<<WebApplicationInitializer>>AppWebApplicationInitializer
: Spring Ioc 컨테이너를 준비
: DispatcherServlet 준비
: 기타 필터 준비

WebApplicationInitializer 인터페이스를 직접 구현해서 AppWebApplicationInitializer 구현체를 만든다.

서블릿 컨테이너에서 웹 애플리케이션이 시작할 때:
====> SpringServletContainerInitialzer.onStartup() 호출
=========> WebApplicationInitializer 구현체의 onStartup() 호출

public class AppWebApplicationInitialzer implements WebApplicationInitializer {

  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    System.out.println("AppWebApplicationInitialzer.onStartup() 호출 !!");
  }
}

Multipart 설정 정보

public class AppWebApplicationInitialzer implements WebApplicationInitializer {

  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    System.out.println("AppWebApplicationInitialzer.onStartup() 호출 !!");

    // 웹관련 컴포넌트를 다룰 수 있는 기능이 포함된 스프링 IoC 컨테이너 준비
    AnnotationConfigWebApplicationContext iocContainer =
        new AnnotationConfigWebApplicationContext();
    iocContainer.register(AppConfig.class);

    //웹 애플리케이션의 루트 경로를 ServletContext 보관소에 저장한다.
    servletContext.setAttribute("contextPath", servletContext.getContextPath());

    // 자바 코드로 서블릿 객체를 직접 생성하여 서버에 등록하기
    DispatcherServlet servlet = new DispatcherServlet(iocContainer);
    Dynamic config = servletContext.addServlet("app", servlet);
    config.addMapping("/app/*"); // /app/* 주소에서 실행되게 매핑한다.
    config.setLoadOnStartup(1); // 웹 애플리케이션을 시작할 때 프론트 컨트롤러를 자동 생성, (1)가장 먼저 시작하는 서블릿으로 지정

    // 멀티파트 설정 정보를 애노테이션에서 가져오기. (@MultipartConfig(maxFileSize = 1024 * 1024 * 10))
    config.setMultipartConfig(
        new MultipartConfigElement(this.getClass().getAnnotation(MultipartConfig.class)));
 
    // 2) 멀티파트 설정 정보를 직접 지정하기
    System.out.println(System.getProperty("java.io.tmpdir")); // 입출력 데이터를 일시적으로 보관할 폴더를 가리킨다.
    config.setMultipartConfig(new MultipartConfigElement(System.getProperty("java.io.tmpdir"), // 클라이언트가 보낸 파일을 임시 저장할 디렉토리
        1024 * 1024 * 5, // 한 파일의 최대 크기
        1024 * 1024 * 10, // 첨부 파일을 포함한 전체 요청 데이터의 최대 크기
        1024 * 1024 // 첨부 파일 데이터를 일시적으로 보관할 버퍼 크기
    ));

WebApplicationInitializer(AbstractContextLoaderInitializer)

<<interface>>																				<<concreate>>
WebApplicationInitializer         		<-- AppWebapplicationInitializer
		
AbstractContextLoaderInitializer  		<-- AppWebapplicationInitializer
	+ ContextLoaderlistener 생성
	+ Root IoC 컨테이너 준비
// 서블릿 컨테이너에서 웹 애플리케이션이 시작할 때:
// ===> SpringServletContainerInitialzer.onStartup() 호출
// ======> AppWebApplicationInitializer.onStartup() 호출
// =========> AbstractContextLoaderInitializer 구현체의 onStartup() 호출
// ============> registerContextLoaderListener() 호출
// ===============> createRootApplicationContext() 호출
// =========> IoC 컨테이너, 프론트 컨트롤러, 필터 준비
// @MultipartConfig(maxFileSize = 1024 * 1024 * 10)
public class AppWebApplicationInitialzer extends AbstractContextLoaderInitializer {

  @Override
  protected WebApplicationContext createRootApplicationContext() {
    // 당장 Root IoC 컨테이너를 생성하지 않을 것이다.
    // 따라서 null을 리턴한다.
    // null을 리턴하면 ContextLoaderListener 객체도 생성되지 않을 것이다.
    // 당연히 해당 리스너가 서블릿 컨테이너에 등록되지 않는다.
    return null;
  }

  // 수퍼클래스의 onStartup()을 재정의한다.
  // => super.onStartup(): ContextLoaderListener를 준비하는 일을 한다.
  // => + 프론트 컨트롤러와 프론트 컨트롤러에서 사용할 IoC 컨테이너를 등록한다.
  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    // 수퍼클래스가 ContextLoaderListener를 준비하는 작업은 그대로 수행하게 한다.
    super.onStartup(servletContext); // 수퍼 클래스의 메서드는 그래도 실행!

    // 프론트 컨트롤러 및 프론트 컨트롤러가 사용할 IoC 컨테이너 준비
    System.out.println("AppWebApplicationInitialzer.onStartup() 호출 !!");
  }
}

WebApplicationInitializer(AbstractDispatcherServletInitializer)

// 수퍼 클래스 코드를 분석하시오!! 제발 !!!
//
public class AppWebApplicationInitialzer extends AbstractDispatcherServletInitializer {

  // 수퍼클래스에서 ContextLoaderListener 를 준비할 때 사용할 Root IoC 컨테이너를 리턴한다.
  @Override
  protected WebApplicationContext createRootApplicationContext() {
    return null; // 설정할 필요가 없다면 null을 리턴
  }

  // 수퍼클래스의 메서드에서 DispatcherServlet을 준비할 때 사용할 서블릿 이름을 리턴한다.
  @Override
  protected String getServletName() {
    return "app";
  }

  // 수퍼클래스에서 DispatcherServlet을 준비할 때 사용할 IoC 컨테이너를 리턴한다.
  @Override
  protected WebApplicationContext createServletApplicationContext() {
    // 웹관련 컴포넌트를 다룰 수 있는 기능이 포함된 스프링 IoC 컨테이너 준비
    AnnotationConfigWebApplicationContext iocContainer =
        new AnnotationConfigWebApplicationContext();
    iocContainer.register(AppConfig.class);
    return iocContainer;
  }

  // 수퍼 클래스에서 DispatcherServlet의 URL을 연결할 때 사용할 경로를 리턴한다.
  @Override
  protected String[] getServletMappings() {
    return new String[] {"/app/*"}; // 한개여도 배열로 리턴해야한다.
  }

  // 수퍼 클래스에서 필터를 등록할 때 사용할 정보를 리턴한다.
  // 정교하게 필터를 제어할수 없다...
  // 직접필터를 등록할때는 app이라는 서블릿이 동작할때마다 필터가 동작한다.
  @Override
  protected Filter[] getServletFilters() { // 이 메서드가 호출되면 필터를 생성한다.
    return new Filter[] {new CharacterEncodingFilter("UTF-8"), new AdminCheckFilter(),
        new LoginCheckFilter()};
  }

  // 수퍼 클래스에서 DispatcherServlet을 준비할 때 추가적으로 설정할 것이 있으면 설정한다.
  @Override
  protected void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(new MultipartConfigElement(System.getProperty("java.io.tmpdir"), // 클라이언트가 보낸 파일을 임시 저장할 디렉토리
        1024 * 1024 * 5, // 한 파일의 최대 크기
        1024 * 1024 * 10, // 첨부 파일을 포함한 전체 요청 데이터의 최대 크기
        1024 * 1024 // 첨부 파일 데이터를 일시적으로 보관할 버퍼 크기
    ));
  }
}

WebApplicationInitializer(AbstractAnnotationConfigDispatcherServletInitializer)

// 수퍼 클래스 코드를 분석하시오!! 제발 !!!
public class AppWebApplicationInitialzer
    extends AbstractAnnotationConfigDispatcherServletInitializer {

  // 수퍼클래스에서 Root IoC 컨테이너를 만들어 준단다.
  // 그럼 우리가 해야 할 일은 그 컨테이너가 사용할 클래스 정보만 알려주면 된다.
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return null;
  }

  // 수퍼클래스에서 ContextLoaderListener 를 준비할 때 사용할 Root IoC 컨테이너를 리턴한다.
  @Override
  protected WebApplicationContext createRootApplicationContext() {
    return null; // 설정할 필요가 없다면 null을 리턴
  }

  // 수퍼클래스의 메서드에서 DispatcherServlet을 준비할 때 사용할 서블릿 이름을 리턴한다.
  @Override
  protected String getServletName() {
    return "app";
  }

  // 수퍼클래스에서 DispatcherServlet을 준비할 때 사용할 IoC 컨테이너를 만들어 준단다.
  // 그럼 우리가 할 일은 그 컨테이너를 만들 때 사용할 java config 클래스를 알려주면된다.
  @Override
  protected Class<?>[] getServletConfigClasses() {
    // java config 클래스 정보를 배열에 담아서 리턴한다.
    return new Class<?>[] {AppConfig.class};
  }

  // 수퍼 클래스에서 DispatcherServlet의 URL을 연결할 때 사용할 경로를 리턴한다.
  @Override
  protected String[] getServletMappings() {
    return new String[] {"/app/*"}; // 한개여도 배열로 리턴해야한다.
  }

  // 수퍼 클래스에서 필터를 등록할 때 사용할 정보를 리턴한다.
  // 정교하게 필터를 제어할수 없다...
  // 직접필터를 등록할때는 app이라는 서블릿이 동작할때마다 필터가 동작한다.
  @Override
  protected Filter[] getServletFilters() { // 이 메서드가 호출되면 필터를 생성한다.
    return new Filter[] {new CharacterEncodingFilter("UTF-8"), new AdminCheckFilter(),
        new LoginCheckFilter()};
  }

  // 수퍼 클래스에서 DispatcherServlet을 준비할 때 추가적으로 설정할 것이 있으면 설정한다.
  @Override
  protected void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(new MultipartConfigElement(System.getProperty("java.io.tmpdir"), // 클라이언트가 보낸 파일을 임시 저장할 디렉토리
        1024 * 1024 * 5, // 한 파일의 최대 크기
        1024 * 1024 * 10, // 첨부 파일을 포함한 전체 요청 데이터의 최대 크기
        1024 * 1024 // 첨부 파일 데이터를 일시적으로 보관할 버퍼 크기
    ));
  }
}

Root IoC 컨테이너와 프론트 컨트롤러

ContextLoaderListenerSpring IoC 컨테이너를 포함(소유)하고있다.
ContextLoaderListener 웹어플리케이션이 시작되면 자동으로시작되는 리스너 객체

/app/* : 일반사용자용 기능
DispatcherServlet /app으로 들어오는 모든 요청은 dispatcherServlet이 처리하고있고, DispatcherServlet 생성할때 Spring IoC 컨테이너늘 주입하고있으며,

    AnnotationConfigWebApplicationContext iocContainer =
        new AnnotationConfigWebApplicationContext();
    iocContainer.register(AppConfig.class);
    DispatcherServlet servlet = new DispatcherServlet(iocContainer);

DispatcherServletSpring IoC 컨테이너를 포함(소유)하고있다.
Spring IoC 컨테이너는 TransactionManager, DataSource, DAO, Service, Controller, MultipartResolver, ViewResolver를 관리한다.
웹관련 컴포넌트 : Controller, MultipartResolver, ViewResolver

/shop/* : 판매자용 기능
DispatcherServletSpring IoC 컨테이너를 포함(소유)하고있다.
Spring IoC 컨테이너도 TransactionManager, DataSource, DAO, Service, Controller, MultipartResolver, ViewResolver 등이 필요하다.

/mgr/* : 관리자용 기능
DispatcherServletSpring IoC 컨테이너를 포함(소유)하고있다.
Spring IoC 컨테이너도 TransactionManager, DataSource, DAO, Service, Controller, MultipartResolver, ViewResolver 등이 필요하다.

:: DispatcherServlet이 소유한 Spring IoC 컨테이너의 객체가 중복생성된다.
:: Root Ioc Container : DispatcherServlet이 소유한 Spring IoC 컨테이너
중복 생성되는 객체들(TransactionManager, DataSource, DAO, Service)은 ContextLoaderListener로 이동한다.
왜? 공유할수 있다.
ContextLoaderListener 여러 프론트 컨트롤러에서 공유하는 객체를 보관한다.

0개의 댓글