일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 스프링 핵심 원리 - 기본편
- 프로그래머스
- BFS
- DP
- 백준
- 스프링
- BinarySearch
- 알고리즘
- 브루트포스
- 그래프 이론
- 동적 프로그래밍
- 정렬
- lower bound
- 자바
- 코딩테스트준비
- 데이터베이스
- 그리디
- Java
- 그래프
- DFS
- 백트래킹
- 99클럽
- 항해99
- 우선순위큐
- Spring
- 개발자취업
- 트리
- 네트워크 계층
- Til
- 완전탐색
- Today
- Total
AtraFelis's Develop Diary
[Spring] 컴포넌트 스캔 (스프링 핵심원리 - 기본편 Section7) 본문
스프링 핵심 원리 - 기본편 강의를 수강하며 작성한 글입니다.
*Section 7
이제까지는 설정 정보, 그러니까 @Configuration
을 이용하여 스프링 빈을 등록해주었다. 하지만 이렇게 일일히 스프링 빈을 등록하는 것도 굉장히 귀찮은 일이다. 프로젝트의 규모가 커지면(언제나 세계 규모의 프로젝트를 진행한다고 상상을 하며 공부를 한다) 빈으로 등록해주어야 할 클래스들도 엄청나게 늘어날 것이다.
이런 귀찮은 일을 해소하기 위해 만들어진 것이, 컴포넌트 스캔이다.
컴포넌트 스캔을 이용하여 의존관계를 자동 주입하는 방법은 간단하다.
구성 파일로 사용하고자 하는 클래스에 @ComponenetScan
을, 스프링 빈으로 등록하고자 하는 클래스에는 @Component
를 붙이면 된다.
@ComponentScan
public class AppConfig {}
@Component
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
@Override
public void save(Member member) { ... }
@Override
public Member findById(Long memberId) { ... }
}
이렇게 하면, 자동으로 스프링 빈에 MemoryMemberRepository가 등록이 된다. 이름은 수동으로 등록할 때 와 동일한 규칙을 적용하여, 따로 지정을 하지 않을 경우 첫 문자만 소문자로 바꾸어 memoryMemberRepository로 스프링 빈에 등록된다.
즉, @Component
가 붙은 클래스가 스캔의 대상이 되어 스프링 컨테이너에 등록이되는 것이다.
그리고 중요한 애노테이션이 하나 더 있는데, 바로 @AutoWired
이다.
이렇게 OrderServiceImpl 같은 경우에는 다른 두 가지의 클래스 memberRepository와 discountPolicy에 의존적이다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
/* 생략 */
}
그렇기에 기존의 AppConfig에서 직접적으로 의존관계를 명시해주었다. 하지만 @ComponentScan
을 사용할 때는 이런 설정 정보 자체가 사라지므로, 클래스 내부에서 직접적으로 명시를 해주어야한다.
그 역할을 하는 것이 바로 @AutoWired
이다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@AutoWired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
/* 생략 */
}
이렇게 생성자 위에 @Autowired
를 붙여주면, 스프링에서 의존관계를 자동으로 주입해준다.
의존 관계 주입 방식 Tip
의존 관계를 주입하는 방법에는 세 가지가 존재한다.
1. 생성자 주입
@Component public class MemberServiceImpl implements MemberService { private MemberRepository memberRepository; @Autowired public MemberServiceImpl(MemberRepository memberRepository) { this.memberRepository = memberRepository; } }
2. setter 주입
@Component public class MemberServiceImpl implements MemberService { private MemberRepository memberRepository; @Autowired public void setMemberRepository(MemberRepository memberRepository) { this.memberRepository = memberRepository; } }
3. 필드 주입
@Component public class MemberServiceImpl implements MemberService { @AutoWired private MemberRepository memberRepository; }
하지만 의존관계를 실행 중 동적으로 변경할 경우는 거의 없으므로 생성자 주입을 권장한다. 이것에 대한 더 자세한 설명은 바로 다음 Section에 나온다.
컴포넌트 스캔의 동작 과정
1. @ComponentScan
@Component
가 붙은 모든 클래스를 스캔하여, 스프링 컨테이너에 등록한다. 이때 스캔의 대상은 따로 지정하지 않았다면, @ComponentScan
이 붙은 설정 파일과 동일한 패키지와 그 하위 패키지가 된다.
위에서 언급했지만, 스프링 빈의 이름은 지정하지 않았을 시, 클래스 명에서 첫 문자만 소문자로 바꾸어 스프링 컨테이너에 등록한다.
💡 Tip 이름 부여하는 법
이름을 따로 지정하고 싶을 경우, @Component("AtraFelis")와 같이 지정해줄 수 있다. 하지만 이전 포스팅에서도 말했듯, 타당한 이유 없이 자의적으로 변경하는 것은 개발에 혼란을 불러올 수 있으므로 권장하지 않는다.
2. @
AutoWired
스프링 빈에 등록을 했으므로, 이 스프링 빈 객체를 이용해 의존관계를 주입해준다. @AutoWired
가 붙어 있는 생성자(혹은 필드, setter 메소드)를 찾아 스프링 컨테이너가 자동으로 주입해준다.
컴포넌트 스캔의 대상
@Component
가 붙은 클래스가 컴포넌트 스캔의 대상이라고 하였다. 하지만, 이것이 이외에도 컴포넌트 스캔이 되는 애노테이션이 더 존재한다.
// Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service { ... }
// Controller
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller{ ... }
// Repository
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository{ ... }
// Configuration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration{ ... }
위 애노테이션들을 보면 전부 @Component
를 포함하고 있는 것을 알 수 있다. 즉, 이 애노테이션을 붙이면 자동으로 @Component
가 붙은 것과 동일하게 컴포넌트 스캔의 대상이 되는 것이다.
또, 여기서 한 가지 중요한 것은 설정 파일을 의미하는 @Configuration
도 컴포넌트 스캔의 대상이라는 것이다. 이전의 기억을 떠올려보면, AppConfig또한 스프링 빈에 등록되었던 것을 알 수 있을 것이다.
그 이유가 바로 @Configuration
에 포함된 @Component
때문이다.
어? 하지만 기존에는 @Configuration
을 스캔할 클래스가 없었지 않나요?
날카로운 의문이다. 이 의문에 대한 답은 바로 @SpringBootApplication
에 존재한다.
@SpringBootApplication
스프링 부트를 이용해 스프링 프로젝트를 처음 생성하면, 이런 애노테이션이 붙은 클래스가 기본적으로 생성되어 있다.
이 애노테이션을 직접 확인해보면,
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan( ... )
public @interface SpringBootApplication { ... }
@ComponentScan
이 붙어 있는 것을 알 수 있다.
즉, 스프링 부트를 사용한다면 따로 설정 파일 클래스를 만들 필요 없이, 스프링 빈으로 등록하고 싶은 클래스에만 @Comopnent
, @AutoWired
를 붙이면 되는 것이다.
[스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술]
[지금 무료]스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 | 김영한 - 인프런
김영한 | 스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확
www.inflearn.com
이 강의를 들으셨던 분은 생각해보면 설정 파일을 따로 만들기 전에 @Service
, @Repository
등의 애노테이션를 이용해 자동으로 스프링 빈에 등록했던 기억이 있을 것이다.
@ComponentScan
으로 설정 파일을 구성하지 않았음에도, 그것이 되었던 이유가 바로 @SpringBootApplication
때문이다.
또 위에서 던졌던 질문인 "@Configuration
을 스캔하는 클래스가 없었지 않나요?"에 대한 답도 이것으로 되었을 것이다.
중복 등록
자동 빈 등록 vs 자동 빈 등록
자동으로 등록 시에는 이미 같은 이름으로 등록된 스프링 빈이 존재하면, ConflictingBeanDefinitionException
예외가 발생한다.
수동으로 등록한 빈들끼리는 이름이 중복되었을 경우에는 설정에 따라 하나의 빈을 덮어버려 이 예외가 발생하지 않을 수 있다.
자동 빈 등록 vs 수동 빈 등록
이 경우에는 수동 빈이 자동 빈을 오버라이딩하는 형태로 우선권을 가진다.
Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing
이런 로그가 나타난다.
하지만, 스프링 부트에서는 이 경우 오류가 발생하도록 기본값으로 설정이 되어있다. @SpringBootApplication
이 붙은 스프링 부트 클래스를 실행해보면, 실행되지 않고 에러가 나타나는 것을 확인할 수 있다.
References
'Programming > Spring' 카테고리의 다른 글
[Spring] 빈 생명주기 콜백 (스프링 핵심원리 - 기본편 Section9) (0) | 2025.01.23 |
---|---|
[Spring] 의존관계 자동 주입 (스프링 핵심원리 - 기본편 Section8) (1) | 2025.01.19 |
[Spring] 싱글톤 컨테이너 (스프링 핵심원리 - 기본편 Section6) (0) | 2025.01.13 |
[Spring] 스프링 컨테이너와 스프링 빈 (스프링 핵심원리 - 기본편 section5) (0) | 2025.01.12 |
[Spring] 객체 지향 원리 적용하기 (스프링 핵심원리 - 기본편 section4) (0) | 2025.01.11 |