@Configuration 어노테이션을 붙여주고, 각 메서드에 @Bean 어노테이션을 붙여주면, 스프링 컨테이너에 빈으로 등록한다는 것이 된다. MemberServiceImpl이 자기 자신 안에서 getBean()을 호출해서 의존 객체를 능동적으로 검색한다.
문제: 이 클래스는 이제 '스프링이 있다'는 걸 알아야 하고, ApplicationContext라는 스프링 API에 직접 의존하게 된다.. → 순수한 비즈니스 로직이 프레임워크에 오염된다. 옛날 방식이다.
단순히 호출하는게 아니라, 의존관계를 검색을 하고, 자신이 필요로 하믄 의존 객체를 능동적으로 찾는다.
즉 의존관계를 맺을 객체 결정,생성은 외부 컨테이너에서 IoC로 처리하지만, 가져올 때는 스스로 컨테이너에게 요청을 한다. 이렇게 검색을 할 때 getBean() 메서드를 호출한다.
예전과 다르게 DI는, MemberServiceImpl은 getBean이 뭔지도 모르고, ApplicationContext라는게 뭔지 모른다.
그러면 getBean을 누가 호출해주냐면, @Autowired 필드나 서블릿 진입점 등이 이 역할을 대신해준다.
ApplicationContext를 스프링 컨테이너라고 한다. 모든 빈을 관리한다. @Configuration이 붙은 AppConfig를 설정 정보로 사용하고, Bean 어노테이션이 붙은 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이 객체를 스프링 빈이라고 한다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
}
-> 메서드명 memberService로 빈 등록이 된 것이다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = ac.getBean("memberService", MemberService.class);
이렇게하면 appConfig -> memberService 순으로 등록이 된다.
memberService() 안에서 memberRepository()를 직접 호출할하고 있는데, 객체가 호출 시마다 생성하는 게 아니라, 이미 등록된 빈이 있으면 반환하고 없으면 등록한다. 이게 어떻게 되는 걸까..?
@Configuration이 붙은 클래스는 스프링이 CGLIB 바이트코드 조작 라이브러리로 AppConfig를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한다.
이 프록시 클래스가 @Bean이 붙은 메서드마다 이미 스프링 컨테이너에 등록된 빈이 있으면 그 빈을 반환하고, 없으면 생성해서 등록 후에 반환하는 코드를 생성해준다.
그래서 싱글톤이 깨지지 않는 것이다.
@Configuration 없이 단독으로 사용한다면 @Component클래스에 Bean을 붙이는 식이 될 건데, 이렇게 하면 CGLB 프록시가 적용되지 않아서 메서드 호출 시마다 새로운 객체가 생성되고, 이 경우에 싱글톤이 깨진다.
CGLB 프록시는 중간에서 메서드 호출을 가로채는 프록시다. 그래서 적용되면 이미 있는 것을 반환하고 호출은 패스하도록 해주는 것이다.
근데 이 방법이 컴포넌트 스캔이랑은 어떻게 다른지? 궁금했다..
여기서 MemberService가 memberRepository를 생성자 파라미터로 가지고 있기 때문에 repository 메서드를 호출하는게 아니라, 컨테이너가 이미 만들어놓은 인스턴스를 생성자 파라미터로 꽂아준다.
즉 여기서는 프록시가 별도로 필요가 없다.
@Repository
public class MemoryMemberRepository implements MemberRepository { ... }
@Service
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) { // 호출X , 주입
this.memberRepository = memberRepository;
}
}
@Service
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
public OrderServiceImpl(MemberRepository memberRepository) { // 주입
this.memberRepository = memberRepository;
}
}
빈 또는 빈 오브젝트는 스프링이 IoC 방식으로 관리하는 오브젝트라는 뜻이다.
스프링을 사용하는 애플레이케이션에서 만들어지는 모든 오브젝트가 다 빈은 아니고, 스프링이 생성과 제어를 담당하는 오브젝트만을 빈이라고 한다.
ApplicationContext 빈 등록
@Bean 어노테이션을 달아준 memberService가 있다고 하자. 이걸 스프링 컨테이너의 빈 저장소에 이름,객체 이렇게 등록을 해준다. 빈 이름은 메서드명을 하용하지만, 직접 부여할 수도 있다.
빈은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면 다른 빈이 무시되거나 덮어쓰기 하거나 하는 설정에 따라 오류가 발생한다.
스프링 빈 의존관계 설정
memberService -> memberRepository
orderService -> discountPolicy
이런식으로 의존관계가 있을 것이다. 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입한다.
스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
빈 조회
빈 조회하기: ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
빈 이름으로 빈 객체 조회 : ac.getBean(), ac.getBean(빈이름,타입)
동일한 타입이 둘 이상이면 오류가 발생한다.
ac.getBeansOfType()을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.
스프링 빈 조회 - 상속 관계
ApplicationContext가 제공하는 부가 기능


public class XmlAppContext {
@Test
void xmlAppContext() {
ApplicationContext ac = new
GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService",
MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
BeanDefinition 이라는 추상화로 스프링은 다양한 설정 형식을 지원한다. 즉 BeanDefinition 자체가 인터페이스고 스프링 컨테이너는 추상화된 BeanDefinition에만 의존한다. BeanDefinition 을 빈 설정 메타정보라 한다.@Bean , <bean> 당 각각 하나씩 메타 정보가 생성된다.
AnnotationConfig안의 AnnotatedBeanDefinitionReader : AnnotationConfigApplicationContext 안에 들어가보면 있는 건데, 이걸 사용해서 AppConfig.class 내부의 설정 정보 등등이 있는 코드를 읽고 BeanDefinition이라는 빈 메타정보를 생성한다.
못하는 게 뭘까?