LostCatBox

AboutSpring

Word count: 2.6kReading time: 16 min
2022/12/25 Share

Spring의 전반적인 설명

Created Time: November 15, 2022 10:54 AM
Last Edited Time: November 27, 2022 9:54 PM
References: https://devscb.tistory.com/119
https://jypthemiracle.medium.com/servletcontainer%EC%99%80-springcontainer%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%A4%EB%A5%B8%EA%B0%80-626d27a80fe5
https://huisam.tistory.com/entry/springDI
Tags: Spring

Spring 이란?

  • MVC
    • DI - 느슨한 결합력과 인터페이스
  • 트랜잭션
    • AOP
  • 인증 및 권한
    • Servlet Filter
    • Spring Security

Spring 구조 및 구성요소

스크린샷 2022-11-15 오전 11.18.56

ServletContainer (서블릿 컨테이너)

Client (보통 크롬, 파이어폭스 등과 같은 웹브라우저)로부터 HTTP요청을 받아 서블릿 로직을 처리할 수 있는 구성요소입니다.

서블릿이란 javax.servlet 패키지에 정의된 인터페이스로, 자바 class 파일로 된 서버로직이라고 볼 수 있습니다.

ServletContainer의 대표적인 예로는 Tomcat이 있습니다.

Spring (스프링)

Spring framework (스프링 프레임워크) 영역입니다.

DispatcherServlet (디스패처 서블릿)

Spring framework (스프링 프레임워크)에서 front controller역할을 합니다.

실제 동작하기 위한 로직요청에 대해 처리할 수 있도록 해줍니다.

dispatch의 뜻을 보내다라는 뜻으로, 이곳에 요청이 오면 실제로 로직을 수행할 컴포넌트로 요청을 보내주는 역할을 하고, 반환을 받는 역할을 합니다.

HandlerMapping (핸들러 매핑)

Spring framework(스프링 프레임워크)에 작성된 여러 Controller(컨트롤러) 중에서 로직을 수해할 contoller를 확인해주는 컴포넌트입니다.

Controller (컨트롤러)

개발자가 직접 개발하는 컴포넌트로, http request를 개발자가 처리할 수 있도록 개발하는 컴포넌트입니다.

Service (서비스)

개발자가 직접 개발하는 컴포넌트로, 비지니스 로직을 수행하는 컴포넌트이니다.

DAO (Data Access Object, 디에이오)

개발자가 직접개발하는 컴포넌트로, Database에 직접적으로 접근하는 객체입니다.

보통 mybatis와 같은 persistence framework를 많이 활용합니다.

ViewResolver (뷰 리졸버)

ViewName을 기반으로 어떤 View 파일을 사용할것인지 확인해주는 컴포넌트입니다.

View (뷰)

개발자가 직접개발하는 컴포넌트로, 보통 UI화면을 의미합니다.

Spring 요소 시퀀스 다이어그램

2

  1. HTTP 요청을 client에서 보내면 servlet container에서 이를 처리하도록 합니다.

  2. servlet container에서는 spring이 처리해야한다는것을 인지하고, dispatcherServlet이 처리하도록 http request정보를 전달합니다.

servlet container에서 spring이 처리해야한다는 내용은 web.xml 파일에 기입이 되어 있습니다.

  1. dispatherServlet은 http request 중 url과 메서드 등을 확인하여 @RequestMapping으로 매핑되어 있는 controller를 찾습니다.

  2. 앞 단계에서 찾아진 controller가 할당이 되어 로직을 처리하게 됩니다.

  3. controller 처리는 service의 조합으로 처리가 됩니다.

  4. service는 DAO를 호출하여 비지니스 로직을 수행하게 됩니다.

  5. DAO는 DB에 직접접근하여 쿼리를 수행하게 되고, 리턴결과를 자바 객체로 받게 됩니다.

  6. DAO -> Service -> Controller로 결과를 리턴받게 되고, Controller는 Viewname과 Model을 리턴합니다.

  7. Viewname을 리턴받은 DispatcherServlet은 ViewResolver로 어떤 View 파일을 활용할지 Viewname을 통해 확인합니다.

  8. 확인된 View파일과 Model을 이용하여 사용자에게 최종적으로 보여질 UI화면을 http response에 실어서 클라이언트에게 리턴해줍니다.

  9. client에서는 http response를 받아서(텍스트형식) 결과를 파싱하는 등의 처리를 진행해서 최종적으로 사용자에게 화면을 표시해주게 됩니다.

Spring bean life cycle

3

DI, IoC 그리고 의존성 주입(DI)의 3가지 방법

출처

이렇게 코드 자체를 수정해야 한다. 이러한 구조는 객체 간의 높은 결합도를 갖게 되며 이후 변동사항이 생기면 많은 부분들을 수정해야 하기 때문에, 프로젝트의 규모가 비대해질수록 수정하기가 어려워진다.

그래서 Spring에서는 객체 간의 관계 설정을 클래스가 직접 하는 방식 대신, Spring Container를 이용하여 외부에서 객체를 생성하고, 객체를 주입하는 방식, 즉 의존성 주입(DI) 방식을 사용하고 있다.

위와 같은 방식은, 객체끼리 서로 생성에 관여하지 않고 Spring Container가 대신 함으로써 객체 간의 결합도를 최대한 낮추는 게 스프링 IoC(Inversion of Control)제어의 역전 스프링의 핵심 개념이다.

1. Field Injection (필드 주입)

1
2
3
//1. 필드주입
@Autowired
private BService Bservice;

2. Setter based Injection (수정자 주입, 세터 주입)

1
2
3
4
5
6
7
//2. 세터주입
private BService bService;

@Autowired
public void setBService(BService bService) {
this.bService = bService;
}

3. Constructor based Injection (생성자 주입)

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class BController {

//3. 생성자주입
private final BService bService;

@Autowired
public BController(BService bService) {
this.bService = bService;
}
}

무엇이 다른가?

3가지 방식에는 빈을 주입하는 순서가 다르다.

Field Injection

  1. 주입받으려는 빈의 생성자를 호출하여 빈을 찾거나 빈 팩토리에 등록

  2. 생성자 인자에 사용하는 빈을 찾거나 생성

  3. 주입하려는 빈 객체의 수정자를 호출하여 주입

  4. 필드에 주입

Setter based Injection

  1. 주입받으려는 빈의 생성자를 호출하여 빈을 찾거나 빈 팩토리에 등록

  2. 생성자 인자에 사용하는 빈을 찾거나 생성

  3. 주입하려는 빈 객체의 수정자를 호출하여 주입

위에 2가지 방식은 빈 생성후, 이후에나 런타임에서 의존성을 주입하기 때문에 의존성을 주입하지 않아도 객체가 생성될 수 있다.

Constructor based Injection

  1. 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 생성

  2. 찾은 인자 빈으로 주입하려는 생성자를 호출

즉, 먼저 빈을 생성하지 않고 주입하려는 빈을 먼저 찾습니다.(NullPointerException방지)

무엇을 사용하는 것이 좋을까?

스프링에서는 현재 생성자 주입 방식을 권고하고 있다. (인텔리제이에서 필드 주입 사용 시 경고창이 나옴)

1. 필드에 final 키워드 사용이 가능하다.

Field Injection시 final 키워드를 사용할 수 없지만,

Constructor based Injection 은 사용할 때 final 키워드를 사용할 수 있다.

그러므로 불변(immutable)하게 사용할 수 있다.

2. 순환 참조를 방지할 수 있다.

어떤 클래스 A가 B를 참조하고, B가 A를 참조하는 경우를 순환 참조라고 말한다.

Field Injection, Setter based Injection은 빈이 생성된 후 참조를 하기에 애플리케이션은 아무런 오류나 경고 없이 구동된다. 실제 코드가 호출되기 전까지는 문제를 알 수 없는 것이다.

반면, Constructor based Injection으로 통해 실행해보면 BeanCurrentlyInCreationException이 발생한다.

순환 참조뿐만 아니라 의존 관계에 내용을 외부로 노출시킴으로써 애플리케이션을 실행하는 시점에서 오류를 체크할 수 있다.

3. 테스트 작성의 편의성

테스트를 하려는 클래스가 Field Injection을 사용할 때에는 외부에서 빈을 주입해줄 수가 없다. 그렇기에 해당 필드는 null 이 된다. 따라서 스프링 빈 및 모든 설정을 가져와서 실행해야 테스트를 할 수 있다. Constructor based Injection일 때는 테스트 코드 자체에서 필요한 의존관계만 만들어서 테스트가 가능하다.

Spring Container에 대해

https://huisam.tistory.com/entry/springDI

https://marrrang.tistory.com/58

그림을 보시면 이 Application Context가 Bean Factory를 의존하고 있고,

Bean Factory가 객체에 대한 생성을 담당하는 것을 볼 수 있습니다

Spring에서 IoC Container라는 것은 결국 Application Context와 동일하며,

바로 이 IoC(Inversion of Control) Container가 주축이 되어 DI가 이루어지게 됩니다

IoC Container가 객체(Bean)에 대한 설정 파일(xml) 혹은 설정 객체(Configuration)들을 읽어서

해당 객체(Bean)의 의존성 주입이 이루어지게 되는 것이죠

한마디로 IoC Conatiner객체(Bean)에 대한 생명 주기(life cycle)을 담당하고 있다고 보시면 될 것 같습니다

그럼 도대체 생명 주기를 어떠한 과정을 통해서 객체(Bean)에 대한 생성과 소멸이 이루어지게 될까요?

Spring Singleton 수십만건 처리?

https://ojt90902.tistory.com/650 (!!!)

https://jeong-pro.tistory.com/204 (!!!)

https://jypthemiracle.medium.com/servletcontainer와-springcontainer는-무엇이-다른가-626d27a80fe5 (!!!)

일단 WAS에 대한 학습요소로 많이 쓰이는 것이 Tomcat이다. Tomcat은 default로 설정되어 있는 worker thread가 200개인 것으로 알고 있다. Tomcat은 하나의 프로세스에서 동작하고, thread pool을 만들어 HttpRequest가 들어왔을 때 하나씩 쓰레드를 재사용 및 재배정을 진행한다. 쓰레드 풀
에 생성될 수 있는 쓰레드 개수의 max를 우리가 지정할 수 있는데, 실질적으로는 idle한 상태로 남겨지는 쓰레드의 최소 개수도 있다. 요청이 많아지면, 그에 따라 실질적으로 큐에 쌓였다가 쓰레드가 만들어지는 등 일반적인 쓰레드 풀의 동작을 한다. 아니, Request 별로 Thread가 별도로 생성되고 이에 따라 각각의 ServletContext를 갖는 것은 분명한데, 이 쓰레드들이 하나의 Controller 객체를 공유한다는 것이 가능한가? 라는 질문을 갖게 되었다.

Controller 객체 하나를 생성하면 객체 자체는 Heap에 생성되지만, 해당 Class의 정보는 Method Area(또는 Permanent Area)에 저장된다는 것이다. 결국 힙 영역이던 메소드 영역이던 모든 쓰레드가 객체의 Binary Code 정보를 공유할 수 있다는 뜻이다. 공유되는 정보를 사용하기 위하여 굳이 Controller 객체를 사용하고 있는 쓰레드나 Controller 객체 자체가 Block될 필요는 없다는 것이다.

Controller가 내부적으로 상태를 갖는 것이 없으니, 그냥 메소드 호출만 하면 되기 때문에 굳이 동기화할 이유도 없고 명분도 없고 그저 처리 로직만 ‘공유되어’ 사용되는 것이기 때문에 몇 십만개의 요청이 들어오든 상관없다는 것이다.

Spring Bean을 만들 때는 stateless하게 만들어야 한다는 교훈을 얻었다. 지식적으로는 알고 있었지만, 그 이유를 알 수 있는 계기가 되었다. 스프링 Bean이 상태를 갖게 되었을 때는 그 상태를 공유하는 모든 쓰레드들로 부터 안전할 수 있게 동기화를 해줘야 하고 동기화를 하는 순간 싱글톤으로써의 혜택이 날아간다고 봐야하기 때문이다.

서블릿 컨테이너와 스프링 컨테이너의 차이

Servlet에 대하여

Servlet은 Java EE의 표준 중 하나로 javax.servlet Package를 기반으로 Server에서 동작하는 Class들을 의미한다. 각 Servlet은 init(), service(), destory() 3개의 method를 반드시 정의해야 한다.

  • init() : init()은 Servlet 생성시 호출된다. Parameter로 javax.servlet.ServletConfig Interface 기반의 Instance가 넘어오는데, Servlet을 초기화 하고 Servlet이 이용하는 자원을 할당하는 동작을 수행한다.
  • service() : Servlet으로 요청이 전달 될때마다 호출된다. 실제 Service Logic을 수행한다.
  • destroy() : Servlet이 삭제될때 호출된다. Servlet에서 이용하는 자원을 해지하는 동작을 수행한다.

웹 서버의 측면에서 서블릿을 바라보면, 1) Socket의 생성 2) Input/OutputStream의 생성 등의 업무를 개발자를 대신해서 진행해준다. Container는 Servlet의 생성주기(life-cycle)을 관리하고, 매번 요청이 들어올 때마다 새로운 쓰레드를 요청 별로 부여한다

Untitled

Q: 하나의 클라이언트에 하나의 쓰레드가 부여되는 것 아닌가요? 각각의 클라이언트는 자신만의 쓰레드에서 동작하는데요?

*A: 요청 당 하나의 쓰레드다. Container는 누가 요청을 보냈는지에 대해서는 관심이 없다. 새로운 요청은 새로운 쓰레드를 만들어낸다.*

Q: Thread-per-request를 Thread-per-connection에 우선해서 쓰는 이유는 뭐죠?

*A: Scalability에 유리하다. Java 쓰레드는 비용이 비싼데, 1Mb 메모리 segment가 하나씩 붙는다. active 상태이건 idle 상태이건 상관이 없다. 커넥션 하나당 하나의 쓰레드에 붙이면, 쓰레드는 요청이 계속 오기까지 idle 상태로 대기를 타야할 것이다. 궁극적으로 framework가 새로운 커넥션을 만들지 못하게 될 것이고 (쓰레드를 더 만들 수가 없어서)… 아니면 기존 커넥션을 끊어버리든가 해야겠죠.. 즉, 커넥션이 연결되는 동안 스레드가 유지되어야 합니다. 그런데 thread-per-request를 쓰면 request가 진행될 때만 쓰레드가 개입을 하니까, 서비스는 수만명이 사용한다고 하더라도 현재 사용중인 요청에만 쓰레드를 투입시키면 되니까 경제적이다 이겁니다.*

다만, 클라이언트가 지속적으로 요청을 날려야 하는 상황에서는 HTTP Keep-alives를 사용하여 Connection 별로 Thread를 지속적으로 유지하도록 할 수 있습니다.

ServletContainer에 대해

1

Servlet Container(Web Container)는 Servlet Instance를 생성하고 관리하는 역할을 수행한다. HTTP 요청을 도식화하면 다음과 같다.

  1. Web Browser에서 Web Server에 HTTP Request를 보내면, Web Server는 받은 HTTP 요청을 WAS Server의 Web Server에 전달한다.
  2. WAS Server의 Web Server는 HTTP 요청을 Servlet Container에 전달한다.
  3. Servlet Container는 HTTP 요청 처리에 필요한 서블릿 인스턴스가 힙 메모리 영역에 있는 지 확인한다. 존재하지 않는다면, Servlet Instance를 생성하고 해당 Servlet Instance의 init() method를 호출하여 Servlet Instance를 초기화한다.
  4. Servlet Container는 Servlet Instance의 service() 메소드를 호출하여 HTTP 요청을 처리하고, WAS Server의 Web Server에게 처리 결과를 전달한다.
  5. WAS Server의 Web Server는 HTTP 응답을 Web Server에게 전달하고, Web Server는 받은 HTTP 응답을 Web Browser에 전달한다.

우리가 대표적으로 알고 있는 ServletContainer는 Tomcat이다. ServletContainer는 하나의 WebApplication 하나씩 붙는다. ServletContainer인 Tomcat도 자바 프로그램이기 때문에, 하나의 JVM이 붙는다. 즉, one WAS per one JVM인 것이다.

7

Q: Apache와 Tomcat의 차이는 무엇인가요?

A: Client에서는 미리 정해진 HTTP 규격에 따라 요청을 보내게 된다. HTTP를 해석한 후, 그에 맞는 데이터 형식으로 보내주는 것이 Web Server가 할 일이다. 요청을 데이터와 단순 매칭을 하고, 이를 HTTP에 맞게 돌려주면 되므로 이러한 과정을 Static Web Server라고 부른다.

하지만 이러한 형태의 사이트에서는 동적인 기능을 제공할 수 없게 된다. 이러한 문제를 해결하기 위해서 Web Application Server를 사용하게 된다. WAS는 일부 Web Server의 기능과 Web Container로 함께 구성되는데, 앞단의 일부 Web Sever는 HTTP 요청을 받아 Web Container로 넘겨준다.

Web Container는 내부 프로그램 로직 처리를 한 후, 데이터를 만들어서 Web Server로 다시 전달한다. Web Server에 따른 처리를 Java 진영에서는 Servlet Container라고 부르고 있다. Servlet Container의 예시로는 Tomcat이 있는데, Tomcat Server가 요청을 받으면 Tomcat Engine이 요청에 맞는 Context를 찾고, 해당 Context는 본인의 web.xml을 바탕으로 전달받은 요청을 Servlet에 전달하여 처리되도록 한다.

Tomcat도 자바 프로그램인 만큼, 사용자의 컴퓨터에 설치된 자바 구동환경과 JVM 위에서 실행된다. 자바 프로그램 하나에 JVM 하나가 구동된다. 참고로, Spring Boot에는 Embedded Tomcat이 있어, 데이터를 처리하고 DB에 스스로 영향을 줄 수 있다.

Apache Web Server는 멀티 프로세스와 멀티 쓰레드 방식을 함께 사용할 수 있다. 항상 idle한 수의 프로세스 및 쓰레드를 생성해두므로, 요청이 들어왔을 때 새로운 프로세스나 쓰레드가 생성되는 것을 기다릴 필요가 없다. 평소에는 요청 하나가 쓰레드 하나에 대응을 하다가, 사용자의 접속이 증가하면 Apache MPM 방식에 따라 프로세스를 fork하거나 쓰레드를 할당한다.

이와 달리 Tomcat은 멀티 쓰레드 방식을 사용한다. 쓰레드 풀을 관리하고 있는 Acceptor Thread 하나가 존재하고, 이를 관리하는 여러 개의 Thread를 동시에 띄워둔다. 클라이언트로부터 요청이 들어오면, Acceptor Thread가 available worker thread와 connection을 맺어준다. worker thread는 응답을 받아 Tomcat engine에 request를 보내 request를 처리하고, request header와 associated virtual host and contexts에 따른 적합한 응답을 보내달라고 한다. 이후 Tomcat은 client와의 socket 통신이 열리면 다시 worker thread를 활성화 한다.

Untitled

Untitled

HttpRequest가 Servlet Container로 새로이 들어오면 Servlet Container는 HttpServletRequest, HttpServletResponse 두 객체를 생성한다. GET/POST 여부에 따라 doGet() 이나 doPost()를 실행하게 되며, 동적 페이지 생성 후, HttpServletResponse 객체에 응답을 보낸다. 이 때, 각각의 Servlet 클래스는 JVM의 Class Loader에 의해 로딩된다. Java Servlet container는 서블릿 생성자를 호출하고, 각각의 서블릿 생성자는 어떠한 매개변수도 받지 않는다.

HTTP 요청 처리 과정을 보면 Servlet Instance는 HTTP 요청이 올 때마다 기존의 Servlet Instance를 이용한다. 즉, 하나의 Servlet Instance가 여러개의 HTTP 요청을 동시에 처리하게 된다. 따라서 Servlet Instance는 Thread-Safe하지 않다. Thread-Safe한 변수를 이용하기 위해서는 Method의 지역변수를 이용해야 한다. Servlet Container는 사용되지 않아 제거되야할 Servlet Instance의 destory() method를 호출하고 JVM의 GC(Garbage Collector)에서 Servlet Instance를 해지할 수 있도록 표시해둔다. GC는 표시된 Servlet Instance를 해지한다.

Spring Container는 ?

Untitled

Untitled

Spring Container는 Bean 생명주기를 관리한다. Bean을 관리하기 위해 IoC가 이용된다. Spring Container에는 BeanFactory가 있고, ApplicationContext는 이를 상속한다. 이 두 개의 컨테이너로 의존성 주입된 빈들을 제어할 수 있다.

  1. WebApplication이 실행되면, WAS(Tomcat, ServletContainer per process)에 의해 web.xml이 로딩된다.
  2. web.xml에 등록되어 있는 ContextLoaderListener가 Java Class 파일로 생성된다. ContextLoaderListenerServletContextListener 인터페이스를 구현한 것으로서, root-content.xml 또는 ApplicationContext.xml에 따라 ApplicationContext를 생성한다. ApplicationContext에는 Spring Bean이 등록되고 공유되는 곳인데, Servlet Context는 Spring Bean에 접근하려면 Application Context를 참조해야 한다. ApplicationContext도 ServletContainer에 단 한 번만 초기화되는 Servlet이다. 코드 참고하기
  3. ApplicationContext.xml에 등록되어 있는 설정에 따라 Spring Container가 구동되며, 이 때 개발자가 작성한 비즈니스 로직과 DAO, VO 등의 객체가 생성된다.
  4. Client로부터 Web Application 요청이 왔다. Spring의 DispatcherServlet도 Servlet이니 ****이 때 딱 한 번만 생성된다. DispatcherServlet은 Front Controller 패턴을 구현한 것이다. 처음에 Request가 들어오면, Dispatcher Servlet으로 간다. web.xml에는 서블릿이 등록되어 있는데, Dispatcher Servlet도 Servlet이기 때문에 web.xml에 등록이 되어 있다. 모든 요청이 오면 Dispatcher Servlet로 가라고 하고 등록을 시켜 놓는다.
  5. 그러면 그에 맞는 요청에 따라 적절한 Controller를 찾는다. 핸들러는 컨트롤러보다 더 큰 개념인데, 핸들러 매핑을 통해서 요청에 맞는 컨트롤러를 찾아준다. HandlerMapping*에는 *BeanNameHandlerMapping 이 있어, Bean 이름과 Url을 Mapping하는 방식이 default로 지정되어 있다.
  6. HandlerMapping에서 찾은 Handler(Controller)의 메서드를 호출하고, 이를 ModelAndView 형태로 바꿔준다.

결국 개발자가 작성한 비즈니스 로직도 ServletContainer가 관리하게 되고, Spring MVC도 ServletContainer가 관리하고 있는 Servlet 한 개를 의미한다. Spring MVC로 들어가는 모든 요청과 응답은 DispatcherServlet이 관리하고 있는 것이다. 물론 Spring Container는 Spring의 자체 Configuration에 의해 생성되기는 하다. (Spring Boot uses Spring configuration to bootstrap itself and the embedded Servlet container.)

아무튼 요는 Servlet Container는 Process 하나에 배정되어 있는 것이요, 이에 따르는 요청들은 Thread 별로 처리하도록 ThreadPool에서 역할을 배정시키는 것이다. 그 중, 클라이언트가 임의의 서블릿을 실행하라고 요청했는데 만약 최초의 요청이면 init()을 실행하고, 아니라면 새로 서블릿을 만들지 않고 메소드 영역에 있는 서블릿을 참고해서 service()를 실행하는 것이다. 이렇게 개발자가 아닌 프로그램에 의해 객체들이 관리되는 것을 IoC(Inversion of Control)이라고 한다.

Spring Web MVC가 없던 과거에는, URL마다 Servlet를 생성하고 Web.xml로 Servlet로 관리했다. URL마다 서블릿이 하나씩 필요하다 보니, 매번 서블릿 인스턴스를 만들어야 했다. 그런데 Dispatcher Servlet이 도입된 이후에는 FrontController 패턴을 활용할 수 있게 되면서 매번 서블릿 인스턴스를 만들 필요가 없어졌다. 또한, View를 강제로 분리하는 효과도 볼 수 있게 되었다.

[https://www.youtube.com/watch?v=calGCwG_B4Y](

CATALOG
  1. 1. Spring의 전반적인 설명
  2. 2. Spring 이란?
  3. 3. Spring 구조 및 구성요소
  4. 4. Spring 요소 시퀀스 다이어그램
  5. 5. Spring bean life cycle
    1. 5.1. DI, IoC 그리고 의존성 주입(DI)의 3가지 방법
      1. 5.1.1. Field Injection
      2. 5.1.2. Setter based Injection
      3. 5.1.3. Constructor based Injection
      4. 5.1.4. 무엇을 사용하는 것이 좋을까?
  6. 6. Spring Container에 대해
  7. 7. Spring Singleton 수십만건 처리?
  8. 8. 서블릿 컨테이너와 스프링 컨테이너의 차이
    1. 8.1. Servlet에 대하여
  9. 9. ServletContainer에 대해
  10. 10. Spring Container는 ?