Spring Container
스프링 컨테이너는 스프링 프레임워크의 핵심 컴포넌트이다.
자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공한다.
스프링에서는 자바 객체를 빈(Bean)이라 한다.
즉, 내부에 존재하는 빈의 생명주기를 관리(빈의 생성, 관리, 제거 등)하며, 생성된 빈에게 추가적인 기능을 제공하는 것이다.
스프링 컨테이너의 종류
스프링 컨테이너는 Beanfactory와 ApplicationContext 두 종류의 인터페이스로 구현되어 있다.
빈 팩토리는 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트이고,
빈 팩토리를 좀 더 확장한 것이 애플리케이션 컨텍스트이다.
BeanFactory
- 스프링 컨테이너 최상위 인터페이스이다.
- 빈을 등록, 생성, 조회 등의 빈을 관리하는 역할을 하며, getBean() 메서드를 통해 빈을 인스턴스화 할 수 있다.
ApplicationContext
- BeanFactory의 기능을 상속받아 제공한다.
- BeanFactory의 기능 외의 부가 기능을 제공한다.
- MessageSource : 메시지 다국화를 위한 인터페이스.
- EnvironmentCapable : 개발, 운영, 환경변수 등으로 나누어 처리하고,
애플리케이션 구동 시 필요한 정보들을 관리하기 위한 인터페이스. - ApplicationEventPublisher : 이벤트 관련 기능을 제공하는 인터페이스.
- ResourceLoader : 파일, 클래스 패스, 외부 등 리소스를 편리하게 조회.
스프링 컨테이너 사용 이유
먼저, 객체를 생성하기 위해서는 new 생성자를 사용하는데 이렇게 생성하면 객체가 많아지고 서로 참조하게 된다.
객체 간의 참조가 많을수록 의존성이 높아지는데, 의존성이 높아지면 객체간의 결합도가 높아진다.
ex) A클래스에서 B클래스를 직접 생성하면, B를 C로 바꿀 경우 A의 코드를 수정해야 한다.
이는 객체지향 프로그래밍의 핵심과는 거리거 멀기 때문에 객체간의 의존성을 낮추기 위해 스프링 컨테이너를 사용한다.
또한, 기존의 방식으로는 새로운 기능이 생기게 되면 변경 사항들을 수작업으로 수정해야 한다.
프로젝트가 커질수록 의존도는 높아질 것이고, 변경도 많아질 것이다.
하지만, 스프링 컨테이너를 사용하면 구현 클래스에 있는 의존성을 제거하고 인터페이스에만 의존하도록 설계할 수 있다.
스프링 컨테이너 생성
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
- ApplicationContext 를 스프링 컨테이너라 부른다.
-
스프링 컨테이너는 XML을 기반으로 만들 수 있고, 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.
어노테이션 기반으로 컨테이너를 구성하고 등록.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
@Configuration
스프링 컨테이너가 해당 어노테이션이 붙은 클래스를 설정 정보로 사용한다.
클래스 내부에 @Bean 어노테이션이 적힌 메서드를 모두 호출하여 얻은 객체를 스프링 컨테이너에 등록하게 된다.
스프링 컨테이너에 등록된 객체를 스프링 빈(Bean)이라 한다.
어노테이션 기반으로 위 코드에서 구성한 스프링 컨테이너를 생성.
public class MemberApp {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class)
}
}
IoC
IoC란 프로그램의 제어 흐름 구조가 바뀌는 것이다.
대부분 main()같은 프로그램이 시작되는 지점에서 다음에 사용할 오브젝트를 결정, 생성하고
만들어진 오브젝트 내의 메소드를 호출하는 작업을 반복한다.
즉, 모든 종류의 작업을 사용하는 쪽에서 제어하는 구조이다.
하지만 IoC는 거꾸로 뒤집는다.
오브젝트는 자신이 사용할 오브젝트를 스스로 생성하거나 선택하지 않고,
자신이 어떻게 만들어진지 어디서 사용되는지 알 수 없다.
모든 제어 권한은 자신이 아닌 다른 대상에게 위임하는 것이다.
요약
- 작업을 수행하는 쪽에서 객체 를 생성하는 제어 흐름의 개념을 거꾸로 뒤집는다.
- IoC 에선 객체가 자신이 사용할 객체를 생성하거나 선택하지 않고 어디서 어떻게 사용되는지 모른다.
- 모든 객체는 제어 권한을 위임받는 특별한 객체 에 의해 만들어지고 사용된다.
클래스 호출 방식
클래스 호출 방식
클래스내에 선언과 구현이 같이 있기 때문에 다영한 형태로 변화가 불가능하다.
인터페이스 호출 방식
클래스를 인터페이스와 구현클래스로 분리.
구현 클래스 교체 및 변화가 다양해 지지만 구현클래스 교체시 호출클래스의 코드에서 수정이 필요하다.(DIP 위반)
팩토리 호출 방식
팩토리가 구현클래스를 생성하기 때문에 호출은 팩토리를 호출하는 것만으로 충분하다.
구현클래스 변경 시 팩토리만 수정하면 되기 때문에 호출클래스에는 영향을 미치지 않는다.
하지만 팩토리를 호출하는 클래스의 코드를 작성해야 하기 때문에 팩토리에 의존하게 된다.(OCP 위반)
IoC
클래스는 어떤 곳에서도 의존하지 않는다.
조립기에서 의존성이 삽입된다는 의미로 DI라고도 표현한다.
IoC 용어
- bean : 스프링 컨테이너가 관리하는 자바 객체.
- bean factory : 스프링의 IoC를 담당하는 핵심 컨테이너.
- application context : bean factory를 확장한 IoC컨테이너.
- configuration metadata : application context 혹은 bean factory 가 IoC를 적용하기 위해 사용하는 메타정보.
- container(IoC container) : IoC방식으로 bean을 관리한다는 의미에서 bean factory 나 application context를 가리킨다.
IoC 구현 방법
IoC 는 두 가지 방법으로 구현 가능하다.
- DL(Dependency LookUp) - 의존성 검색
- DI(Dependency Injection) - 의존성 주입
여기선 DI만 알아볼 것이다.
DI(Dependency Injection) - 의존성 주입
각 계층 사이, 각 클래스 사이에 필요한 의존 관계를 빈 설정을 통해 컨테이너가 자동으로 연결해주는 것.
DI 두 가지 방법
- Setter Injection - 인자가 없는 생성자나 인자가 없는 static factory 메소드가
bean을 인스턴스화 하기 위하여 호출된 후 bean의 setter 메소드를 호출하여 실체화하는 방법
- 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다.
- 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 public으로 설정하는 것은 좋은 설계가 아니다.
- Constructor Injection - 생성자를 이용하여 클래스 사이의 의존 관계를 연결
- 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부 분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.
- 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다.
즉, 의존성 주입은 생성자 주입을 선택하는 것이 좋다.
DI - 생성자 주입
생성자 주입을 사용해야 하는 이유
- 객체의 불변성 확보
- 실제로 의존 관계의 변경이 필요한 상황은 거의 없다.
하지만 수정자 주입이나 필드 주입을 이용하면 수정의 가능성을 열어두기 때문에 유지보수성이 떨어진다.
- 실제로 의존 관계의 변경이 필요한 상황은 거의 없다.
- 테스트 코드의 작성
- 테스트가 특정 프레임워크에 의존하는 것은 안 좋다.
그래서 가능한 순수 자바로 테스트를 작성하는 것이 가장 좋은데,
생성자 주입이 아닌 다른 주입으로 작성된 코드는 순수한 자바 코드로 단위 테스트를 작성하는 것이 어렵다.
- 테스트가 특정 프레임워크에 의존하는 것은 안 좋다.
- 스프링에 의존하지 않는 코드 작성
- 필드 주입을 사용하려면 @Autowired를 이용해야 하는데,
이것은 스프링이 제공하는 어노테이션이며 사용하면 스프링에 의존하게 된다.
- 필드 주입을 사용하려면 @Autowired를 이용해야 하는데,
- 순환 참조 에러 방지
- 객체가 생성될 때 해당 객체가 필요로 하는 모든 의존성이 생성자를 통해 주입되기 때문에 순환 참조가 발생하지 않습니다.
생성자 주입 예시 코드
public interface UserService {
void join(User user);
}
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Override
public void join(Member member) {
userRepository.save(member);
}
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
public class UserRepository {
}
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl(userRepository());
}
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// AppConfig에서 UserService를 가져옴
UserService userService = context.getBean(UserService.class);
// UserService의 기능 사용
userService.doSomething();
}
}
'스프링' 카테고리의 다른 글
Spring - MVC (0) | 2024.05.20 |
---|---|
Spring - ComponentScan(feat. Autowired) (0) | 2024.04.18 |
스프링 시큐리티 defaultSecurityFilterChain (1) | 2023.12.20 |
스프링 시큐리티 내부 흐름 (1) | 2023.12.18 |
스프링 DI (0) | 2023.12.17 |