3 minute read

?

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 등록 방법

  1. @Configuration 을 클래스에 붙인다.
    1. @Configuration 이 붙지않으면 스프링 컨테이너가 bean들을 전부 싱글톤 방식이 아닌 계속 인스턴스생성을하여 메모리 낭비가 발생할수 있다.
     // without annotation
     hello.core.AppConfig@4c4748bf
        
     // with annotation
     // 스프링이 객체생성 낭비를 하지 않도록 특별한 처리를 한다.
     hello.core.AppConfig$$EnhancerBySpringCGLIB$$df5a8890@470a696f
    
  2. @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들을 만드는것을 지원한다. AnnotationConfigApplicationContextApplicationContext 를, ApplicationContextBeanFactory 를 상속하고 있다. 따라서 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