다른 말로 스프링 IoC 컨테이너 또는 IoC 컨테이너, DI 컨테이너라도도 부른다. 스프링 프레임워크의 핵심 컴포넌트로 애플리케이션 빈의 생명주기를 관리한다.
여기서 말하는 빈이란 스프링 컨테이너가 관리하는 객체를 의미한다.
여기서 말하는 컨테이너란 무엇인가?
컨테이너의 의미
- 소프트웨어 개발 용어의 관점에서 컨테이너란 내부에 또 다른 컴포넌트를 가지고 있는 어떤 컴포넌트를 의미
- 컨테이너는 먼저 객체를 생성하고 객체를 서로 연결
- 객체를 설정하는 단계를 지나 마지막으로 생명 주기 전반을 관리
- 객체의 의존성을 확인해 생성한 후 적절한 객체에 의존성을 주입
BeanFactory
는 빈을 관리하는 역할을 한다.@Bean
어노테이션이 붙은 메서드 명을 스프링 빈의 이름으로 사용한다.
Spring에서 스프링 컨테이너에 해당하는 코드부분이 ApplicationContext interface
이다. 여러 interface들을 상속받고 있다. 단순히 빈의 생명주기만 관리하는 것이 아니라 다른 기능들도 담당하고 있다.
객체는 구성 메타데이터와 결합되어ApplicationContext
(Spring Container)에 빈 등록을 한 후 사용한다.
객체 간의 의존성을 낮추기 위해 Spring 컨테이너를 사용한다. DI와 관련이 있다.
스프링 컨테이너에 의해 관리되는 자바 객체를 의미한다.
@Configuration
: 구성정보를 담당하는 것을 설정할때 사용@Bean
: 메서드에 @Bean 어노테이션을 붙이면 스프링 컨테이너에 자동으로 등록// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
CatStoreService service = context.getBean("myMarket", myMarketService.class);
// use configured instance
List<String> userList = service.getUsernameList();
getBean()
메서드를 호출하면 해당 빈의 부모 빈까지 모두 확인할 수 있다.
Spring은 빈 등록을 XML 또는 JAVA Code로 할 수 있다. 이렇게 다양한 설정형식은 BeanDefinition
이라는 추상화 덕분에 지원할 수 있다.
@Bean
or <bean>
당 각 1개씩 메타 정보가 생성BeanDefinition
인터페이스를 통해 관리하기 때문에 컨테이너 설정을 XML, Java로 할 수 있는 것이다.BeanDefinition
만 알면 된다.bean definition을 만들 때 해당 bean definition에 의해 정의된 클래스의 실제 인스턴스를 만들기 위한 레시피를 만든다. 즉 빈이 존재할 수 있는 범위를 의미한다.
특정 bean 정의에서 생성된 개체에 연결할 다양한 의존성 및 구성 값뿐만 아니라 특정 bean 정의에서 생성된 개체의 범위도 제어할 수 있다.
Scope | Description |
---|---|
singleton | (Default) 각 Spring 컨테이너에 대한 단일 객체 인스턴스에 대한 단일 bean definition의 범위를 지정 |
prototype | 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프 |
request | 웹 요청이 들어오고 나갈때까지 유지되는 스코프 |
session | 웹 세션이 생성되고 종료될 때까지 유지되는 스코프 |
application | 웹의 서블릿 컨텍스와 같은 범위로 유지되는 스코프 |
websocket | 단일 bean definition 범위를 WebSocket의 라이프사이클까지 확장, Spring ApplicationContext의 컨텍스트에서만 유효 |
클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
스프링 컨테이너의 시작과 함께 생성되어서 스프링 컨테이너가 종료될때까지 유지된다.
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
private SingletonService() {}
public void logic() {
System.out.println("싱글톤 객체를 호출");
}
}
static
, final
키워드를 이용해 공용, 값 변경이 안되게 설정하여 getInstance() 메서드를 이용하여 instance를 가지고 오게 한다.
스프링 컨테이너의 기본값은 싱글톤이다.
일반적인 싱글톤 패턴의 문제점은 다음과 같다.
싱글톤 패턴의 문제점
- 싱글톤 패턴을 구현하는 코드가 많다.
- 의존관계상 클라이언트가 구체 클래스에 의존
- private 생성자를 사용하여 자식 클래스를 만들기 어렵기 때문에 유연성 떨어짐
- Application 초기 구동 시 인스턴스 생성하기 때문에 싱글톤 빈이 많을수록 구동 시간이 증가
하지만 스프링 컨테이너는 이러한 싱글톤 패턴의 문제점들을 해결해준다. 스프링 컨테이너는 싱글톤 컨테이너의 역할을 수행한다.
import com.example.cmarket.Order.OrderService;
import com.example.cmarket.Order.OrderServiceImpl;
import com.example.cmarket.discount.CurrentDiscountInfo;
import com.example.cmarket.discount.DiscountInfo;
import com.example.cmarket.user.UserRepository;
import com.example.cmarket.user.UserRepositoryImpl;
import com.example.cmarket.user.UserService;
import com.example.cmarket.user.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl(userRepository());
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(userRepository(), discountInfo());
}
@Bean
public DiscountInfo discountInfo() {
return new CurrentDiscountInfo();
}
}
@Configuration
과 @Bean
어노테이션을 추가해 @Bean
어노테이션을 통해 스프링 컨테이너에 등록하여 싱글톤 방식으로 사용한다.
여기서 주의할 점이 몇가지 있다.
@Bean
은 메서드-레벨 어노테이션이다. 해당 객체가 bean definitions의 소스임을 나타내는 @Configuration
이 지정된 클래스 내부에서 메서드 위에 사용해야한다.
@Bean
을 정의한 코드의 일부이다.@Target
어노테이션을 보면 메서드를 타겟으로 지정하고 있음을 확인할 수 있다.
앞서 포스팅한 DI 유형을 조금 더 자세히 살펴보려고 한다.
@Autowired
를 생략해도 자동 주입 됨NullPointerException
을 방지가능final
로 선언 가능@Component
public class OrderServiceImpl implements OrderService {
private final UserRepository userRepository;
private final DiscountInfo discountInfo;
@Autowired
public OrderServiceImpl(UserRepository userRepository, DiscountInfo discountInfo) {
this.userRepository = userRepository;
this.discountInfo = discountInfo;
}
}
생성자가1개일 때
@Autowired
가 없어도 작동되는 이유
스프링이 해당 클래스 객체를 생성하여 빈에 넣어야 하는데 생성할 때 생성자를 부를 수 밖에 없다. 그래서 빈을 등록하면서 의존 관계 주입도 같이 발생한다.
@Autowired
를 입력하지 않으면 실행이 되지 않는다.@Component
가 실행하는 클래스를 스프링 빈으로 등록한다. 스프링 빈으로 등록한 다음 의존 관계를 주입하게 되는데 @Autowired
가 있는 것들을 자동으로 주입@Component
public class OrderServiceImpl implements OrderService {
private UserRepository userRepository;
private DiscountInfo discountInfo;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Autowired
public void setDiscountInfo(DiscountInfo discountInfo) {
this.discountInfo = discountInfo;
}
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private UserRepository userRepository;
@Autowired
private DiscountInfo discountInfo;
}
과거에는 Setter, 필드 주입을 많이 사용했지만, 최근에는 대부분 생성자 주입 사용을 권장한다. 그 이유에 대해 알아보자
BeanCurrentlyInCreationException
이 발생하여 순환 참조를 방지할 수 있다.스프링은 설정 정보 없이 자동으로 스프링 빈을 등록하는 Component Scan 기능을 제공한다.
앞에서 본 예제들은 @Bean
, @Configuration
어노테이션을 이용하여 직접 작성하고 스프링 빈을 등록하였다. @ComponentScan
어노테이션을 이용하면 @Component
어노테이션이 붙은 모든 클래스를 스프링 빈으로 등록해준다.
또한 의존 관계를 자동으로 주입해주는 @Autowired
기능도 제공한다.
여기서 주의할 점은 @ComponentScan
을 사용하면 @Configuration
이 붙은 설정 정보도 자동으로 등록된다.
그 이유는 @Configuration
어노테이션에 @Component
어노테이션이 붙어 있기 때문이다. @Configuration
설정이 된 파일이 있을 때 아래의 코드를 추가하여 @Configuration
부분을 스캔하지 않도록 한다.
@ComponentScan(excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration 설정.class))
@Component
: 컴포넌트 스캔에서 사용@Controller
& @RestController
: 스프링 MVC 및 REST 전용 컨트롤러에서 사용@Service
: 스프링 비즈니스 로직에서 사용@Repository
: 스프링 데이터 접근 계층에서 사용@Configuration
: 스프링 설정 정보에서 사용@Bean
어노테이션과 @Component
어노테이션의 기능은 스프링 컨테이너에 bean을 등록하는 것으로 비슷한데 차이점을 다음과 같이 확인할 수 있다.
@Bean
은 메서드를 타겟으로
@Component
는 Class를 타겟으로