스프링 컨테이너, 빈 로직

김파란·2024년 5월 14일

Spring

목록 보기
5/10

1. 스프링 컨테이너와 빈

	@Test
    @DisplayName("빈 설정 메타정보 확인")
    void findApplicationBean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
            System.out.println("beanDefinition = " + beanDefinition);
        }
        
    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다")
    void findBeanByParentTypeDuplicate(){
        DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
        Assertions.assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(DiscountPolicy.class));
    }

    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다")
    void findBeanByParentTypeBeanName(){
        DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
        DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
        assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
    }
    
    @Test
    @DisplayName("부모 타입으로 모두 조회하기")
    void findAllBeanByParentType(){
        Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + " value: " + beansOfType.get(key));
        }
    }
    
    @Test
    @DisplayName("특정 타입을 모두 조회하기")
    void findAllBeanByType(){
        Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
        for (String s : beansOfType.keySet()) {
            System.out.println("key = " + s + " value: " + beansOfType.get(s));
        }
    }
    @Test
    @DisplayName("조회한 빈이 모두 필요할 때")
    void findAllBean(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class);

        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, "userA", Grade.VIP);
        int discountPolicy = discountService.discount(member, 10000, "fixDiscountPolicy");
    }

    static class DiscountService{
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;

        public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies;
        }

        public int discount(Member member, int price, String discountCode){
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            return discountPolicy.discount(member, price);


        }

    }

2). ComponentScan

// 탐색위치와 스캔
@ComponentScan(
        basePackages = "hello.core.member",
        excludeFilters= @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
   // 자동주입
    @Service
	public class MemberServiceImpl implements MemberService{
    	private MemberRepository memberRepository;
    	@Autowired // 파라미터에 있는 MemberRepository라는 이름을 가진 빈을 컨테이너에서 찾아서 넣어준다
    	public MemberServiceImpl(MemberRepository memberRepository) {
        	this.memberRepository = memberRepository;
    }
// 자동주입 대상이 없을경우 메서드 자체가 호출이 안됨
@Autowired(required = false) 
public void setNoBean(Member member){ 
	setNoBean = member
// 없을 경우 null이 입력
@Autowired
public void setNoBean3(@Nullable<Member> member){ // 자동주입 대상이 없으면 Optional.empty입력
setNoBean3 = member

@Autowired
public void setNoBean3(Optional<Member> member){ // 자동주입 대상이 없으면 Optional.empty입력
setNoBean3 = member
}

3) 여러빈이 있을 경우

// 애노테이션 직접 만들기
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
public class OrderServiceImpl implements OrderService{
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    
  // 타입 매칭을 시도하고, 여러빈이 있을 경우 필드 이름, 파라미터 이름으로 추가매칭
  @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy fixDiscountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    
// @Primary -> 일단 얘가 우선순위를 가진다
@Component
@Primary
public class FixDiscountPolicy implements DiscountPolicy

// @Qualifier  이름을 변경하는게 아니라 추가구분을 할 수 있게 해주는것
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy 

@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy

@Component
public class OrderServiceImpl implements OrderService{
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;
    public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("fixDiscountPolicy") DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

4. 빈 생명주기 콜백

  • 스프링 빈 라이프 사이클: 객체 생성 -> 의존관계 주입
  • 스프링 빈의 이벤트 라이프사이클: 스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 사용전 콜백 -> 스프링 종료
  • 3가지 방법으로 빈 생명주기 콜백 지원
// 방법1.
public class NetworkClient implements InitializingBean, DisposableBean
// 방법2.
@Configuration
    static class LifeCycleConfig{
        @Bean(initMethod = "init", destroyMethod = "close")
        public NetworkClient networkClient(){
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
    }
    public void init(){
        System.out.println("init");
    }
    public void close(){
        System.out.println("NetworkClient.close");
    }// 방법3.
    @PostConstruct
    public void connect(){
        System.out.println("connect: " + url);
    }

    
    @PreDestroy
    public void disconnect(){
        System.out.println("close " + url);

    }

5. 빈 스코프

  • 싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
  • 프로토타입: 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
  • 웹 관련 스코프
  1. request: 웹 요청이 들어오고 나갈때까지 유지되는 스코프
  2. session: 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
  3. application: 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

1). 싱글톤, 프로토타입

// singleton에서 Provider를 활용해서 DL로 prototype을 쓰는 방법
 @Test
    void singletonClientUsePrototype(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);

        ClientBean bean = ac.getBean(ClientBean.class);
        int count1 = bean.logic();
        assertThat(count1).isEqualTo(1);



    }

    @Scope("singleton")
    static class ClientBean{

        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;

        public int logic(){
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }

    @Scope("prototype")
    static class PrototypeBean {
        private int count = 0;

        public void addCount() {
            count++;
        }

        public int getCount() {
            return count;
        }

        @PostConstruct
        public void init() {
            System.out.println("PrototypeBean.init" + this);
        }

        @PreDestroy
        public void destroy() {
            System.out.println("PrototypeBean.destroy");
        }
    }

2. 웹 스코프

  • 스프링이 해당 스코프의 종료시점까지 관리한다. 따라서 종료 메서드가 호출
  • 종류: request, session, application, websocket
  • requestURL은 빈이 생성되는 시점에는 알 수 없으므로 setter로 입력받는다
  • Scope가 고객이 들어와서 나갈때까지 인데 그냥 스프링 컨테이너가 띄워질때 주입이 될수없다
    스프링에게 빈을 달라고 하는 시점은 스프링 애플리케이션 실행할때가 아니라 고객의 요청이 올때
    이럴때 Provider를 쓴다
@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final ObjectProvider<MyLogger> myLoggerProvider;

    @RequestMapping("/log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestURL(requestURL);

        myLogger.log("controller Test");
        logDemoService.logic("testId");
        return "ok";
    }
}

// 이것도 귀찮다 프록시
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Data
public class MyLogger {
    private String uuid;
    private String requestURL;

    public void log(String message) {
        System.out.println("["+uuid+"] " + " ["+ requestURL+"] "+message);
    }

    @PostConstruct
    public void init() {
        uuid = UUID.randomUUID().toString();
        System.out.println("["+uuid+"] request scope bean create: " + this);
    }

    @PreDestroy
    public void close(){
        System.out.println("["+uuid+"] request scope bean close: " + this);
    }
}

0개의 댓글