Spring bean
?
Spring IOC 컨테이너 에서 관리하는 오브젝트를 bean 이라 한다.
Bean관점에서의 IOC (제어역전)
인터페이스에 대한 구현체의 선택을 IOC에 위임한다.
before
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemberRepositoryImpl();
private final DiscountPolicy discountPolicy = new DiscountPolicyImpl();
}
After
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
AnnotationConfigApplicationContext를 통한 수동 Bean 등록 방법
- @Configuration 을 클래스에 붙인다.
- @Configuration 이 붙지않으면 스프링 컨테이너가 bean들을 전부 싱글톤 방식이 아닌 계속 인스턴스생성을하여 메모리 낭비가 발생할수 있다.
// without annotation hello.core.AppConfig@4c4748bf // with annotation // 스프링이 객체생성 낭비를 하지 않도록 특별한 처리를 한다. hello.core.AppConfig$$EnhancerBySpringCGLIB$$df5a8890@470a696f
- @Bean 어노테이션을 메소드에 붙인다. 메소드는 인터페이스에 대한 구현체를 반환하도록 설계한다. 이때 bean들의 이름은 default로 메소드의 이름이다. 이후
AnnotationConfigApplicationContext
생성시 같이 등록한다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
ApplicationContext
어플레케이션의 configuration을 제공하는 중앙 포인트라고 보면 될것 같다.
- 어플리케이션을 위한 BeanFactory 기능제공
- 파일 리소스 로딩기능
- 이벤트 publisher
- 국제화 기능, event resolver
- WAS 상의 각각의 DispatcherServlet이 독립적인 ApplicationContext 구현체를 가질수 있다.
AnnotationConfigApplicationContext
ApplicationContext
의 구현체 중 하나다. @Configuration 어노테이션이 붙은 클래스를 통해 bean들을 만드는것을 지원한다.
AnnotationConfigApplicationContext
는 ApplicationContext
를, ApplicationContext
는 BeanFactory
를 상속하고 있다. 따라서 getBean
메소드를 통해 빈을 인터페이스, 구현체, 이름 등으로 읽어올수 있다.
타입으로 조회시 같은 타입이 둘이상 있으면 중복 오류가 발생한다. 이때는 빈의 이름으로 조회하거나 getBeansOfType
메소드를 사용한다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
public class AnnotationConfigApplicationContextTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
MemberService memberService = ac.getBean("memberService", MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
@Test
@DisplayName("이름 없이 타입으로 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구현체 타입으로 조회")
void findBeanByDetailType() {
MemberServiceImpl memberService = ac.getBean(MemberServiceImpl.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
ComponentScan
@ComponentScan 어노테이션을 통해 @Configuration, @Component, @Service, @Repository 이 붙은 클래스를 자동으로 Bean으로 등록한다.
/**
* 해당 클래스가 속한 패키지와 하위 패키지를 기본으로 탐색한다.
*/
@Configuration
@EnableWebMvc
@ComponentScan
public class WebConfiguration { }
/**
* 특정 클래스를 제외시키거나 추가하는것도 가능하다.
*/
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
/**
* base package를 설정하는것도 가능하다.
*/
@Configuration
@ComponentScan(value="com.remember")
public class AppConfig {}
의존성 주입
생성자 주입
1회만 호출을 보장시킬수 있다. (final 키워드) 불변, 필수 의존관계에 많이 사용한다고 한다. /**
-
생성자가 하나면 @Autowired 생략 가능 */ @Service @RequiredArgsConstructor public class PracticeStatususService { private final PracticeStatusAssembler assembler;
public CollectionModel
findAll() { return assembler.toCollectionModel(PracticeStatus.findAll()); } }
setter 주입
모든 빈 등록후 setter가 실행된다. 선택, 변경 가능성이 있을때 많이 사용한다고 한다.
@Service
@RequiredArgsConstructor
public class PracticeStatususService {
private PracticeStatusAssembler assembler;
@Autowired
public void setAssembler(PracticeStatusAssembler assembler) {
this.assembler = assembler;
}
public CollectionModel<PracticeStatusResponse> findAll() {
return assembler.toCollectionModel(PracticeStatus.findAll());
}
}
필드 주입
스프링의 서포트없이 테스트하기 힘든 단점이 있다. 스프링의 기술에 많이 의존하는 클래스에 사용하면 좋다. (ex: @Configuration이 붙은 클래스)
@Configuration
public class BeanConfig {
@PersistenceContext
private EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
메소드 주입
@Service
@RequiredArgsConstructor
public class PracticeStatususService {
private PracticeStatusAssembler assembler;
@Autowired
public void init(PracticeStatusAssembler assembler) {
this.assembler = assembler;
}
public CollectionModel<PracticeStatusResponse> findAll() {
return assembler.toCollectionModel(PracticeStatus.findAll());
}
}
같은 타입의 bean 이 여러개일때의 대응
구현체의 이름으로 필드이름을 정한다.
@Component
public class MyAssembler implements PracticeStatusAssembler {}
@Service
@RequiredArgsConstructor
public class PracticeStatususService {
private PracticeStatusAssembler myAssembler;
}
이렇게 하면 PracticeStatusAssembler 인터페이스의 구현체가 여러개이면 필드이름과 같은 이름을 가진 bean을 찾는다.
@Qualifier
@Component
@Qualifier("hohohoho")
public class MyAssembler implements PracticeStatusAssembler {}
@Service
public class PracticeStatususService {
@Autowired
@Qualifier("hohohoho")
private PracticeStatusAssembler myAssembler;
}
@Qualifier 로 bean에 별명을 부여할수 있다. @Autowired와 함께 명시하면된다.
@Primary
@Component
@Primary
public class FirstAssembler implements PracticeStatusAssembler {}
@Component
public class SecondAssembler implements PracticeStatusAssembler {}
@Service
public class PracticeStatususService {
@Autowired
private PracticeStatusAssembler myAssembler;
}
@Primary 어노테이션을 사용하여 우선순위를 높일수 있다. 위와 같은 경우 FirstAssembler 가 로딩되게 된다. @Primary 와 @Qualifier가 같이 사용되면 @Qualifier 의 우선순위가 높다.
정적변수와 스프링빈
- 정적변수: jvm class loader scope
- spring bean: applicationContext scope
Leave a comment