이전 글에서 Hello World를 출력해보았는데 의문점이 많이 있을 것입니다.

설정 파일을 어디에 왜 그렇게 작성해야만 하는지, 어노테이션의 역할이 무엇인지, 객체를 어디에 미리 만들어 둬서 의존성 주입을 한다는 것인지 등 궁금한 것이 많을 것입니다.

그런데 이 모든 궁금증은 스프링의 동작 과정을 이해하면 해결이 될 것이라 생각이 됩니다.


지금부터 말씀드리는 부분은 틀린 부분이 있을 수도 있으며, 의심이 가는 부분은 소스 코드를 까보셔서 직접 확인하시면 그것 또한 좋은 공부가 될 것이라 생각합니다.





톰캣 버튼을 실행했을 때 일어나는 동작



1)

톰캣을 실행하기 전에 개발자는 톰캣의 설정파일인 web.xml 파일에 <context-param>으로 전역 파라미터를 설정합니다.

파라미터의 이름은 contextConfigLocation이고 어떤 객체들을 미리 만들어 놓을지가 작성된 설정파일의 경로를 값으로 할당해 놓습니다.





2)

이어서 톰캣이 실행되면 수행할 클래스( 리스너 )의 이름을 같은 파일인 web.xml에 작성합니다.



톰캣을 실행하면 <listener>가 등록되어 있는 ContextLoaderListner 객체를 호출하는데, 이 객체는 내부적으로 부모 객체를 실행합니다.

부모 객체는 ContextLoader이며 이 객체에서 Root Application Context를 생성하는데, 이 컨테이너에는 웹과 관련이 없는 객체들을 저장합니다.

예를들면 DAO 객체들은 웹과 직접적인 관련이 없는 객체입니다.

단지 DB에 접근하기 위한 객체들일 뿐이죠.




3)

Root Application Context 컨테이너에 객체들을 생성하기 위해서, 1단계에서 작성한 전역 파라미터 이름인 contextConfigLocation의 값으로 설정된 /WEB-INF/applicationContext.xml을 읽어 들입니다.



ContextLoader 클래스에는 CONFIG_LOCATION_PARAM라는 상수가 정의되어 있는데, 기본 값으로 문자열 contextConfigLocation이 할당되어 있습니다.

그래서 1단계에서 전역 파라미터 이름을 contextConfigLocation으로 작성한 것입니다.

그리고 contextConfigLocation는 사실, 설정 파일 경로( WEB-INF/applicationContext.xml )를 의미하구요.


WEB-INF/applicationContext.xml은 어떤 객체들을 미리 생성해 놓을지를 정의한 설정 파일이라고 했었습니다.

Context Loader Listner를 통해 applicationContext.xml를 읽어들이고, Root Application Context 컨테이너에 웹과 관련이 없는 객체들이 생성됩니다.




4)

applicationContext.xml은 3 - layer 구성된 component를 정의하고 있습니다.

( 3 layer architecture는 Controller - Service - DAO로 정의된 구조를 말하는데, 지금은 이르지만 궁금하시면 이글을 참고해주세요. )



이 코드는 .xml 파일에 설정을 하는 대신에 어노테이션으로 설정을 하겠다는 의미이며, 어노테이션을 스캔 할 범위인 base-package를 정의하고 있습니다.

base-package 범위 내에서 @Repository , @Service , @Component 어노테이션을 스캔하겠다는 의미입니다.


예를들어 GuestbookDAO를 컨테이너에 추가하고 싶다면 base-package안에 GuestbookDAO 클래스를 생성한 후 3 - layer 계층에 맞는 어노테이션을 추가하면 됩니다.



Repository는 DAO에 대응되는 개념이라고 생각하시면 됩니다.

이렇게 @Repository를 등록하면 Root Application Context, 즉 bean Factory에 해당 객체가 싱글톤 형태로 저장이 됩니다.



정리하면 ApplicationContext.xml 파일에 의존성 주입을 위한 객체들을 미리 정의합니다.

여기서는 그 방식을 어노테이션으로 해결 하겠다고 했으며, 스프링이 어노테이션을 스캔할 수 있도록 범위를 지정해줬습니다.

그러면 톰캣이 실행되었을 때 ContextLoaderListner에 의해 ContextLoader가 실행되어 컨테이너에 의존성 해결을 위한 객체들을 생성하게 됩니다.


여기까지가 톰캣 start 버튼을 눌렀을 때의 수행되는 과정입니다.

저희가 해야 할 것은 설정파일을 등록하는 것과 어노테이션을 추가하는 것 밖에 없습니다.

설정만 잘 해주면 스프링이 알아서 객체들을 생성해주고 관리해줍니다.

이전 글들에서 계속해서 말해왔던 "어딘가"가 바로 Root Application Context이고, 여기에서 객체들을 미리 만들어 놓습니다.






요청이 왔을 때 실행 동작


1) 


이 코드는 Hello World를 출력하는 이전 글에서 web.xml 파일에 작성했던 코드 그대로입니다.

" 모든 요청이 왔을 때 DispactherServlet의 init() 메서드가 실행되도록 해라 "는 의미가 담겨있는 코드입니다.

물론 한 번 요청이 왔었다면 init() 메서드는 Servlet의 라이프 사이클에 따라 생략이 되겠죠.


위의 코드를 보시면 Servlet 매핑을 할 때 작성한 <servlet-name>이 spring이므로 DispatcherServlet의 init() 메서드는 spring-servlet.xml 파일을 읽어들입니다.

spring-servlet.xml 파일의 이름은 ( <servlet-name>의 값 ) + ( -servlet.xml ) 이 되어 생성된 이름입니다.




2) 

이 코드 역시 이전 글에서 설정했던 spring-servlet.xml 파일의 코드입니다.


즉 어떤 요청이 오면 DispatcherServlet은 spring-servlet.xml 파일을 읽어서 " base-package 범위 내에 있는 어노테이션을 스캐닝 한다 "는 의미가 담겨있는 코드입니다.




3)

따라서 base-package에 속한 클래스의 선언부에 @Controller 어노테이션을 추가하면 스캐닝이 이루어지고, Web Application Context 컨테이너에 해당 객체를 저장할 것입니다.





4) 

그러면 톰캣을 실행 했을 때 생성했던 컨테이너(Root Application Content)에 있는 DAO 객체를 불러올 수 있습니다.

아래와 같이 @Autowired 어노테이션을 통해 미리 생성된 객체인 DAO를 사용 할 수 있으며, 여기서 의존성 주입( DI )이 일어납니다.





5)

또한 스캐닝 작업을 계속하여 @RequestMapping 어노테이션에 대하여 Handler Mapping을 수행합니다.

즉 어떤 URL이 올 때 어떤 메서드를 실행하겠다는 매핑 테이블을 만드는 것입니다.

개발자가 해야 할 것은 @RequestMapping 어노테이션으로 URL을 작성하고, 메서드에 수행할 로직을 컨트롤러답게( 데이터를 받아와서 뷰 페이지에 전달 ) 작성하면 되는 것입니다.


main 메서드의 내부는 다음 글에서 살펴볼 것입니다.

지금은 URL에 /main 으로 요청이 오면 DAO 객체에서 데이터를 가져오고, 이 데이터를 JSP에 전달하는 컨트롤러 역할을 하는 메서드라고 이해 하시면 됩니다.


DispatcherServlet의 init() 메서드는 이와 같이 어떤 URL이 왔을 때 어떤 메서드를 실행할 것인지에 대한 Mapping을 만들어 놓습니다.




6)

스캐닝 과정을 거쳐 Mapping table이 등록되면 이후의 요청은 DispatherServlet의 init()메서드가 실행되지 않을 것입니다.

service() 메서드가 실행되어 DispatcherServlet는 HandlerMapping에게 질의를 하고 어떤 URL 요청에 대하여 어떤 메서드를 수행해야 할 지 분기를 시킵니다.


컨트롤러는 return에 작성된 경로명을 보고 응답할 View를 찾기 위해 ViewResolver에게 질의해서 View 객체를 반환 받고, 전달할 데이터를 추가하여 클라이언트에게 응답합니다.


뷰 페이지에 데이터를 넘겨주는 방식은 Model 객체를 이용하는 방법이 있는데,
View 객체를 찾는 ViewResolver와 Model에 대해서는 나중에 다루도록 하겠습니다.




이렇게 해서 요청이 왔을 때 수행되는 스프링의 내부 동작 과정을 알아보았습니다.

이 흐름을 알면 어떤 설정 파일에 어떤 코드를 작성해야 하고, 의존성 주입은 어떻게 일어나는지 알 수 있을 것입니다.





Thread safe

Root Application Context , Application Context 안에 들어 있는 객체는 Thread safe입니다.


예를들어 VO가 Root Application Context에 있을 경우, 다른 여러 요청이 같은 VO에 접근하면 이전 요청과 간섭이 발생하여 올바른 데이터를 얻지 못할 것입니다.

즉 각 컨테이너의 객체는 싱글톤이며, Thread safe 해야 합니다.


Thread safe하기 위해서는 객체에 인스턴스 변수가 없으면 됩니다.

나중에 코드를 보시면 아시겠지만 DAO , Controller는 외부에서 접근 가능한 멤버 변수가 없었습니다.

그러나 VO는 getter , setter로 멤버 변수 조작이 가능하죠.





이상으로 스프링의 동작과정을 알아보았습니다.

스프링의 동작 과정을 알면 문제가 발생했을 때 대처하기가 쉬워질 것이기 때문에 이해하고 넘어가는 것을 권장합니다.

당장 이해가 안되시더라도 이후에 애플리케이션을 만들어 보면서 적응이 될 것이니 나중에라도 다시 보면 좋을 것 같습니다.