@Bean이나 XML의 <bean> 등을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열했다.컴포넌트 스캔이라는 기능을 제공한다.@Autowired 기능도 있다.@ComponentScan은 @Component 애노테이션이 붙은 클래스를 찾아서 자동으로 스프링 빈에 등록한다.package com.example.demo;
import com.example.demo.member.MemberRepository;
import com.example.demo.member.MemoryMemberRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
// basePackages = "com.example.demo.member",
//어디서부터 찾는지 시작 위치를 설정해줄 수 있다.
//지정을 하면
//이 패키지를 포함한 하위 패키지를 모두 탐색한다.
//만약 지정하지 않으면 `@ComponentScan`이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
//권장하는 방법은 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
//스프링 빈 등록하는 것 중에 뺄 것이 있으면 빼주는 부분
//AppConfig에서 @Configuration을 썼기 때문에 제외해주는 것이다. 중복 등록이 될 수 있기 때문에
)
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
*이제 스프링 빈을 등록한 자바 파일로 가서 @Component 애노테이션만 붙혀주면 된다.
MemberServiceImpl
package com.example.demo.member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired //자동 의존관계 주입 //ac.getBean(MemberRepository.class)과 비슷하게 동작
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
//생성자 주입 , AppConfig가 대신 해준다.
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
//테스트 용도
public MemberRepository getMemberRepositoy() {
return memberRepository;
}
}
OrderServiceImpl
package com.example.demo.order;
import com.example.demo.discount.DiscountPolicy;
import com.example.demo.member.Member;
import com.example.demo.member.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
this.memberRepository = memberRepository;
}
//생성자 주입 , AppConfig가 맡아서 한다.
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
public MemberRepository getMemberRepository(){
return memberRepository;
}
}
그렇다면, 이렇게 애노테이션을 통해 빈을 등록하는 것까지는 좋지만 가장 중요한 의존 관계 주입은 어떻게 하는걸까?
이때 쓰이는 것이 바로 @Autowired이다.
@Autowired를 사용하면 생성자에서 여러 의존관계도 한번에 주입받을 수 있다.
AnnotationConfigApplicationContext를 사용하는 것은 기존과 동일
다만, 설정 정보로 AutoAppConfig클래스를 넘겨준다.
@Component이 붙은 모든 클래스를 찾아서 스프링 컨테이너에 싱글톤으로 자동 등록
스프링 빈의 이름은 애노테이션이 붙은 클래스명을 사용하되 맨 앞글자만
소문자를 사용
@Autowired 애노테이션은 생성자에 파라미터가 많아도 타입으로 다 찾아서 자동으로 주입l
basePackages = "com.example.demo.member”,@ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.com.hellocom.hello.servicecom.hello.repositorycom.hello로 하여 최상단에 두는 것이다.@Componenet 뿐만 아니라 아래 내용도 추가 대상에 포함한다.@Component : 컴포넌트 스캔에서 사용@Controller : 스프링 MVC 컨트롤러에서 사용@Service: 스프링 비즈니스 로직에서 사용@Repository : 스프링 데이터 접근 계층에서 사용@Configuration : 스프링 설정 정보에서 사용@Component
public @interface Controleer{
}
@Component
public @interface Service{
}
@Component
public @interface Configuration{
}
@Controller : 스프링 MVC 컨트롤러로 인식@Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.@Configuration : 앞서 보았듯이 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처@Service : 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나라고 비즈니스 계층을 인식하는데 도움이 된다.includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.MyExcludeComponent(직접 만드는 애노테이션)
package com.example.demo.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
MyIncludeComponent(직접 만드는 애노테이션)
package com.example.demo.scan.filter;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
BeanA
package com.example.demo.scan.filter;
@MyIncludeComponent
public class BeanA {
}
BeanB
package com.example.demo.scan.filter;
@MyExcludeComponent
public class BeanB {
}
ComponentFilterAppConfigTest
package com.example.demo.scan.filter;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
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.*;
import static org.springframework.context.annotation.ComponentScan.*;
public class ComponentFilterAppConfigTest {
@Test
void filterScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean("beanA", BeanA.class);
assertThat(beanA).isNotNull();
System.out.println("beanA = " + beanA);
// ac.getBean("beanB", BeanB.class);
// org.junit.jupiter.api.Assertions.assertThrows(
// NoSuchBeanDefinitionException.class,
// () -> ac.getBean("beanB", BeanB.class)
// );
//bean은 exclude를 했기 때문에
}
@Configuration
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig{}
}
@MyIncludeComponent애노테이션이 붙은 beanA는 추가됐고 @MyExcludeComponent애노테이션이 붙은 beanB는 추가되지 않았다.컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까?
두 가지 경우가 있다.
ConfilictingBeanDefinitionException 예외 발생AutoAppConfig 부분에 memoryMemberRepository라는 이름을 가진 빈을 생성하고
테스트를 돌려보자
AutoAppConfig에 추가한 모습
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
이 경우 수동 등록이 우선순위를 가진다.
다만, 나중에 방대한 양의 코드를 다룰 때 이런 문제가 발생하면 굉장히 복합적인 오류가 발생할 수 있어서
스프링이 자체적으로 오류를 발생시켜서 막는다.