관리 메뉴

여름 언덕에서 배운 것

[스프링기본편1]스프링 컨테이너와 스프링 빈 본문

가랑비에 옷 젖는 줄 모른다 💻/스프링

[스프링기본편1]스프링 컨테이너와 스프링 빈

잔뜩 2023. 10. 11. 23:20

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

www.inflearn.com

스프링 빈 조회 -기본

package hello.core.beanfine;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

public class ApplicationContextBasicFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    @Test
    @DisplayName("빈 이름 조회")
    void findBeanByName(){
        MemberService memberService = ac.getBean("memberService", MemberService.class);
       // System.out.println("memberService = " + memberService);
        //System.out.println("memberService = " + memberService.getClass());

        //멤버 서비스가 멤버서비스Impl의 인스턴스면 성공!
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);

    }
    @Test
    @DisplayName("이름 없이 타입으로만 조회")
    void findBeanByType(){
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);

    }
    @Test
    @DisplayName("구체 타입으로 조회")
    void findBeanByName2(){
        MemberService memberService = ac.getBean(MemberServiceImpl.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);

    }
    @Test
    @DisplayName("빈 이름으로 조회가 X ")
    void findBeanByNameX(){
        //ac.getBean("zz", MemberService.class);  멤버 서비스(?) 에 없다.
        MemberService xxx =  ac.getBean("zz", MemberService.class);
        assertThrows(NoSuchBeanDefinitionException.class,
                ()->ac.getBean("zz", MemberService.class));

        //화살표 오른쪽에 있는 코드가 실행이 되면 왼쪽에 있는 예외가 터져야 한다는 뜻

    }
}

스프링 빈 조회 - 동일한 타입이 둘 이상

타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정하자.

 

package hello.core.beanfine;

import hello.core.AppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

public class ApplicationContextSameBeanFindTest {
    //이걸 사용하면 AppConfig 손봐야 하니까 , 여기서만 사용할 class static으로 선언하기
   // AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상있으면 , 중복오류가 발생한다")
    void findBeanByTypeDuplicate(){
        MemberRepository bean = ac.getBean(MemberRepository.class);
    }

    @Configuration // Java 구성 클래스를 선언할 때 사용
    static class SameBeanConfig{ //클래스 안에서 클래스를 썼다는 것은 여기 안에서만 scope을 사용하겠단 뜻입니다.

        @Bean
        public MemberRepository memberRepository1(){
            return new MemoryMemberRepository();
        }
        @Bean
        public MemberRepository memberRepository2(){
            return new MemoryMemberRepository();
        }

    }
}

CTRL SHIFT ENTER  코드로 넘어가게 해주는 단축키

CTRL ALT V  변수 지정명(?) 해주는 단축키

package hello.core.beanfine;

import hello.core.AppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ApplicationContextSameBeanFindTest {
    //이걸 사용하면 AppConfig 손봐야 하니까 , 여기서만 사용할 class static으로 선언하기
   // AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상있으면 , 중복오류가 발생한다")
    void findBeanByTypeDuplicate(){
       // MemberRepository bean = ac.getBean(MemberRepository.class);
        assertThrows(NoUniqueBeanDefinitionException.class,
                () -> ac.getBean(MemberRepository.class));
    }

    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상있으면,빈 이름을 지정하면 된다")
    void findBeanByName(){
        //빈 이름을 지정하여 조회, 지정된 이름을 사용하여 repository를 가져오면서 이 빈이 클래스의 인스턴스인지 확인
        MemberRepository repository = ac.getBean("memberRepository1", MemberRepository.class);
        assertThat(repository).isInstanceOf(MemberRepository.class);

    }

    @Test
    @DisplayName("특정타입을 모두 조회하기")
    void findAllBeansByType() {
        //MAP 키, 밸류 값
        Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
        /여기서 String은 빈의 이름(빈 ID)이 되고, MemberRepository는 빈의 인스턴스
        for(String key : beansOfType.keySet()){
            System.out.println("key = " + key + "value = " + beansOfType.get(key));
        }
        System.out.println("beansOfType = " + beansOfType);
        assertThat(beansOfType.size()).isEqualTo(2);
    }

    @Configuration // Java 구성 클래스를 선언할 때 사용
    static class SameBeanConfig { //클래스 안에서 클래스를 썼다는 것은 여기 안에서만 scope을 사용하겠단 뜻입니다.

        @Bean
        public MemberRepository memberRepository1() {
            return new MemoryMemberRepository();
        }

        @Bean
        public MemberRepository memberRepository2() {
            return new MemoryMemberRepository();
        }

    }
}

`keySet()`은 자바에서 `Map` 인터페이스를 구현한 컬렉션에서 사용되는 메서드 중 하나입니다. 

`keySet()` 메서드는 해당 `Map` 객체에서 모든 키(key)를 가져와서 그 키들을 포함한 `Set` 컬렉션을 반환합니다.

여기서 각 용어에 대한 설명은 다음과 같습니다:

- **Map**: `Map`은 키(key)와 값(value)을 쌍으로 저장하는 자료구조입니다. 키를 사용하여 값을 검색할 수 있습니다. 

예를 들어, 전화번호부의 이름과 전화번호가 저장된다면, 이름이 키이고 전화번호가 값입니다.
- **keySet()**: `keySet()` 메서드는 `Map`에서 모든 키(key)를 추출하여 이를 `Set` 컬렉션으로 반환합니다. 

`Set`은 중복된 요소를 허용하지 않는 자료구조로, 각 키는 중복되지 않고 유일합니다.

예를 들어, 다음은 `Map` 객체에서 `keySet()`을 사용하는 간단한 예제입니다:

Map<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 30);
ageMap.put("Bob", 25);
ageMap.put("Charlie", 35);

Set<String> names = ageMap.keySet();
System.out.println(names); // 출력: [Alice, Bob, Charlie]


위 코드에서 `ageMap.keySet()`을 호출하면 `ageMap`에서 모든 키를 추출하여 `Set`으로 반환합니다.

따라서 `names` 변수에는 모든 이름이 중복 없이 저장되어 있습니다.

이렇게 추출된 키를 사용하여 `Map`에서 값을 검색하거나 수정할 수 있습니다.
`keySet()`은 많은 경우에 `Map`의 키를 반복적으로 접근할 때 유용하며,

 반복문을 통해 `Map`의 키를 순회하거나 특정 키를 검색하는 작업에 자주 활용됩니다.

 

스프링 빈 조회 - 상속 관계

부모 타입으로 조회하면, 자식 타입도 함께 조회한다.
그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.

package hello.core.beanfine;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ApplicationContextExtendsFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

    @Test
    @DisplayName("부모타입으로 조회시 자식이 둘 이상 있으면 중복오류가 발생한다")
    void findBeanByParentTypeDuplicate(){
        assertThrows(NoUniqueBeanDefinitionException.class,
                () -> ac.getBean(DiscountPolicy.class));
    }
    @Test
    @DisplayName("부모타입으로 조회시 자식이 둘 이상 있으면 빈 이름을 지정한다.")
    void findBeanByParentTypeBeanName(){
        DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
        //ac.getBean("rateDiscountPolicy", DiscountPolicy.class)은 스프링 컨테이너에서 이름이 "rateDiscountPolicy"인 빈을 찾아서 반환합니다.
        assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
    }
    @Test
    @DisplayName("특정 하위타입으로 조회") // 좋은 방법은 아니다.
    void findBeanBySubType(){
       RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }
    @Test
    @DisplayName("부모타입으로 모두 조회하기")
    void findAllBeansByParentType(){
        Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
        assertThat(beansOfType.size()).isEqualTo(2);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + "value = " + beansOfType.get(key));
        }

    }
    @Test
    @DisplayName("부모타입으로 모두 조회하기 - Object")
    void findAllBeansByObjectType(){
        Map<String,Object> beansOfType = ac.getBeansOfType(Object.class);
        
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + "value = " + beansOfType.get(key));
        }

    }



    @Configuration
    static class TestConfig{
        @Bean
        public DiscountPolicy rateDiscountPolicy(){
            return new RateDiscountPolicy();
        }
//
//      @Bean  위의 처럼 하는 이뉴는 역할과 구현의 구분을 위해서! 할인 정책이구나를 쉽게 알려고 하는거다.
//        public RateDiscountPolicy rateDiscountPolicy(){
//            return new RateDiscountPolicy();
//        }
        @Bean
        public DiscountPolicy fixDiscountPolicy(){
            return new FixDiscountPolicy();
        }

    }//TestConfig
} // 클래스

Object 타입으로 조회하면 자바 객체는 모든게 object 타입이라서 스프링 빈에 등록되어 있는 모든 객체가 튀어 나온다.

빈 조회기능은 기본 기능이기도 해서 거의 쓸 일은 없다.

부모타입으로 조회할 때 자식이 어디 까지 조회되는지 알기 위해서~

 

BeanFactory와 ApplicationContext

 

beanfactory는 스프링컨테이너의 최상위 인터페이스

ApplicationContext는 beanFactory에서 부가 기능을 더한 것

 

1.메시지소스를 활용한 국제화 기능
예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
2.환경변수
로컬, 개발, 운영(실제 production에 나가는 것)등을 구분해서 처리
3.애플리케이션 이벤트
이벤트를 발행하고 구독하는 모델을 편리하게 지원
4.편리한 리소스 조회
파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

 

정리
ApplicationContext는 BeanFactory의 기능을 상속받는다.
ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공한다.

BeanFactory를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext를 사용한다.
BeanFactory나 ApplicationContext를 스프링 컨테이너라 한다.

 

다양한 설정 형식 지원 - 자바 코드, XML (가볍게)

스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계되어 있다.
자바 코드, XML, Groovy 등등

애노테이션 기반 자바 코드 설정 사용
지금까지 했던 것이다.
new AnnotationConfigApplicationContext(AppConfig.class)
AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다.


XML(문서) 설정 사용
최근에는 스프링 부트를 많이 사용하면서 XML기반의 설정은 잘 사용하지 않는다. 

아직 많은 레거시 프로젝트 들이 XML로 되어 있고, 또 XML을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있는 장점도 있으므로 한번쯤 배워두는 것도 괜찮다.
GenericXmlApplicationContext 를 사용하면서 xml 설정 파일을 넘기면 된다.

 

빈 등록하는 과정!

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://
www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="memberService" class="hello.core.member.MemberServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository" />
        //생성자
    </bean>
    <bean id="memberRepository"
          class="hello.core.member.MemoryMemberRepository" />
    <bean id="orderService" class="hello.core.order.OrderServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository" />
        <constructor-arg name="discountPolicy" ref="discountPolicy" />
    </bean>
    <bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy" />


</beans>

스프링 빈 설정 메타 정보 - BeanDefinition

스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까? 그 중심에는 BeanDefinition 이라는 추상화가 있다.
쉽게 이야기해서 역할과 구현을 개념적으로 나눈 것이다!


XML을 읽어서 BeanDefinition을 만들면 된다.
자바 코드를 읽어서 BeanDefinition을 만들면 된다.
스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.

 

BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 도 있다. 하지만 실무에서 BeanDefinition을
직접 정의하거나 사용할 일은 거의 없다. 어려우면 그냥 넘어가면 된다^^!
BeanDefinition에 대해서는 너무 깊이있게 이해하기 보다는, 스프링이 다양한 형태의 설정 정보를
BeanDefinition으로 추상화해서 사용하는 것 정도만 이해하면 된다. 

728x90