[Spring 3.1] IoC 컨테이너와 스프링의 동작 원리
DI 구현을 통한 IoC는 스프링이 제공하는 대표적인 기능 중 하나이고, DI 설정 방식과 IoC 컨테이너의 활용 방법은 매우 다양하다.
스프링은 오브젝트를 코드로 관리하는 대신 독립된 컨테이너가 관리하도록 해 IoC를 구현한다.
해당 컨테이너는 오브젝트의 생성과 주입을 포함한 여러 가지 기능을 포함하고, 이 컨테이너를 애플리케이션 컨텍스트라고 부른다. (빈 팩토리는 DI 작업을 하는 컨테이너만을 부르고, 애플리케이션 컨텍스트는 다른 기능이 추가된 컨테이너이다)
빈 팩토리는 빈의 생성, 초기화, DI 등 생명주기를 담당하고 팩토리 빈은 복잡한 생성 로직이 필요한 빈을 만들 때 빈의 생성 과정을 완전하게 제어할 때 사용한다.
빈 팩토리를 확장해서 애플리케이션 컨텍스트 (IoC 컨테이너) 로 사용하고, 팩토리 빈은 컨테이너 내부에서 관리할 수 있는 빈으로 생각하면 된다. 이름이 비슷하니 주의하자.
여기서 빈은 애플리케이션 로직 빈 / 애플리케이션 인프라 빈 / 컨테이너 인프라 빈으로 구분된다.
애플리케이션 로직 빈 : Controller, Service 등 등 비즈니스 로직을 다룬다.
애플리케이션 인프라 빈 : 데이터베이스와 연결할 때 사용되는 DataSource를 생각하면 된다. 로직을 담고 있지는 않다.
컨테이너 인프라 빈 : 빈의 생성, 초기화 등 스프링 컨테이너의 기능에 관여한다.
3.1 버전부터는 빈 설정에서 xml을 걷어낼 수 있다.
스프링 컨테이너는 애플리케이션 컨텍스트 인터페이스를 구현한 구현체이고, 애플리케이션 컨텍스트가 제대로 동작하려면 서비스 추상화가 적용된 POJO 클래스와 설정 메타정보가 필요하다.
비즈니스 로직을 작성할 때 POJO 클래스로 작성해 비즈니스 로직에 집중할 수 있고, 설정 메타정보는 컨테이너가 POJO 클래스 (빈) 중 어떤 것들을 어떻게 애플리케이션에서 사용할 지에 대한 정보를 가진다.
설정 메타정보는 xml, @Component, @Configuration, yml, properties 등으로 표현될 수 있고 모두 BeanDefinition으로 정의된다. (@Configuration 클래스는 하나의 xml과 대응된다)
스테레오타입 애너테이션으로 빈을 등록하는 경우 생산성이 증가되지만, 세밀한 관리와 제어가 힘드니 추후 xml형태로 변경하는 방법도 고려해보자.
BeanDefinition에는 빈 아이디, 스코프 등 관련된 여러 정보들이 포함되어있고 컨테이너는 BeanDefinition을 통해 빈에 대한 정보를 읽어오고 DI 작업을 수행한다. (클래스 이름과 빈의 아이디는 필수 항목이다)
public void registerBean() {
ApplicationContext ac = new ApplicationContext();
ac.registerBeanDefinition("p", new RootBeanDefinition(sp.class));
BeanDefinition def = new RootBeanDefinition(H.class);
def.getPropertyValues().addPropertyValue("name", "Spring");
def.getPropertyValues().addPropertyValue("p", new RuntimeBeanReference("p"));
ac.registerBeanDefinition("H", def);
}
빈 오브젝트가 생성되고 관계가 정해지면 컨테이너는 관여하지 않는다.
애플리케이션 컨텍스트 인터페이스를 제대로 구현한 구현체라면 모두 스프링의 IoC 컨테이너로 사용될 수 있다.
StaticApplicatcationContext, GenericApplicationContext, GenericXmlApplicationContext, WebApplicationContext 등 스프링은 여러 가지 애플리케이션 컨텍스트의 구현체를 제공하지만 직접 구현체를 구현할 일은 없다. 간단한 설정으로 자동으로 만들어지는 방법을 사용한다.
자바 프로그램의 시작은 main 메서드지만 웹 환경에서는 main메서드를 호출할 수 없다.
대신, 서블릿 컨테이너가 브라우저로부터 전달되는 HTTP 요청을 받아서 해당 요청에 매핑된 서블릿을 실행하는 방식으로 동작한다. (서블릿이 main 메서드 역할을 한다)
(서블릿은 자바로 웹 애플리케이션을 개발하는 기술으로, 배포 시점에 클라이언트의 요청과 매핑된다. 클라이언트로부터 요청이 들어오면 서블릿 컨테이너가 요청에 해당하는 서블릿을 실행해주고 결과를 HTTP 응답으로 생성해 웹 서버를 통해 클라이언트에게 전달한다. 서블릿에는 main 메서드가 없다!!)
WAS는 main 메서드 역할을 하는 서블릿을 미리 여러 개 만들어두고, 애플리케이션 컨텍스트 하나를 미리 만들어둬서 애플리케이션 컨텍스트를 서블릿들이 공유해서 사용하도록 한다. (루트 애플리케이션 컨텍스트)
스프링 MVC에서는 보통 디스패처 서블릿 하나만을 만들어서 사용하고, 이 때 만들어지는 서블릿은 web.xml에서 설정할 수 있다. (서블릿의 이름, 어떤 요청을 해당 서블릿이 처리하게 할 지..)
요청이 서블릿으로 들어올 때 마다 getBean 메서드로 필요한 빈을 가져와 정해진 메서드를 실행해준다.
좀 더 자세하게 살펴보면, 서블릿 컨테이너는 클라이언트의 요청이 들어오면 우선 디스패처 서블릿에게 요청을 전달한다.
디스패처 서블릿은 요청을 분석해 적절한 컨트롤러에게 처리를 위임하고, 애플리케이션 컨텍스트를 사용해 DI 및 기타 작업을 처리한다. (프론트 컨트롤러 패턴을 구현했고, 디스패처 서블릿은 미리 만들어둔 서블릿 중 하나이다)
일반적으로 웹 애플리케이션에서는 디스패처 서블릿 하나만 생성되지만, 특수한 기능을 처리하기 위해 다른 서블릿들이 생성될 수 있다는 점을 기억하자.
웹 애플리케이션 환경에서는 보통 애플리케이션 컨텍스트의 구현체로 WebApplicationContext가 자주 사용된다.
애플리케이션마다 컨테이너가 꼭 하나일 필요는 없다.
컨테이너를 계층 구조로 구성하면 모든 컨테이너들은 각자 독립적인 설정정보로 빈 오브젝트를 관리하지만, DI를 처리하기 위해 빈을 검색할 때는 부모 컨테이너까지 탐색한다. (형제 컨테이너의 빈은 탐색하지 않고, 자식 컨테이너가 우선된다)
웹 애플리케이션에서 IoC 컨테이너는 요청을 처리하는 서블릿에서 하나 만들어지고, 웹 애플리케이션 레벨에서도 하나 만들어진다.
웹 애플리케이션 레벨에서 만들어지는 컨테이너는 루트 애플리케이션 컨텍스트로 서블릿에서 만들어지는 컨테이너의 조상이 된다.
디스패처 서블릿은 애플리케이션 컨텍스트를 내부적으로 만들어 두지만, 서블릿 별로 독립적인 애플리케이션 컨텍스트를 가지지 않을 수도 있다.
웹 관련 설정 및 컴포넌트를 포함하는 경우 서블릿 애플리케이션 컨텍스트가 사용되고 위와 같은 그림처럼 구성되는거지, 일반적인 경우 서블릿들은 공통된 루트 애플리케이션 컨텍스트를 사용한다.
서블릿 별로 독립적인 애플리케이션 컨텍스트를 가지는 경우는 웹 기술에 의존적인 부분과 그렇지 않는 부분을 구분해 빈들의 재사용성과 구성의 효율성을 높이기 위해 사용된다.
서블릿 애플리케이션 컨텍스트가 사용되더라도 어차피 서블릿이니 디스패처 서블릿에 의해 관리되는건 같다.
계층 구조로 구성 시 스프링 외의 웹 기술을 사용할 때 웹 기술을 확장하거나 변경하기 용이해진다.
서블릿 컨텍스트와 루트 애플리케이션 컨텍스트를 계층구조로 구성하거나, 둘 중 하나만 사용하거나..
프로젝트에 따라서 애플리케이션 컨텍스트를 적절하게 설정하자.
루트 애플리케이션 컨텍스트는 이벤트 리스너를 사용해 애플리케이션이 시작될 때 등록하고, 서블릿 애플리케이션 컨텍스트는 디스패처 서블릿이 사용할 수 있도록 등록해주자.
빈 등록 : XML / StereoType @Component / @Configuration @Bean / 자바 코드
의존관계 설정 : XML / XML autowireing / @Resource / @Autowired / 자바 코드
빈 등록과 의존관계 설정은 xml로도 수행할 수 있고, 애너테이션 기반 자바 코드로도 진행할 수 있다.
xml로 할 수 있는 작업을 자바 코드로 대부분 커버할 수 있다. (몇 가지 세부적인 작업은 xml로 진행하는 편이 좋다)
애너테이션으로 빈의 의존관계를 정의하는 방법을 사용하려면 스프링이 애너테이션 기반의 설정을 인식할 수 있도록 설정이 필요하다.
xml에서는 annotation-config 나 component-scan 태그를 사용하고 자바 코드에서는 AnnotationConfigApplicationContext 클래스나 @ComponentScan 애너테이션을 사용해서 설정해 줄 수 있다.
@Controller
public class MyClass {
// 필드 주입
@Autowired
private MyService myService;
//메서드 주입
private MyService myService;
@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}
}
@Controller
public class MyClass {
private final MyService myService;
// 생성자 주입. 생성자가 하나인 경우 애너테이션 생략 가능
public MyClass(MyService myService) {
this.myService = myService;
}
}
애너테이션을 통해 빈의 의존관계를 정의할 때는 생성자, 필드, 수정자(메서드)를 활용할 수 있다.
필드 주입은 코드를 보고 DI 구조를 파악하기 쉬워 자주 사용되지만, 객체의 불변성과 순환 참조 등 여러 요소를 고려했을 때 생성자 주입을 사용하는 편이 합리적이긴 하다.
아래는 사용되는 애너테이션들이다.
@Resource : 참조할 빈의 이름으로 빈을 탐색한다. 생성자에 부여할 수 없다.
@Autowired : 타입으로 빈을 탐색한다. 중복될 시 따로 처리해 줘야 한다 (Spring)
@Inject : 타입으로 빈을 탐색한다. (POJO)
@Qualifier : 타입 외 정보(이름)를 추가해서 Autowire를 도와준다.
보통 Autowired를 사용하고 타입이 여러 개 존재하는 경우 Qualifier나 Resource 애너테이션을 사용하는 편이다.
위의 애너테이션이 작동될 때는 내부적으로 스프링 컨테이너가 getBean() 메서드와 유사한 작업을 수행한다.
애너테이션으로 빈의 의존관계가 정의되는 작업은 빈 오브젝트가 등록된 후 후처리기에 의해 진행된다.
그럼 빈 오브젝트는 언제 등록될까?
스프링 레거시 프로젝트에는 static void main 메서드가 없어 애플리케이션을 실행하려면 별도로 설정한 WAS를 실행해야 한다.
리눅스 서버에 WAR로 묶은 애플리케이션을 배포한다고 생각해보자.
먼저 리눅스 서버에 WAS를 설치하고 프로젝트를 WAR 등 패키지로 묶은 결과물을 리눅스 서버로 옮겨야 한다. (패키징 과정에서 컴파일 작업 등이 실행된다.)
이제 WAS의 배포 디렉토리에 패키징한 결과를 복사하고 WAS를 실행시키면 애플리케이션이 로드되고 서블릿 / 필터 등이 초기화되며 컨테이너가 생성되고 빈 객체가 초기화된다.
즉, 배포 디렉토리에 WAR를 옮겨두고 WAS를 실행시킬 때 빈 오브젝트가 등록된다.
레거시 프로젝트는 main 메서드가 없으니 WAS가 애플리케이션을 실행시켜주고, 클라이언트의 요청이 들어오면 init 메서드로 요청에 해당하는 서블릿 (웬만하면 디스패처 서블릿) 을 초기화하고 service 메서드로 서블릿을 실행한다. (서블릿도 메인 메서드가 없다)
서블릿들은 Tomcat의 서블릿 컨테이너에서 관리되고, init과 service 메서드는 모두 서블릿 컨테이너가 실행시킨다.
메인 메서드가 없으니 메인 메서드 역할을 하는 요소들이 사용된다고 생각하면 된다.
다시 돌아와서... 꼭 애너테이션을 사용해서 빈 의존관계를 정의할 필요는 없다.
@Configuration
public class Config {
@Bean
public TestClass test(Printer printer) {
TestClass testc = new TestClass();
testc.setPrinter(printer);
return testc;
}
@Bean
public Printer printer() {
return new Printer();
}
}
@Bean 애너테이션이 붙은 메서드는 스프링 컨테이너가 관리하고, 싱글톤을 유지함을 기억하자.
new Printer를 호출한다고 해도 하나의 오브젝트가 반복적으로 반환된다.
위와 같이 작성하면 test 메서드가 실행될 때 printer 파라미터에 Printer 타입의 빈이 자동으로 주입된다. (Autowired를 포함하고 있다고 생각하면 된다)
빈 등록과 빈 의존관계 설정을 어떤 방식으로 진행할지는 자유롭게 선택하면 된다.
기본적으로 스프링의 빈들은 싱글톤으로 만들어지기에 동시에 여러 명의 사용자가 스프링 애플리케이션에 접속을 시도하는 경우에도 하나의 빈을 공유해서 사용한다. (멀티쓰레드 환경에서 공유된다)
대부분의 경우 싱글톤으로 진행해도 상관없지만, 가끔씩 상태를 저장해 둬야 하는 경우가 발생한다.
스코프는 꼭 싱글톤만 있는게 아니다. 요청, 세션, 글로벌세션, 애플리케이션, 프로토타입 스코프가 있으니 상황에 따라 스코프를 적절하게 설정해 사용하자.
'Spring > Spring 3.1' 카테고리의 다른 글
[Spring 3.1] 스프링 MVC와 웹 기술 - 핸들러 (0) | 2023.05.13 |
---|---|
[Spring 3.1] 데이터 엑세스 기술 (0) | 2023.05.11 |
[Spring 3.1] Annotation (0) | 2023.05.04 |
[Spring 3.1] Aspect Oriented Programming (0) | 2023.04.28 |
[Spring 3.1] 트랜잭션과 서비스 추상화 (0) | 2023.04.23 |
댓글
이 글 공유하기
다른 글
-
[Spring 3.1] 스프링 MVC와 웹 기술 - 핸들러
[Spring 3.1] 스프링 MVC와 웹 기술 - 핸들러
2023.05.13 -
[Spring 3.1] 데이터 엑세스 기술
[Spring 3.1] 데이터 엑세스 기술
2023.05.11 -
[Spring 3.1] Annotation
[Spring 3.1] Annotation
2023.05.04 -
[Spring 3.1] Aspect Oriented Programming
[Spring 3.1] Aspect Oriented Programming
2023.04.28