Spring IoC & DI(2)

IoC란?

Inversion of Control (제어의 역전) 이라는 의미로 사용되고 있다. 보통의 프로그램에서는 개발자가 필요한 객체를 직접 new를 통해 생성하여 사용한다. 하지만 Spring과 같은 프레임워크에서는 흐름의 주체가 Container가 되어 Container에서 객체의 생성과 소멸을 관리하며 개발자가 개발한 애플리케이션 코드를 호출하여 프로그램의 흐름을 관장한다.

따라서 프로그램의 흐름을 개발자가 관장하는 것이 아닌 프레임워크에게 그 권한이 넘어갔다(역전되었다.) 하여 제어의 역전 (Inversion of Control)이라 불린다.

Container가 도데체 뭐람?

containers

개발 공부를 하다보면 은근 컨테이너(Container)라는 용어를 많이 듣게 된다. Spring 컨테이너, 서블릿 컨테이너, EJB컨테이너등등.. Container라는 용어를 듣게 되는데 컨테이너는 도데체 무엇일까? 위의 이미지와 연상해보면 무엇인가를 담아놓은 박스를 연상할 수 있다. 이러한 컨테이너 말고도, 컨테이너 처럼 생긴 공장도 있다. 이게 개발자가 상상해야 하는 컨테이너와 부합하는 이미지 같다. 컨테이너 내부에 물건을 적재하고 물건에 대한 관리도 하는.. 컨테이너를 생각하는 것이 도움이 될 것 같다. 실제 프레임워크에서의 컨테이너도 같은 역할을 한다.

이는 객체에 대한 생성부터 소멸까지 생명주기를 관리하며, 시스템이 로드 될 때, 필요한 객체를 생성하여 컨테이너에 담아두고 필요 할 때 사용한다. 뿐만아니라 객체에 대한 추가적인 기능을 제공하는 주체라고 할 수 있다.

위에서 예시로 든 컨테이너를 바탕으로 생각을 해보면

서블릿 (Servlet) 컨테이너는 WAS(Web Application Server) 내 에서 서블릿 객체의 생성, 소멸 대한 처리를 담당하며, 외부에서 요청이 들어오면 서블릿 객체를 사용한다. 이 뿐만 아니라 Spring Security 같은 Servlet filter를 이용하여 추가적인 기능을 제공한다.

Spring Container는 POJO 클래스를 Bean으로 생성하고 소멸까지 생명주기를 관리한다. 또한 Bean이 호출 될 때 의존성을 갖는 클래스에 대해 컨테이너에서 필요한 객체를 찾아 주입해 준다. 이 뿐만 아니라 객체에 대한 Transaction, Security, Pooling에 대한 기능을 추가적으로 제공하고 있다.

DI란?

DI란 Dependency Injection의 의미로 의존성 주입이라는 뜻을 가지고 있다. DI는 IoC라는 개념을 실제로 구현한 대표적인 예이며, 대부분 IoC와 혼용하여 사용한다. 개념 자체는 IoC의 형태 중 하나라고 볼 수 있다.

DI는 Spring 프레임워크에서 객체의 의존성 관계를 생성하는 역할을 한다. 클래스 내에서 개발자가 new를 통해 객체 생성을 직접 하지 않아도 DI를 통해 객체를 주입하여 컴포넌트화 하여 객체 내에서 사용 할 수 있다는 장점이 있다.

Spring에서는 @Autowired 어노테이션을 통해 간단하게 의존성 주입 설정을 할 수 있으며 객체 내에 의존성 객체를 주입하는 방식은 4가지 정도 있다.

  1. Constructor Injection
  2. Dependency Constructor Injection
  3. Setter Injection
  4. Field Injection

의존성 주입 Annotation

Spring/Java에서는 의존성 주입을 위한 어노테이션을 제공하여 간단하게 의존성 주입을 구현할 수 있다.

  • @Autowired
    • Spring에서 지원하는 Annotation
    • Type에 맞춰서 주입이 된다.
    • Interface에 상응하는 Bean이 2개 이상 인 경우에는 @Qualifier("beanName")을 혼용하여 필요한 Bean객체를 주입한다.
  • @Inject
    • Java에서 지원하는 Annotation
    • Type에 맞춰서 주입이 된다.
  • @Resource
    • Java에서 지원하는 Annotation
    • 이름에 맞춰서 주입이 된다.

Spring Framework 기반에서 Java Framework로의 Conversion 계획이 있다면, @Inject/@Resource를 사용하면 문제는 없을 것이나.. 대부분은 @Autowired를 사용한다.

DI 구현방식

Constructor Injection

생성자에 @Autowired 어노테이션을 이용하여 UserRepository 객체를 삽입하는 방법이다. 이 방법은 xml 설정 시에 유용한 방법이다. 객체 생성 시, 주입을 하기 때문에 객체가 생성된 이후에는 주입된 객체를 변경 할 수 없다.

1
2
3
4
5
6
7
8
9
public Class UserService {

private UserRepository userRepository;

@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}

1
2
3
4
5
6
<bean id="userRepository" class="com.example.user.UserRepository"></bean>
<bean id="userService", class="com.example.user.UserService">
<construct-args>
<ref bean="userRepository"></ref>
</construct-args>
</bean>

Extension Constructor Injection

Spring 4에 추가된 DI방법이다.

1
2
3
4
5
6
7
public Class UserService {

private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}

UserService에 @Autowired를 선언 하지 않아도 의존성 주입을 할 수 있게 되었다. 어찌보면 당연한 내용 같은데... Lombok을 이용해서 의존성 주입이 가능해 졌다는 점이다.

1
2
3
4
@AllArgsConstructor
public Class UserService {
private UserRepository userRepository;
}

Setter Injection

Setter를 통해 DI하는 방법이다. 이 방법은 외부에서 setter를 통해 의존성 객체를 변경 할 수 있는 소지가 있어 상당히 주의해야 한다.

1
2
3
4
5
6
7
8
9
public Class UserService {

private UserRepository userRepository;

@Autowired
public setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}

Field Injection

Spring을 사용하는 개발자들이 가장많이 사용하는 DI방법이다. 아무래도 코드량이 적으니 다들 많이 사용하는 방법인 듯 싶다.

1
2
3
4
public Class UserService {
@Autowired
private UserRepository userRepository;
}

용어 설명

  • Bean
    • Spring Container에 등록 되는 POJO객체
    • Spring Application의 Component들이 등록 된다.
    • 기본적으로 Bean은 특별한 경우가 아닌이상 Stateless(상태정보는 없음) Bean을 등록한다.
  • Bean Factory
    • Spring IoC를 담당하는 핵심 컨테이너
    • Bean에 대한 등록/생성/조회/소멸을 관리한다.
    • BeanFactory는 interface이며 주로 구현체인 ApplicationContext를 사용한다.
    • 대표적으로 getBean() 메서드를 제공한다. (Bean의 Meta 정보를 바탕으로 Bean 조회)
  • Application Context
    • BeanFactory 인터페이스의 구현체 ()
      • 정확히는 ListableBeanFactory
      • Bean을 Listable하게 보관하는 인터페이스를 말한다.
    • BeanFactory의 기능 이외에 Spring의 추가적인 기능을 제공한다.
      • ResourceLoader - ApplicationContext 로드에 필요한 설정 파일들을 로드한다.
      • ApplicationEventPublisher - 이벤트 발생을 관장한다.
      • MessageSource - properties파일을 통해 다국어 설정이 가능
      • BeanLifecycle - Bean의 초기화, 소멸을 담당한다.
  • Configuration Meta Data
    • Application Context에서 IoC 설정을 위해 사용되는 메타정보
    • 컨테이너에 어떤 기능을 세팅할때 사용
    • Bean 생성/구성할 경우에도 사용
    • @Configuration 어노테이션을 통해 클래스로 설정 정보 구현이 가능

Bean 등록 방법

Component-Scan

Spring에서 가장 편하게 Bean을 등록할 수 있는 방법이다. Spring에서 component-scan에 package나 class를 설정하면 해당 class들에 대해 @Component 어노테이션이 달려 있는 경우 자동으로 Spring Bean으로 등록 해주는 기능이다. 분명 편리한 기능이긴 하지만 여러 프로젝트에서 공통적으로 패키지를 사용하는 경우에는 안쓰는 Bean들도 생길 수 있기 때문에 불필요한 메모리 할당이 이루어질 수 있다. 그렇기는 해도 최근 Spring을 사용하여 개발하는 경우에는 가장 많이 하는 설정이 아닐까 싶다.

  • @Component 종류
    • @Controller : Presentaion Layer의 Bean을 선언 (주로 외부로부터 요청을 받는 Controller Class)
    • @Service : Service Layer의 Bean을 선언 (핵심 비지니스 로직을 구현한 Class)
    • @Repository : Persistent Layer의 Bean을 선언 (주로 DB, Cache와 같은 저장소와 연동을 위한 Class)
    • @Configuration : Configuration Layer의 Bean을 선언 (주로 Bean을 생성하거나, Spring Container에 대한 설정을 하는 Class)

Servlet-Context.xml

기존 Bean 생성을 xml로 등록하는 방식이다. 이 방식을 Bean등록을 한군데에서 모아 볼 수 있다는 장점이 있지만... 보통의 Enterprise급 프로젝트에서는 설정하는 Bean이 너무 많아 오히려 복잡하다. 위에서 언급한 Component-scan을 주로 사용하는 것이 좋다.

1
2
3
4
5
6
7
8
<bean name="beanNameViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="1"/>
</bean>
<bean name="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="2"/>
</bean>

@Configuration

xml파일에서 Bean등록을 하던 역할을 Java Config로 할 수 있도록 해준다. 뿐만아니라 @Configuration 어노테이션을 붙인 클래스는 Spring Container에 대한 설정까지 추가 할 수 있다. Bean을 추가하고자 하는 경우에는 추가하고자 하는 Bean을 return 하는 메서드를 만들어 @Bean 어노테이션을 붙여주면 된다.

1
2
3
4
5
6
7
@Configuration
public class UserConfig {
@Bean
public UserService userService() { //메소드 명이 기본적으로 Bean Name으로 추가된다.
return new UserService();
}
}

Java Config를 이용할 수 있다는 장점과 Test Case 작성 시 Mock객체를 Bean으로 생성해야 하는 경우 아주 유용한 방법이라 할 수 있겠다.

Bean 생명주기

Spring Container에서는 Bean의 생성~소멸까지 Bean을 관리한다. Bean의 생성 시점과 소멸 시점에 대한 처리를 할 수 있는 기능을 제공하고 있다.

Bean 생명주기 처리를 하는 방법은 3가지 방식이 있다.

  1. InitializingBean, DisposableBean 인터페이스 구현
  2. Bean정의 시 , 메소드 지정
  3. @PostConstruct, @PreDestroy Annotation 사용

InitializingBean, DisposableBean 인터페이스 구현

InitializingBean, DisposableBean 인터페이스를 구현 하여, Bean의 생성과 소멸 시 기능을 확장 할 수 있다.

InitializingBean, DisposableBean 클래스는 Spring에서 제공하는 인터페이스 이기 때문에 Spring에 종속적이게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestBeanClass implements InitializingBean, DisposableBean{

private Connection conn;

@Override
public void afterPropertiesSet() throws Exception {
//자원의 할당 처리
conn = DBConnectionCaller.getConnection();
System.out.println("bean 생성 및 초기화 : afterPropertiesSet() 호출됨");
}

@Override
public void destroy() throws Exception {
//주로 할당된 자원의 해제를 한다.
if(conn != null) conn.close();
System.out.println("bean 소멸 : destroy 호출됨");
}
}

Bean정의 시 , 메소드 지정

1
2
<bean id="testBean" class="com.example.TestBeanClass"                      
init-method="init" destroy-method="destroy"/>

  • xml설정 파일 <bean>에서 init-method 지정 시 Bean생성과 properties의존성 주입 후 지정한 메소드 호출
  • xml설정 파일 <bean>에서 destroy-method 지정 시 Bean소멸 전 콜백으로 지정한 메소드 호출

@PostConstruct, @PreDestroy Annotation 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestBeanClass {be

private Connection conn;

@PostConstruct
public void init() {
//자원의 할당 처리
conn = DBConnectionCaller.getConnection();
System.out.println("bean 생성 및 초기화");
}

@PreDestroy
public void destroy() {
//주로 할당된 자원의 해제를 한다.
if(conn != null) conn.close();
System.out.println("bean 소멸");
}
}

@PostConstuct, @PreDestroy 은 JSR-250 Annotation 기반이다 (한마디로 자바기반) 따라서 Spring프레임워크에 종속적이지 않다는 장점이 있다. 따라서 Spring 기반에서 다른 Java기반의 프레임워크로 이식 할 경우 별도로 수정할 필요가 없다.

@PostConstuct, @PreDestroy 을 사용하기 위해서는 <annotation-config/>설정이나, @AnnotationDrivenConfig어노테이션을 붙여줘야 한다.

Bean Scope

https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html 을 참고함

  • Singleton Scope : IoC Container 단위 별로 Bean이 생성 된다.
  • Prototype Scope : Bean이 사용되는 시점 별로 다른 Bean이 생성된다.
  • Request Scope : Web프로젝트에서 http 요청 별로 Bean이 생성된다. http의 Lifecycle동안 같은 Bean을 사용
  • Session Scope : Request Scope와 마찬가지로 Web 프로젝트에서 사용하며, Session별로 Bean이 생성된다. (이 경우에는 Bean에 Session정보를 세팅하여 사용할 수 있다.)
  • Global Session Scope : Request Scope와 마찬가지로 Web 프로젝트에서 사용하며, Global Http Session별로 다른 Bean을 생성한다. 일반적으로 Portlet Context에서 사용된다.

참조

http://www.javajigi.net/pages/viewpage.action?pageId=3664&focusedCommentId=3751#Spring%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EC%86%8C%EA%B0%9C%EC%99%80IoC%EB%B0%8FSpringIoC%EC%9D%98%EA%B0%9C%EB%85%90-2.1IoC%EB%9E%80%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%3F http://wiki.javajigi.net/pages/viewpage.action?pageId=281 https://jongmin92.github.io/2018/02/11/Spring/spring-ioc-di/ https://okky.kr/article/415474 http://www.javajigi.net/pages/viewpage.action?pageId=68 http://www.javajigi.net/download/attachments/5614/V3_InversionOfControl.pdf?version=1 http://isstory83.tistory.com/91 https://spring.io/blog/2016/03/04/core-container-refinements-in-spring-framework-4-3 토비의 스프링 VOL.2 Spring IoC