이 글은 김영한 님의 스프링 핵심 원리 - 기본편(https://www.inflearn.com/스프링-핵심-원리-기본편/dashboard)을 수강하며 학습한 내용을 정리한 글입니다. 모든 출처는 해당 강의에 있습니다.
@Bean
이나 XML의 <bean>
등을 통해 설정 정보에 스프링 빈을 직접 나열하여 등록함@Autowired
기능을 제공함@Autowired
: 의존관계 자동 주입AutoAppConfig.java
기존의 AppConfig.java
는 그대로 두고 AutoAppConfig.java
를 새로 생성한다.
[src/main/java/hello/core/AutoAppConfig.java]
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan( //컴포넌트 스캔 사용
//@Component가 적용된 클래스를 스캔하여 스프링 빈으로 등록
//컴포넌트 스캔 대상에서 제외 할 설정 정보 지정
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
AppConfig
와는 다르게 @Bean
으로 등록한 클래스가 존재하지 않음💡 참고
- 컴포넌트 스캔 사용 시
@Configuration
이 붙은 설정 정보도 자동으로 등록됨
- 앞서 생성한 설정 정보(
AppConfig
,TestConfig
등)도 함께 등록되어 실행 됨- 내부적으로
@Component
가 적용되어 있으므로 수동 등록과 같음=>
excludeFilters
적용하여 제외
- 일반적으로 설정 정보는 컴포넌트 스캔 대상에서 제외하지 않음
@Component
public class MemoryMemberRepository implements MemberRepository { ... }
@Component
public class RateDiscountPolicy implements DiscountPolicy { ... }
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired //ac.getBean(MemberRepository.class)와 같음
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
AutoAppConfig
에 설정 정보가 없으므로, 이 클래스 내에서 의존관계 주입 필요@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
사용 시 여러 의존관계 한 번에 주입 가능[src/test/java/hello/core/scan/AutoAppConfigTest.java]
package hello.core.scan;
import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
public class AutoAppConfigTest {
@Test
void basicScan(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
...
ClassPathBeanDefinitionScanner - Identified candidate component class:
... RateDiscountPolicy.class
... MemberServiceImpl.class
... MemoryMemberRepository.class
... OrderServiceImpl.class
...
Autowiring by type from bean name 'memberServiceImpl' via constructor to bean named 'memoryMemberRepository'
...
... 'orderServiceImpl' via constructor to bean named 'memoryMemberRepository'
... 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'
...
@ComponentScan
@Component
가 적용된 모든 클래스를 스프링 빈으로 등록@Component("원하는 이름")
@Component("memberService2")
@Autowired
: 의존관계 자동 주입getBean(MemberRepository.class)
와 동일한 의미@ComponentScan(
basePackages = "hello.core",
...
)
basePackages
basePackages = {"hello.core", "hello.service"}
basePackageClassed
@ComponentScan
이 적용된 설정 정보 클래스의 패키지(기본) ★com.hello
→ 프로젝트 시작 루트AppConfig
@ComponentScan
설정basePackages
생략com.hello.service
com.hello.repository
@SpringBootApplication
(내부적으로@CompoentScan
포함)을 프로젝트 시작 루트 위치에 둠항목 | 설명 | 부가 기능 |
---|---|---|
@Component | 컴포넌트 스캔에서 사용 | |
@Controller | 스프링 MVC 컨트롤러에서 사용 | 스프링 MVC 컨트롤러로 인식 |
@Service | 스프링 비즈니스 로직에서 사용 | ◾ 특별한 처리 x ◾ 개발자들의 비즈니스 계층 인식에 도움 |
@Repository | 스프링 데이터 접근 계층에서 사용 ex) JPA, JDBC | ◾ 스프링 데이터 접근 계층으로 인식 ◾ 데이터 계층의 예외를 스프링 예외로 변환 ex) 다른 DB로 변경하는 경우, 예외 발생 시 다른 DB의 예외가 발생 → 서비스 및 다른 계층의 코드에도 영향 ∴ 스프링이 예외를 추상화하여 반환 |
@Configuration | 스프링 설정 정보에서 사용 | ◾ 스프링 설정 정보로 인식 ◾ 스프링 빈의 싱글톤 유지를 위한 추가 처리 수행 |
내부적으로 @Component
를 포함한다.
@Component
public @interface Controller {
}
@Component
public @interface Service {
}
@Component
public @interface Configuration {
}
💡 애노테이션에는 상속관계가 존재하지 않는다. 스프링에서 지원하는 기능을 통해 애노테이션에 적용된 특정 애노테이션이 인식 가능한 것이다.
💡
userDefaultFilters
- 유효한 패키지 경로에서 스테레오 타입 애노테이션이 붙은 클래스를 빈으로 등록하는 속성 ex)
@Bean
,@Component
- 기본값은
true
false
로 지정시 기본 스캔 대상들 제외
includeFilters
: 컴포넌트 스캔 대상을 추가로 지정excludeFilters
: 컴포넌트 스캔에서 제외할 대상 지정Annotation
으로 클래스 생성한다.[src/test/java/hello/core/scan/filter/MyIncludeComponent.java]
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
[src/test/java/hello/core/scan/filter/MyExcludeComponent.java]
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
[src/test/java/hello/core/scan/filter/BeanA.java]
package hello.core.scan.filter;
@MyIncludeComponent
public class BeanA {
}
[src/test/java/hello/core/scan/filter/BeanB.java]
package hello.core.scan.filter;
@MyExcludeComponent
public class BeanB {
}
[src/test/java/hello/core/scan/filter/ComponentFilterAppConfigTest.java]
package hello.core.scan.filter;
import org.assertj.core.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.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class ComponentFilterAppConfigTest {
@Test
void filterScan() {
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentfilerAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
assertThat(beanA).isNotNull();
//ac.getBean("beanB", BeanB.class); → 에러 발생
assertThrows(
NoSuchBeanDefinitionException.class,
() -> ac.getBean("beanB", BeanB.class));
}
@Configuration
@ComponentScan(
//BeanA 스프링 등록
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
//BeanB 스프링 등록 제외
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentfilerAppConfig {
}
}
항목 | 설명 |
---|---|
ANNOTATION | ◾ 기본값 → 생략 가능 ◾ 애노테이션을 인식하여 동작 ex) org.example.SomeAnnotation |
ASSIGNABLE_TYPE | 지정한 타입과 자식 타입 인식하여 동작 → 클래스 직접 지정 ex) org.example.SomeClass |
ASPECTJ | AspectJ 패턴 사용 ex) org.example..*Service+ |
REGEX | 정규 표현식 ex) org\.exampe\.Default.* |
CUSTOM | TypeFilter 인터페이스 구현하여 처리ex) org.example.MyTypeFilter |
BeanA
제외하고 싶은 경우
@ComponentScan(
includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class)
},
excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class),
@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)
}
)
💡 참고
includeFilters
는 거의 사용되지 않음excludeFilters
는 간혹 사용될 때가 있지만 많지 않음- 스프링 부트에서 컴포넌트 스캔을 기본으로 제공
→ 스프링의 기본 설정에 최대한 맞추어 사용하는 것을 권장
컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까?
ConflictingBeanDefinitionException
예외 발생@Component //기본 이름 → memoryMemberRepository
public class MemoryMemberRepository implements MemberRepository {}
@Configuration
@ComponentScan {
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
}
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
pubic MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
Overrriding bean definition for bean 'memoryMemberRepository'
with a different definition: replacing
false
로 변경함CoreApplication
을 실행하면 다음과 같은 오류가 발생함Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true
application.properties
에 등록하면 됨📖 참고