본 글은 김영한님의 강의 내용을 바탕으로 정리한 글입니다.
1. 컴포넌트 스캔과 의존관계 자동 주입
Spring에서는 어노테이션을 활용하여 클래스에 @Component를 명시하여 @Bean을 등록해 줄 수 있고,
@AutoWired라는 어노테이션을 활용해 등록된 Bean을 자동으로 연결시켜줄수 있다.
지금까지 우리는 @Bean을 명시해주는 @Configuration 이 명시된 AppConfig에 쭉 나열해주었었다.
하지만, 그렇게 작업하기에는 추후 수십~수백개의 컴포넌트들을 전부 넣기에는 너무나도 많은 작업이 된다.
스프링은 자동으로 빈을 등록하고 DI을 진행하는 또 다른 방법이 있다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository(){
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository(); //메모리 맴버리포지토리로 생성
}
@Bean
public OrderService orderService(){
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
System.out.println("call AppConfig.discountPolicy");
return new FixDiscountPolicy(); // 고정 할인 정책으로 생성
}
}
위 코드는 기존에 AppConfig 를 통한 Bean 등록과 의존관계 주입코드이다.
@Configuration을 명시한 DI컨테이너에서 생성자를 호출하는 방식으로 의존된 컴포넌트들을 넣어줬다면,
AppConfig의 작성 없이 코드 안에서 @Bean을 등록하고, 의존관계를 주입받아보자.
현재 개발한 도메인에서 우리가 사용하는 빈은 아래와 같이 4가지 이다.
각각의 빈을 AppConfig가 아닌, 자체적으로 클래스안에서 @Component 를 명시하여 등록하고,
그 안에서 의존성을 가지는 컴포넌트는 @AutoWired를 명시하여 자동의존성 주입을 해주도록 바꾼다.
Member 도메인 -> MemberRepository ->MemoryMemberRepository 를 빈으로 등록사용
@Component
public class MemoryMemberRepository implements MemberRepository {
Member 도메인 -> MemberService ->MemberServiceImpl 를 빈으로 등록사용
@Component
public class MemberServiceImpl implements MemberService{
...
@Autowired
public MemberServiceImpl(MemberRepository memberRepository){
...
discount 도메인 -> DiscountPolicy -> RateDiscountPolicy 를 빈으로 등록사용
@Component
public class RateDiscountPolicy implements DiscountPolicy{
order 도메인 -> OrderService -> OrderServiceImpl 를 빈으로 등록사용
@Component
public class OrderServiceImpl implements OrderService{
...
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,DiscountPolicy discountPolicy){
이렇게 변경해주었다면, 이제 AppConfig 없이도 빈 등록과, 의존성이 전부주입된 싱글톤 패턴의 서비스가 수행이 된다.Test를 진행해보자.
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
AppConfig와 그 외에 @Configuration을 명시한 설정클래스는 무시되도록 하나의 클래스를 만들어준다.
이 클래스를 통해 등록된 정말로 자동으로 빈이 등록되고 연결되었는제 조회해보자.
package hello.core.scan;
import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;
public class AutoAppConfigTest {
@Test
void basicScan(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
이러한 방식으로 사용할 Bean에 @Component를 명시하여 자동등록 및 의존성 주입을 실현할 수 있다.
@Component를 명시하면 아래와 같이 빈 객체의 클래스명이 소문자형식으로 네이밍 규칙에 따라 등록되는데,
@Component("memberService2") 와 같이 이름을 지정하여 등록할 수 도 있다.
@AutoWired를 명시하면 등록된 스프링 컨테이너에서 빈객체를 찾아서 주입해준다.
동일 객체 타입이 등록되면, 충돌이 발생할 수 있기때문에, 등록되 연결이름을 신경써주어야 한다.
2. 탐색 위치와 기본 스캔 대상
@ComponentScan을 통해 자동 스캔을 할 때, 다양한 속성을 지정해 줄 수 있다.
1) basePackage 탐색패키지
명시해주게 되면 해당 패키지에 하위에 있는 컴포넌트만 탐색하여 등록한다.
@Configuration
@ComponentScan(
basePackages = "hello.core.member",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
basePackage를 명시해주지 않게된다면,
해당 클래스가 위치한 패키지를 시작 위치로하여 하위 패키지들을 모두 탐색한다.
통상적으로 AppConfig는 프로젝트를 대표하는 구성정보이기 때문에, 프로젝트에 시작루트 패키지에 위치시킨다.
+ 참고 : Spring 프로젝트 생성시 만들어지는 coreApplication에 @SpringBootApplication에
이미 @ComponentScan이 내장되어 있다,
AutoAppConfig를 만들었던 내용은 필요없는 과정이다 (우리의 내용은 테스트와 검증을 위해 만든것임)
package hello.core;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
컴포넌트 스캔 대상
- @Component: 컴포넌트 스캔에서 사용
- @Controlller: 스프링 MVC 컨트롤러에서 사용
- @Service: 스프링 비즈니스로직에서 사용 (가시성, 명시성)
- @Repository: 스프링 데이터 접근 계층에서 사용
- @Configuration: 스프링 설정 정보에서 사용
3. 필터
컴포넌트 스캔에서 추가 / 제외 대상을 지정할 수 있다.
똑같은 어노테이션 2개와 Bean클래스 2개를 각각 다른이름으로 만들어서 추가 / 제외 설정을 해보겠다.
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
package hello.core.scan.filter;
@MyIncludeComponent
public class BeanA {
}
package hello.core.scan.filter;
@MyExcludeComponent
public class BeanB {
}
BeanA는 @MyIncludeComponent를
BeanB는 @MyExcludeComponent를 적용시키고,
Test를 통해 @ComponentScan 속성중에
includeFilters 에 @MyIncludeComponent로 무조건 포함되게 지정,
excludeFilters 에 @MyExcludeComponent가 무조건 제외되도록 지정.
package hello.core.scan.filter;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.assertj.core.api.Assertions.assertThat;
@Configuration
@ComponentScan(
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
public class ComponentFilterAppConfigTest {
@Test
void filterScan(){
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfigTest.class);
BeanA beanA = ac.getBean("beanA",BeanA.class);
assertThat(beanA).isNotNull();
Assertions.assertThrows(NoSuchBeanDefinitionException.class, ()-> ac.getBean("beanB", BeanB.class));
}
}
결과는 BeanA는 생성되고,
BeanB는 생성되지않아 NoSuchBeanDefinition익셉션이 발생됨
Filters 속성에 Type은 5가지 옵션이 있다.
- ANNOTATION : 기본값, 애노테이션을 인식해서 동작한다. ex)org.example.SomeAnnotation
- ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해서 동작한다. ex)org.example.SomeClass
- ASPECTJ : AspectJ 패턴 사용 ex)org.example..*Service+
- REGEX : 정규 표현식 ex)org\.example\.Default.*
- CUSTOM : TypeFilter 라는 인터페이스를 구현해서 처리 ex)org.example.MyTypeFilter
예시코드) excludeFilter에서 ASSIGNABLE_TYPE 을 통해 BeanA를 제외시키는 코드 ↓
@ComponentScan(
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes =MyIncludeComponent.class),
},
excludeFilters ={
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes =MyExcludeComponent.class),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)
})
4. 중복 등록과 충돌
컴포넌트 스캔시 주의해야 하는 부분중 하나가 이름이다.
이름의 충돌이 발생하면, ConflictingBeanDefinitionException 익셉션이 발생한다.
웹 CSS도 인라인 CSS > HEAD CSS > 외부 CSS 순서로 우선권을 가지는 것처럼
수동 빈 등록 (AppConfig - @Confiuration, @Bean) > 자동 빈 등록 (@AutoWired, @Component) 이다
따라서 , 수동과 자동 모두 동일이름이 등록된 경우, 수동 빈 등록이 우선권을 가지므로 자동빈을 덮어쓴다.
@Configuration
@ComponentScan(
basePackages = "hello.core.member",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
}
수동빈등록, 자동빈등록 오류시 Spring boot 에러
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
스프링부트인 CoreApplication을실행 해보면 오류를 볼 수 있다.
소스코드 ↓
'Study > Java Spring Boot' 카테고리의 다른 글
[Spring] 핵심 원리 8 빈 생명주기 콜백 (0) | 2022.02.23 |
---|---|
[Spring] 핵심 원리 7 의존 관계 자동 주입 (@AutoWired) (0) | 2022.02.21 |
[Spring] 핵심 원리 5 싱글톤 컨테이너 (싱글톤 패턴과 주의점) (0) | 2022.02.15 |
[Spring] 핵심 원리 4 스프링 컨테이너와 빈 (Spring 전환) (0) | 2022.02.10 |
[Spring] 핵심 원리 3 객체 지향 원리 적용 (DI컨테이너) (0) | 2022.02.07 |