Spring DI(Dependency Injection)
목차
- 스프링 컨테이너
- Bean
- 의존관계 주입 DI
- 컴포넌트 스캔
배운 내용
AppConfig
- 애플리케이션의 전체 동작을 구성하기 위해 사용
- 직접 객체를 생성하는 코드에서 생성자를 통해 객체를 주입 받고 해당 객체는 AppConfig에 의해 결정될 수 있도록 코드를 수정
- 서비스Impl는 의존관계에 대해 신경 쓸 필요 없이 실행에만 집중
- 구현 부분에 변경사항이 생길 경우, AppConfig에서 구현 부분만 수정
**스프링 컨테이너(Spring Container)**
컨테이너
: 내부에 또 다른 컴포넌트를 가지고 있는 어떤 컴포넌트
- 객체를 생성하고 객체를 서로 연결
- 객체의 의존성을 확인해 생성한 뒤 적절한 객체에 의존성을 주입
- 스프링 프레임워크의 핵심 컴포넌트
- 내부에 존재하는 애플리케이션 빈의 생명주기를 관리
ApplicationContext
를 스프링 컨테이너라고 하고 인터페이스로 구현되어있다
@Configuration
: 구성정보를 담당하는것을 설정할때 @Configuration 을 붙여준다.
@Bean
: 각 메서드에 @Bean을 붙이면 스프링 컨테이너에 자동으로 등록
- 컨테이너는 개발자가 정의한 Bean을 객체로 만들어 관리하고 개발자가 필요로 할 때 제공
- 의존성 주입을 통해 애플리케이션의 컴포넌트를 관리
- 서로 다른 빈을 연결해 애플리케이션의 빈을 연결하는 역할
- 스프링 컨테이너를 만드는 다양한 방법은
ApplicationContext
인터페이스의 구현체이다.
- 스프링 컨테이너는 Configuration Metadata를사용
- 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록
- new AnnotationConfigApplicationContext(
구성정보.class
)로 스프링에 있는 @Bean의 메서드를 등록 → 애너테이션 기반 컨테이너 구성
- 어떤 객체가 주입될지는 외부 (AppConfig)에서 결정
- 애플리케이션 클래스는 구성 메타데이터와 결합되어 ApplicationContext 생성 및 초기화된 후 완전히 구성되고 실행 가능한 시스템 또는 애플리케이션을 갖게 된다.
스프링 컨테이너 **종류**
**BeanFactory**
- 스프링 컨테이너의 최상위 인터페이스
- 빈을 등록하고 생성하고 조회하고 돌려주는 등 빈을 관리하는 역할
- @Bean이 붙은 메서드의 명을 스프링 빈의 이름으로 사용해
빈 등록
**ApplicationContext**
- BeanFactory의 기능을 상속 + 부가기능
**빈(Bean)**
- 스프링 컨테이너에 의해 관리되는 재사용 소프트웨어 컴포넌트
- 빈(bean)은
인스턴스화된 객체
이다.
@Bean
이 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록
- 빈은 클래스의 등록정보, 게터/세터 메서드를 포함
- 빈은 컨테이너에 사용되는 설정 메타데이터로 생성
- 설정 메타데이터
XML 또는 자바 애너테이션, 자바 코드
로 표현
- 컨테이너의 명령과 인스턴스화, 설정, 조립할 객체를 정의
- ApplicationContext 사용하여 bean 정의를 읽고 액세스가능
getBean
을 사용하여 bean의 인스턴스를 가져올 수 있다.
- 실제적으로 응용 프로그램 코드에서는 getBean() 메서드로 호출하여 사용하면 안됨
**BeanDefinition(빈 설정 메타정보)**
- 속성에 따라 컨테이너가 Bean을 어떻게 생성하고 관리할지 결정
- 스프링이 설정 메타정보를 BeanDefinition 인터페이스를 통해 관리
@Bean
당 각 1개씩 메타 정보가 생성되고, 스프링 컨테이너는 이런 메타 정보를 기반으로 스프링 빈을 생성
- 스프링 컨테이너는 설정 형식이 XML인지 Java 코드인지 모르고 BeanDefinition만 알면 된다
BeandDefiniton 구성
- BeanClassName : 생성할 빈의 클래스 명(자바 설정처럼 팩토리 역할의 빈을 사용하면 없음)
- factoryBeanName : 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
- factoryMethodName : 빈을 생성할 팩토리 메서드 지정, 예) userService
- Scope : 싱글톤(기본값)
- lazyInit : 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때까지 최대한 생성을 지연처리 하는지 여부
- InitMethodName : 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
- DestoryMethodName : 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
- Constructor arguments, Properties : 의존관계 주입에서 사용한다. (자바 설정처럼 팩터리 역할의 빈을 사용하면 없음)
**빈 스코프(Bean Scope)**
- 특정
bean 정의
에서 생성된 개체에 연결할 다양한 의존성 및 구성 값뿐만 아니라 특정 bean 정의
에서 생성된 개체의 범위도 제어할 수 있다.
**싱글톤(singleton) 스코프**
- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는디자인 패턴
- 스프링 컨테이너의 시작과 함께 생성되어서 스프링 컨테이너가 종료될 때 까지 유지
- 단일 인스턴스는 싱글톤 빈의 캐시에 저장된다.
- 이름이 정해진 빈에 대한 모든 요청과 참조는 캐시된 개체를 반환
- 싱글톤 스코프의 스프링 빈은 여러번 호출해도 모두 같은 인스턴스 참조 주소값을 가진다.
- 스프링 컨테이너에서 빈 스코프의 기본값은 싱글톤 스코프이다.
- 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다.
- 싱글톤 객체로 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.
- 싱글톤 방식은 여러 클라이언트가 하나의 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 무상태로 설계해야 한다.
- 특정 클라이언트가 값을 변경할 수 있으면 안된다.
- 읽기만 가능해야 한다.
- 스프링 빈의 공유 값을 설정하면 장애가 발생할 수 밖에 없다.
**AnnotationConfigApplicationContext 를 사용하여 스프링 컨테이너를 인스턴스화**
@Configuration
클래스가 입력으로 제공되면 @Configuration 클래스 자체가 Bean 정의로 등록되고 클래스 내에서 선언된 모든 @Bean 메서드도 Bean 정의로 등록
@Component 클래스
와 JSR-330 클래스
가 제공되면 빈 정의로 등록되며 필요한 경우 해당 클래스 내에서 @Autowired
또는 @Inject
와 같은 DI 메타데이터가 사용되는 것으로 가정
**@Bean**
**빈 선언**
- @Bean 애너테이션을 메서드에 추가해서 Bean으로 정의(선언)
**빈 의존성**
- @Bean 애너테이션이 추가된 메서드는 빈을 구축하는데 필요한 의존성을 나타내는데 매개 변수를 사용가능
**@Configuration**
- 해당 객체가 bean definitions의 소스임을 나타내는 애너테이션
- @Bean-annoted 메서드를 통해 bean을 선언
- @Bean 메서드에 대한 호출을 사용하여 bean 사이의 의존성을 정의
**Bean 사이에 의존성 주입**
- 빈이 서로 의존성을 가질 때, 다른 bean 메서드를 호출하여 나타냄
Bean객체 설정 과정
- AppConfig.java 등의 설정 정보에 작성한 @Bean을 통해 스프링 컨테이너에 빈 객체를 저장
- 빈 객체에 이름은 메서드 이름을 사용해야 한다.
- 직접 부여도 가능
ex) @Bean(name="UserRepository")
- 이름의 경우엔 항상 다른 이름을 부여해야 한다.
- 같은 이름을 부여하면 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생
- 빈 객체가 생성되면 빈 의존관계를 설정
**다양한 의존관계 주입 방법**
**생성자 주입**
- 생성자에 @Autowired를 하면 스프링 컨테이너에 @Component로 등록된 빈에서 생성자에 필요한 빈들을 주입
- 생성자 호출 시점에 딱 1번만 호출되는 것이 보장
- 생성자가 1개만 존재하는 경우에는 @Autowired를 생략해도 자동 주입됨
**수정자 주입 (setter 주입)**
- set필드명 메서드를 생성하여 의존 관계를 주입
- @Autowired를 입력하지 않으면 실행이되지 않음
**필드 주입**
- 필드에 @Autowired 붙여서 바로 주입
- 외부에서 변경이 불가능하여 테스트하기 힘들다
**옵션 처리**
- 주입할 스프링 빈이 없을 때 동작해야하는 경우
- @Autowired만 사용하는 경우 required 옵션 기본값인 true가 사용되어 자동 주입 대상이 없으면 오류가 발생하는 경우가 있음
- 스프링 빈을 옵셔널하게 해둔 상태에서 등록이 되지 않고, 기본 로직으로 동작하게 하는 경우
@Autowired(required=false)
: 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않음
org.springframework.lang.@Nullable
: 자동 주입할 대상이 없으면 null
이 입력됩니다.
Optional<> :
자동 주입할 대상이 없으면 Optional.empty
가 입력됩니다.
**생성자 주입을 사용해야하는 이유**
불변
- 의존 관계 주입은 처음 애플리케이션이 실행될 때 대부분 정해지고 종료 전까지 변경되지 않고 변경되서는 안됩니다.
수정자 주입
같은 경우에는 이름 메서드를 public으로 열어두어 변경이 가능하기 때문에 적합하지 않음.
final 키워드
사용 가능
- 생성자에서 값이 설정되지 않으면 컴파일 시점에서 오류를 확인
순환 참조
방지
- 의존관계 설정이 되지 않으면 객체생성이 불가능
- 테스트 코드 작성 용이
**Component 스캔**
- 설정 정보 없이 자동으로 스프링 빈을 등록하는 기능
@ComponentScan
은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록해주기 때문에 설정 정보에 붙여주면 된다.
- 의존관계도 자동으로 주입하는
@Autowired
기능도 제공
- 컴포넌트 스캔을 사용하면
@Configuration
이 붙은 설정 정보도 자동으로 등록
@Component
- @ComponentScan
이 등록된 곳에서 @Component
를 가져오기 위해 사용됩니다.
@Autowired
- 생성자 의존성 주입에 필요한 설정 정보 대신 의존관계 자동 주입
**basePackages**
- 탐색할 패키지의 시작 위치를 지정하고, 해당 패키지부터 하위 패키지 모두 탐색
@ComponentScan()
의 매개변수로 basePackages = “”
를 사용가능
- 지정하지 않으면,
@ComponentScan
이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
- 설정 정보 클래스의 위치를 프로젝트 최상단에 두고 패키지 위치는 지정하지 않는 방법이 가장 편함
- 스프링 부트를 사용하면
@SpringBootApplication
를 이 프로젝트 시작 루트 위치에 두는 것을 추천
@SpringBootApplication
에 @ComponentScan
포함
**컴포넌트 스캔 기본 대상**
@Component
: 컴포넌트 스캔에서 사용됩니다.
@Controller & @RestController
: 스프링 MVC 및 REST 전용 컨트롤러에서 사용됩니다.
@Service
: 스프링 비즈니스 로직에서 사용됩니다.
@Repository
: 스프링 데이터 접근 계층에서 사용됩니다.
- 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.
@Configuration
: 스프링 설정 정보에서 사용됩니다.
- 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
**필터**
includeFilters
: 컴포넌트 스캔 대상을 추가로 지정합니다.
excludeFilters
: 컴포넌트 스캔에서 제외할 대상을 지정합니다.
- FilterType 옵션
- ANNOTATION:
기본값
, 애너테이션으로 인식해서 동작합니다.
- ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작합니다.
- ASPECTJ: AspectJ 패턴을 사용합니다.
- REGEX: 정규 표현식을 나타냅니다.
- CUSTOM:
TypeFilter
라는 인터페이스를 구현해서 처리합니다.
- ex) @ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))