스프링 컨테이너 등록

2023. 11. 26. 11:04Spring/스프링부트_핵심원리와 활용

번 시간에는 WAS와 스프링을 통합하는 작업을 할 것이다. 

[목차]

1) 스프링 컨테이너 만들기
2) 컨트롤러를 스프링컨테이너에 빈등록
3) 디스패처 서블릿을 서블릿 컨테이너로 등록

이렇게 3가지 과정을 거칠것이다. 

1) 스프링 컨테이너 만들기

 

프로젝트 파일 구조

일단 위에 프로젝트 파일 구조이다. 

위 그림처럼 서블릿에 Dispatcher Servlet을 등록하는 과정을 해야한다. 그리고 스프링 컨테이너를 위해서 build.gradle에 스프링 라이브러리 추가가 필요하다. 

//스프링MVC 
implementation 'org.springframework:spring-webmvc:6.0.9'

위에 저 문장을 build.gradle에 추가한다. 추가하면 아래 소스처럼 될것이다. 

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'

    //스프링MVC
    implementation 'org.springframework:spring-webmvc:6.0.9'

}

tasks.named('test') {
    useJUnitPlatform()
}

 

gradle 새로고침

그리고서 gradle 새로고침 버튼을 누르면 스프링 관련 라이브러리가 다운로드가 진행된다. 다운로드가 완료되면 아래 그림처럼 라이브러리가 추가된 것을 볼 수 있다.

스프링 관련 라이브러리

2) 컨트롤러를 스프링컨테이너에 빈등록

package hello.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping("hello-spring")
    public String hello() {
        System.out.println("helloController.hello");
        return "hello spring!!!";
    }
}

그리고서 package controller를 생성 후에 패키지 안에 HelloController 클래스를 생성한다. 클래스 안에 내용은 간단하다. console에 메시지 추가 후에 String 형태로 "hello spring!!!" 을 반환하는 로직이다. 

그리고 나서 HelloConfig라는 클래스를 만든 후 HelloController를 @Bean등록한다. 이렇게 하는 이유는 @Bean을 등록한다는 의미가 스프링 컨테이너에서 관리를 하겠다는 뜻이기 때문이다. 즉, Spring Container안에 HelloController가 들어가는 과정이다.

package hello.config;

import hello.controller.HelloController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HelloConfig {
    
    @Bean
    public HelloController helloController() {
        return new HelloController();
    }
}

등록은 위에 소스처럼 @Configuration이라는 annotation으로 이 클래스는 config 클래스라는 것을 지정하였고, 이 안에 @Bean annotation을 이용해서 수동으로 HelloController를 스프링 컨테이너에 등록하였다. 

package hello.container;

import jakarta.servlet.ServletContext;

public class AppInitV2Spring implements AppInit {

    @Override
    public void onStartUp(ServletContext servletContext) {
        System.out.println("AppInitV2Spring.onStartUp");
    }
}

그 다음에 어플리케이션 초기화가 필요한데 servlet package 안에 AppInitV2Spring이라는 클래스를 만들고 AppInit를 구현한다. 
그러면 onStartUp을 오버라이드해야하고 그 안에 문자를 출력하였다. 그런 다음에 tomcat서버를 재기동하였다. 

tomcat restart 후 console

MyContainerInitV2 c = [class hello.servlet.AppInitV1Servlet, class hello.container.AppInitV2Spring]

그랬더니 위처럼 AppInitV2Spring을 출력하는 것을 볼 수 있다. 왜냐하면 AppInit으로 구현을 하였기 때문에 MyContainerInitV2 class파일 안을 보면 관련 클래스들을 다 출력하도록 소스를 짰기 때문이다. 자세한 것은 지난 포스팅을 보면 도움이 될 것이다. 

3) 디스패처 서블릿을 서블릿 컨테이너로 등록

package hello.container;

import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HandlesTypes;

import java.util.Set;

@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) {
                throw new RuntimeException(e);
            }
        }
    }
}

이제 컨테이너 초기화에서 AppInitV2Spring을 인식하였으니 AppInitV2Spring에서 스프링 컨테이너를 생성할 것이다. (아직 우리는 스프링 컨테이너가 존재하지 않았다.)

package hello.container;

import hello.config.HelloConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

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 Dispatcher Servlet 생성하고 스프링 컨테이너를 연결
        DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
        
        //dispatcher Servlet을 서블릿 컨테이너에 등록한다.
        ServletRegistration.Dynamic dispatcherV2 = servletContext.addServlet("dispatcherV2", dispatcherServlet);
        
        //  /spring/* 요청은 dispatcher servlet에 실행되도록 설정한다. 
        dispatcherV2.addMapping("/spring/*");
    }
}

위에 전체 소스를 설명해보겠다. 먼저 스프링 컨테인너 생성하는 부분은 아래 코드와 같다. 

//스프링 컨테이너 생성
AnnotationConfigWebApplicationContext appContext =
        new AnnotationConfigWebApplicationContext();
appContext.register(HelloConfig.class);

AnnotationConfigWebApplicationContext을 통해서  스프링 컨테이너를 만들고 아까 만들었던 @Bean등록했던 HelloConfig.class를 appContext에 등록한다. 이렇게 하면 스프링 컨테이너가 만들어졌다.  아래 그림 상자가 만들어진것이다. 그리고서 HelloConfig에서 HelloController를 Bean등록하였기 때문에 아래 그림처럼 컨트롤러가 들어가있다.

//스프링 MVC Dispatcher Servlet 생성하고 스프링 컨테이너를 연결
DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
 

위 소스는 Dispatcher Servlet을 생성하고 스프링 컨테이너를 연결하는 역할이다. 그 다음 아래처럼 Spring Container에 연결되는 모습이다. appContext에 HelloConfig를 등록하였기 때문에 이 작업이 가능한 것이다. 

그리고 Disptcher Servlet을 이제 Servlet Container에 등록하는 작업이다. 내가 생각하기로는 서블릿 컨테이너를 만들고 그 안에 Dispatcher Servlet을 등록하고 스프링 컨테이너에 연결해야할거 같은데 내 생각과 다르게 Dispatcher Servlet을 먼저 만들고 스프링 컨테이너에 HelloController를 연결하고서 그 다음에 이것을 ServletContainer에 등록해야한다. 그래서 아래 소스처럼 DisPatcher Servlet을 서블릿 컨테이너에 등록하는 작업이다. 

//dispatcher Servlet을 서블릿 컨테이너에 등록한다.
ServletRegistration.Dynamic dispatcherV2 = 
			servletContext.addServlet("dispatcherV2", dispatcherServlet);

그제서야 위에 그림처럼 서블릿 컨테이너 안에 dispatcher Servlet이 들어가있는 모습이다. 

//  /spring/* 요청은 dispatcher servlet에 실행되도록 설정한다.
dispatcherV2.addMapping("/spring/*");

그 다음은 요청하는 매핍을 해준다. "/spring/*"의 의미는 "localhost:8081/spring/" 이 다음에 아무거나 오는 url에서는 dispatcher Servlet으로 통하라는 말이다. 

example)
http://localhost:8081/spring/1 
http://localhost:8081/spring/2
http://localhost:8081/spring/3
http://localhost:8081/spring/etc
위 주소 모두 다 dispacther Servlet을 통하도록 되어있다. 


server 재시작 후에 http://localhost:8081/spring/hello-spring를 들어가본다. /spring/인 주소가 있기 때문에 dispatcher Servlet을 통하도록 되어있다. 

위 처럼 HelloController가 실행이 정상적으로 작동 되었고, console에도 아래처럼 helloController.hello가 잘 나오고 있다.

 

이번에는 dispatcher servlet을 만들고 servler container에 등록하고, 그리고 스프링 컨테이너도 만든다음 컨트롤러를 연결시키고, 수동으로 빈도 등록하고.. 진짜 많은 작업을 하였다. 이게 우리가 스프링부트를 사용하면 자동으로 해준다는 것인지 강의를 더 봐야알수 있겠지만 지금도 등록하는데에만 정말 많은 작업과 시간이 소요되었고, 포스팅을 하기 위해서 다시 처음부터 코드를 쳐보았는데 처음보았을 때보다 두번재 했을때 과정이 더 잘 들어왔던거 같다. 

이 글을 보는 여러분들도 한번 직접 코드를 쳐보고 실행을 하면서 원리를 파악하면 정말 많은 도움이 될것이라고 생각한다.