컴포넌트 스캔
지금까지 살펴본 방법은 @Bean 을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열했다. 이는 개발자 입장에서 매우 귀찮은 작업이다. 때문에 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
컴포넌트 스캔
컴포넌트 스캔을 사용하려면 @ComponentScan 애노테이션을 설정 정보에 붙여주면 된다.
@Configuration
@ComponentScan
public class AutoAppConfig {
}
기존 설정 정보와 다르게 @Bean 을 통해 직접 등록한 스프링 빈이 하나도 없어도, 컴포넌트 스캔을 통해 @Component 애노테이션이 붙은 클래스를 스캔해서 자동으로 스프링 빈으로 등록한다. 이때, basePackages라는 속성을 통해 탐색할 패키지의 시작 위치를 지정할 수 있다. 지정하지 않을 경우, @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
권장하는 방법은 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이다. 최근 스프링 부트도 @SpringBootApplication 애노테이션 안에 @ComponentScan 을 두어, 프로젝트 시작 루트 위치부터 컴포넌트 스캔을 한다. 저기서 excludeFilters 속성은 컴포넌트 스캔에서 제외할 대상을 지정하는 것이고 반대로 includeFilters는 컴포넌트 스캔 대상을 추가로 지정하는 속성이다. 정리하면, @ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다.
컴포넌트 스캔 기본 대상
컴포넌트 스캔은 기본적으로 @Component 애노테이션이 붙은 클래스를 스프링 빈으로 등록한다. 이때, @Controller, @Service, @Repository, @Configuration 과 같은 애노테이션은 내부에 @Component 를 포함고 있기 때문에 해당 애노테이션이 붙은 클래스도 컴포넌트 스캔의 대상이 된다.
- @Controller : 스프링 MVC 컨트롤러로 인식
- @Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해줌
- @Configuration : 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 함
- @Service : ‘핵심 비즈니스 로직이 있겠구나’라고 비즈니스 계층을 인식하는데 도움을 줌 (일종의 표시)
스프링 빈 이름의 중복 등록과 충돌
컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 이때, 자동으로 등록된 빈들의 이름이 같은 경우 스프링은 ConflictingBeanDefinitionException 오류를 발생시킨다.
의존관계 자동 주입
@Autowired 애노테이션은 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아 의존관계를 자동으로 주입해준다. 이를 사용하면, 편리하게 여러 의존관계도 한번에 주입받을 수 있다.
생성자 주입
@Controller
public class Controller{
private Service service;
@Autowired
public Controller(Service service){
this.service = service;
}
}
생성자 주입은 스프링에서 공식적으로 권장하고 있는 방식이다. 생성자 주입은 생성자에 @Autowired을 붙여 의존성을 주입하는 방법으로 객체의 최초 생성 시점에 스프링이 의존성을 주입해준다. 만약 생성자가 1개 밖에 존재하지 않을 경우 @Autowired를 생략할 수 있다. 생성자 주입 방식을 사용하게 되면 필드에 final키워드를 사용할 수 있고 의존성 주입이 최초 1회만 이루어져 객체의 불변성을 확보할 수 있다. 또한 객체 생성 시점에 모든 의존성을 주입해주므로 Null을 의도적으로 넣어주지 않는 한 NullPointerException을 방지할 수 있다. 더하여 순환 참조 문제를 방지하는데 도움이 된다. 순환참조 현상이 존재할 때, 필드 주입과 수정자 주입 방식은 프로그램 실행 중에 런타임 에러가 발생하지만 생성자 주입 방식은 컴파일 시점에 에러가 발생한다. 즉, 컴파일 시점 또는 애플리케이션 구동 시점에 순환 참조로 인한 문제를 발견할 수 있기 때문에 실제 서비스 되기 전에 순환참조 문제를 방지할 수 있다. 때문에 의존 관계를 주입할 때는 생성자 주입을 사용하도록 하자.
수정자 주입
@Controller
public class Controller{
private Service service;
@Autowired
public setService(Service service){
this.service = service;
}
}
수정자 주입은 setter 메소드 위에 @Autowired 을 선언하여 의존성을 주입하는 방법이다. 주로 런타임에 의존성을 수정해주어야 하거나 의존성을 선택적으로 주입할 때 사용한다. 하지만 객체 생성 후 setter 메서드를 호출하기 전까지는 객체가 불완전한 상태에 있을 수 있다. 때문에 객체의 사용 전 모든 필수 의존성이 설정되었는지 확인하는 추가적인 로직이 필요할 수 있다.
필드 주입
@Controller
public class Controller{
@Autowired
private Service service;
}
필드 주입은 멤버 객체에 @Autowired를 붙여 주입받는 방법이다. 즉, 주입 받고자 하는 필드 위에@Autowired 애노테이션을 붙여주기만 하면 스프링이 직접 의존성을 주입해준다. 이 방식은 간단하고 직관적이지만 의존관계를 정확히 파악하기 힘들다는 단점이 있다. 또한 필드 주입은 필드에 직접 의존성을 주입하기 때문에 생성자나 세터를 통한 수동 주입이 불가능해져 직접 의존성을 넣어 줄 수가 없다. 때문에 필드 주입을 사용하게 되면 의존성이 프레임워크에 강하게 종속되는 문제가 발생한다.
옵션 처리
@Autowired 만 사용하면 required 옵션의 기본값이 true로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다. 만약, 스프링 빈으로 등록되지 않은 객체를 주입받으려고 한다면 어떻게 될까?
//호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
System.out.println("setNoBean2 = " + member);
}
//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
System.out.println("setNoBean3 = " + member);
}
required 옵션만 false로 설정한 첫번째 경우는 함수 자체가 호출되지 않는다. 두번째 경우는 null 값이 주입되며, 마지막 경우는 Optional.empty가 호출된다.
Lombok을 이용한 생성자 주입
롬복 라이브러리가 제공해주는 @RequiredArgsConstructor 애노테이션을 사용하면 자동으로 final이 붙은 필드들을 모아서 생성자를 만들어준다.
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserFinder userFinder;
private final UserUpdater userUpdater;
}
즉, 위와 같이 작성만 해도 롬복 라이브러리는 자바의 애노테이션 프로세서라는 기능을 이용해서 컴파일 시점에 생성자 코드를 자동으로 생성해준다.
@Autowired
@Autowired 는 타입으로 조회한다. 즉, getBean(타입) 을 이용하는 것과 유사하게 동작한다. 하지만, 타입으로 조회하기 대문에 부모 타입으로 조회하게 되면 2개 이상의 자식 타입이 조회되면서 오류가 발생하게 된다.
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
private DiscountPolicy discountPolicy
즉, 위의 코드처럼 FixDiscountPolicy, RateDiscountPolicy라는 2개의 구현체가 있고 이들의 부모 클래스인DiscountPolicy라는 인터페이스를 주입하려고 한다면, NoUniqueBeanDefinitionException 예외가 발생하는 것이다.
@Autowired 필드 명 매칭
@Autowired 는 타입 매칭을 시도하고, 이때 타입이 같은 여러개의 빈이 있다면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
@Autowired
private DiscountPolicy rateDiscountPolicy
즉, 위의 코드에서 필드 이름을 rateDiscountPolicy라고 한다면, 필드 명을 기반으로 매칭을 시도하는 것이다. 이처럼 필드 명 매칭은 먼저 타입 매칭을 시도하고 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능이다.
@Qualifier
@Qualifier 는 추가 구분자를 붙여주는 방법이다. 해당 애노테이션은 주입 시, 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
// 의존관계 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("fixDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
사용 방법은 간단하다. 주입시에 @Qualifier 를 붙여주고 등록한 이름을 적어주면 된다.
@Primary
@Primary 애노테이션은 우선순위를 정하는 방법이다. @Autowired 시에 여러 번이 매칭되면, @Primary 애노테이션이 붙여져 있는 빈이 우선권을 가진다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
// 의존관계 주입
@Autowired
public DiscountPolicy setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Primary 는 @Qualifier 와 달리, 의존관계 주입 시에 명시해주지 않아도 자동으로 @Primary 이 붙여진 RateDiscountPolicy가 주입된다. 만약, @Primary와 @Qualifier 이 함께 사용된다면, @Qualifier 의 우선순위가 더 높다.
조회한 여러개의 빈들이 모두 필요하다면?
해당 타입의 여러 개의 빈들이 모두 필요한 경우에는 어떻게 해야할까?
class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
}
}
여러 개의 빈들이 모두 필요하다면, List나 Map 자료구조를 이용하여 필드를 지정하면 된다. List의 경우, 해당 타입의 모든 인스턴스들이 List에 저장되며, Map의 경우에는 빈 이름을 키로, 빈 인스턴스 값을 값으로 하여 Map을 매핑한다.
'Spring' 카테고리의 다른 글
스프링 MVC 2편 - 섹션 4~5 (0) | 2025.03.17 |
---|---|
스프링 MVC 1편 - 섹션 4~7 (0) | 2025.03.17 |
스프링 MVC 1편 - 섹션 1~3 (0) | 2025.03.17 |
스프링 핵심 원리 기본편 - 섹션 9~10 (0) | 2025.03.17 |
스프링 핵심 원리 기본편 - 섹션 4~6 (0) | 2025.03.17 |