김영한 강사님의 스프링 핵심 원리 - 기본편을 듣고 정리한 내용입니다. 자세한 내용은 강의를 참고해주세요
스프링 컨테이너
스프링 컨테이너: 스프링에서 자바 객체들을 관리하는 공간을 말한다
- 자바 객체를 스프링에선
빈(Bean)이라고 하는데,
스프링 컨테이너에서는 이 빈의 생성부터 소멸까지를 개발자 대신 관리해주는 곳이라고 할 수 있다

- 위의 그림을 보고 스프링 컨테이너를 이해해 보자
- 스프링 컨테이너는 크게 2가지가 있다
- BeanFactory
- ApplicationContext
- 그림에서 보듯이 ApplicationContext는 BeanFactory를 구현하고 있다
- BeanFactory의 기능을 포괄하면서 추가적인 기능을 제공하기 때문에 대부분의 경우에는
ApplicationContext를 사용한다
- 추가적으로 우리는 java에서 AnnotationConfigApplicationContext를 이용해서 진행한다
- 그리고
ApplicationContext는 여러 다른 인터페이스도 구현을 하는데, 이벤트 발행 및 구독 모델 지원 및 리소스 조회 편의성 제공, 환경변수를 구분해주는 기능 및 언어 국제화를 지원한다 (이 기능들이 아까 말한 추가적인 기능이다)
그런데 생각을 해보면, 굳이 왜 스프링 컨테이너를 이용해서, 빈(Bean: 자바에서 객체)을 관리할까?
굳이 프레임워크에 의존하지 말고, 순수 자바 코드로 짜면 안될까??
이 답에 대해서 우리는 웹 어플리케이션을 만들고 있다라고 답을 하고 싶다.
- 웹이란 여러 사용자들이 동시에 접속하고 일이 처리되는 공간이다
- 즉 멀티쓰레드와, 싱글톤 같은 객체의 의존 즉 참조값을 관리하는게 매우 중요한데
- Spring 컨테이너는 이를 쉽고, 정확하게 하는 기능이 매우 좋기 때문이다.
- 그래서 우리는 java진형에서 웹 개발에서는 Spring을 사용한다
- 다음은 Spring을 사용하는 이유를 정리한 글이다.
스프링 컨테이너에 객체, 빈을 등록하는 이유는 스프링이 각 객체간 의존관계를 관리하도록 하는데에 큰 목적이 있습니다.
객체가 의존관계를 등록할 때는 스프링 컨테이너에서 해당하는 빈을 찾고, 그 빈과 의존성을 만듭니다.
- 이제 그러면 스프링 컨테이너에서 어떻게 스프링 빈을 등록해서 관리하고 의존관계를 주입하는 지 알아보자!
- 여기에서도 2가지 방법이 있다.
- 수동 등록
- 자동 등록
- 당연히 수동(자세한거)이 자동과 겹치면 우선순위를 갖는다
- 하나하나 알아보자!
수동 컨테이너 생성 및 빈 등록
- 아까 그림에서 봤듯이 우리는
ApplicationContext을 사용하고
- 그 구현체로는 java진형에서
AnnotationConfigApplicationContext을 사용한다
ApplicationContext 는 스프링 컨테이너이자 인터페이스이다.
AnnotationConfigApplicationContext 는 인터페이스를 구현한 구현체이다.
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
- 다음과 같이 스프링 컨테이너를 생성한다
- 파라미터 정보로, 자바의 구상 정보를 지정해줘야 한다.
- 여기선 AnnotationConfigApplicationContext를 사용했으므로 .class형태의 구상 정보를 등록해줘야 한다.
- 수동 등록이란 의미가, 자동 어노테이션을 사용하지 않고, Config.class파일을 이용해 내가 의존관계를 컨테이너에 직접 주입하는 의미이다.
등록 과정

- new AnnotationConfigApplicationContext(AppConfig.class); 로 만든다
- 스프링 컨테이너를 생성할 때 구성 정보를 지정해줘야하는데, 자바 설정 클래스에는 AppConfig.class를 넣는다.

- AppConfig에
@Configration으로 알려주고, @Bean으로 스프링 빈으로 등록하는 것을 알려준다
- AppConfig의
@Bean을 기준으로, 메서드 이름을 스프링 빈 저장소의 빈 이름으로, 반환값의 new한 인스턴스 객체를 빈 객체에 다 등록한다.
- 이때 빈 이름은 항상 다른 이름이어야 한다.


- 스프링 컨테이너에
@Bean 빈을 등록한다
- 동적인 객체 인스턴스 의존관계를 스프링에 연결해준다.
- 이렇게 되면 객체의 참조값(래퍼런스)들끼리 다 연결이 된다
- 음 그런데 코드를 보면, 아무리 스프링 빈에 등록을 해주긴 하는데
memberRepository같은 경우에는 3번이나 new를 통해 새로운 객체를 생성하는데...???
- 음 그런데 어떻게 같은
memberRepository의 참조값을 가질 수 있지???
- 3번 new로 생성한건데,,, 모두 다른 참조값을 가져야 되잖아!
@Configuration이 해결해준다!!!
- 우리는 repository의 같이 스프링 빈이, 모두 싱글톤을 유지해야지 같은 웹 어플리케이션에서 사용할 수 있다
@Configuration이 싱글톤을 보장해준다!!!

- 이렇게 되야 새로운 Service객체를 계속 생성하지 않고, 하나로 여러 쓰레드(클라이언트롤) 처리할 수 있다
- 만약 요청마다 계속 스프링 빈을 설정한다면, 리소스가 버티지 못하고 서버가 터질 것이다.
- 싱글톤 객체로 생성하고 재사용하자!
- 자세히 일아보자
@Configuration과 바이트 코드 조작
- 우리가 AnnotationConfigApplicationContext안에 넣는 파라미터 AppConfig.class 이것 또한 스프링 빈으로 등록된다
- 즉 AppConfig 또한 스프링 빈으로 등록되는 것이다
- AppConfig빈의 클래스 정보를 봐보자(bean.getClass())

- 어라? 저 CGLIB$$는 뭐지???
- 이는 내가 만든 클래스가 아니라 스프링이
CGLIB이라는 바이트코드 조작 라이브러리를 사용해서
- AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 다형성을 이용해 스프링 빈으로 대신 등록해놓은 것이다

- 이 상속받은 바이트 조작 클래스가 싱글톤을 보장해주는데...

- 이런 로직을 갖을 것이다...
- 즉 처음에 bean으로 등록을 하고, 또 설정정보에서 등록을 한다고 하면, 스프링 컨테이너에서 찾고
- 있으면 등록하지 않고 반환을 해준다.
- 즉 같은 참조값을 갖은 스프링 빈들을 이용해서, 의존관계를 설정할 수 있다는 것이다!!!
잘 이해가 되지 않는다면,
- 수동 설정에서
@Configuration을 사용하면, 스프링 컨테이너 내의 스프링 빈들의 싱글톤을 보장할 수 있다는 것이다.
- 그리고
@Configuration는 CGLIB이라는 스프링의 바이트 코드 기술을 사용한다
먄약 @Configuration을 때고, 수동으로 @Bean만 등록을 한다면???
당연히 CGLIB기술 없이 스프링 빈에 등록될 것이고, 아까 3번 객체를 만든 것 처럼
모두 다른 참조값을 가진 인스턴스(스프링 빈)가 생성될 것이다.
-> 즉 싱글톤을 보장하지 않는다 !!!
ComponentScan을 이용한 자동 빈 등록
- 이제 수동으로 빈을 등록하는 과정을 알아보자
- 컴포넌트 스캔은 말그대로
@Component 에노테이션이 붙은 객체를 찾아서 스프링 빈으로 등록해주는 것 이다.
- 그리고
@Configuration 에노테이션 안에는 @Component에노테이션이 붙어있기에, @ConponentScan의 대상이 된다

- 빈 등록을 하고 의존관계는
@AutoWired 어노테이션을 이용해서 의존관계를 자동으로 등록한다
등록 과정

@ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다
- 이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.
- 빈 이름 기본 전략: MemberServiceImpl 클래스 memberServiceImpl 빈 이름
- 직접 지정:
만약 스프링 빈의 이름을 직접 지정하고 싶으면 @Component("memberService2") 이런식으로 이름을 부여하면 된다

- 생성자에
@Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다
- Bean 객체를 등록하기 위해 생성자를 호출하는데,
- 그래서 생성자에 @AutoWired를 붙인다.
- 생성자를 실행하면, 인자로 받는 객체를 스프링 컨테이너에서 찾는다.
- 찾을 때, 같은 타입(자료형)이 있는지를 기준으로 찾는다
- 이런 방식으로 의존관계를 주입한다(싱글톤을 만족시키면서)
- 같은 타입이 여러개 있으면??? -> 오류가 난다

탐색할 패키지의 시작 위치 지정
basePackages : 이 패키지를 포함한 하위 패키지에서 ComponentScan을 찾아 들어간다.
- 모든 자바 코드를 뒤지기 때문에, 시간이 오래걸려서 한다!
- 지정하지 않으면,
@ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다
- 패키지 위치를 지정하지 않고, 설정정보 클래스의 위치를 프로젝트의 최상단에 둔다!!!(스프링 부트도 그런다)
- 프로젝트 메인 설정 정보(AppConfig)는 프로젝트를 대표하는 정보이기 때문에, 프로젝트 맨 상단에 두는게 좋다!->까보기 좋음
- 스프링 부트를 사용하면, 스프링 부트의 대표 시작 정보인
@SpringBootApplication을 이 프로젝트 시작 루트 위치에 도는 것이 관례 거기안에 @ComponentScan이 들어있다.
- 즉, 스프링 부트를 쓰면,
@ComponentScan을 할 필요가 없다

컴포넌트 스캔 추가 대상
@Component : 컴포넌트 스캔에서 사용
@Controller : 스프링 MVC 컨트롤러에서 사용
@Service : 스프링 비즈니스 로직에서 사용
@Repository : 스프링 데이터 접근 계층에서 사용
@Configuration : 스프링 설정 정보에서 사용
다음 어노테이션들은 @Component를 포함하고 있다!
중복 등록과 충돌
자동 빈 등록 vs 자동 빈 등록
- 자동으로 등록되는 스프링 빈의 이름이 같은 때
-> ConflictingBeanDefinitionException(Component("service"), Component("service"))이런 상황...
- 거의 없다..
동 빈 등록 VS 자동 빈 등록
Overriding bean definition 뭔말이냐..
- 수동 등록 빈이 우선권을 갖는다!!!-> 수동빈이 자동빈을 오버라이드 해버린다
- 그런데 여러 설정들이 꼬여서 이런 결과가 만들어진다...
- 정말 잡기 어려운 버그가 만들어진다. 항상 잡기 어려운 버그는 애매한 버그다...
- 그래서 스프링 부트는 수동빈과 자동빈이 충돌하면 자동으로 오류가 나버린다
- 스프링 부트에서 실행하면 바로 오류가 난다!!!
- The bean 'memoryMemberRepository', defined in class path resource [hello/core/AutoAppConfig.class], could not be registered. A bean with that name has already been defined in file [C:\Spring\core\out\production\classes\hello\core\member\MemoryMemberRepository.class] and overriding is disabled.
- 스프링 부트가 이런 오버라이딩을 꺼버렸다.
자동,수동 등록 뭐를 선택해야돼??
- 설정 정보를 기반으로 애플리케이션을 구성하는 부분과 실제 동작하는 부분을 명확하게 나누는 것이 이상적이지만,
- 개발자 입장에서 스프링 빈을 하나 등록할 때 @Component 만 넣어주면 끝나는 일을 @Configuration 설정 정보에 가서 @Bean 을 적고, 객체를 생성하고, 주입할 대상을 일일이 적어주는 과정은 상당히 번거롭다.
- 또 관리할 빈이 많아서 설정 정보가 커지면 설정 정보를 관리하는 것 자체가 부담이 된다.
- 그리고 결정적으로 자동 빈 등록을 사용해도 OCP, DIP를 지킬 수 있다
그러면 수동 빈 등록은 언제 사용하면 좋을까?
- 애플리케이션은 크게 업무 로직과 기술 지원 로직으로 나눌 수 있다.
- 업무 로직 빈: 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리등이 모두 업무 로직이다. 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.
- 기술 지원 빈: 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들이다
- 업무 로직은 숫자도 매우 많고, 한번 개발해야 하면 컨트롤러, 서비스, 리포지토리 처럼 어느정도 유사한 패턴이있다.
- 이런 경우 자동 기능을 적극 사용하는 것이 좋다. 보통 문제가 발생해도 어떤 곳에서 문제가 발생했는지 명확하게 파악하기 쉽다.
- 기술 지원 로직은 업무 로직과 비교해서 그 수가 매우 적고, 보통 애플리케이션 전반에 걸쳐서 광범위하게 영향을 미친다.
- 그리고 업무 로직은 문제가 발생했을 때 어디가 문제인지 명확하게 잘 드러나지만, 기술 지원 로직은 적용이 잘 되고 있는지 아닌지 조차 파악하기 어려운 경우가 많다.
- 그래서 이런 기술 지원 로직들은 가급적 수동 빈 등록을 사용해서 명확하게 드러내는 것이 좋다
애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 딱! 설정 정보에 바로 나타나게 하는것이 유지보수 하기 좋다
참고로 스프링과 스프링 부트가 자동으로 등록하는 수 많은 빈들은 예외다. 이런 부분들은 스프링 자체를 잘 이해하고스프링의 의도대로 잘 사용하는게 중요하다.
스프링 부트의 경우 DataSource 같은 데이터베이스 연결에 사용하는 기술 지원 로직까지 내부에서 자동으로 등록하는데, 이런 부분은 메뉴얼을 잘 참고해서 스프링 부트가 의도한 대로 편리하게 사용하면 된다.
반면에 스프링 부트가 아니라 내가 직접 기술 지원 객체를 스프링 빈으로 등록한다면 수동으로 등록해서 명확하게 드러내는 것이 좋다.
예를 들면
- DiscountService 가 의존관계 자동 주입으로 Map<String, DiscountPolicy> 에 주입을 받는 상황을 생각해보자.
- 같은 타입이므로 우리는 이를 Map에 저장하였는데,,,
- 여기에 어떤 빈들이 주입될 지, 각 빈들의 이름은 무엇일지 코드만 보고 한번에 쉽게 파악할 수 있을까?
- 다른 사람이 보기엔 애매할 것이다
- 이럴때에는 수동 빈으로 등록하거나 또는 자동으로하면 특정 패키지에 같이 묶어두는게 좋다!
- 핵심은 딱 보고 이해가 되어야 한다는 것이다

- 이렇게 같은 타입의 2가지 빈을 등록한
DiscountPolicyConfig를 보면 사람들은
아 할인 정책에 관련된 수동 빈 등록이구나?라고 알 수 있고
- 정률,정액 2가지 방법의 빈이 등록되는구나! 라고 알 수 있다
이렇게 여러개의 Config파일을 만들고 나중에 수동으로 등록할때 여러개를 등록하면 된다
new AnnotationConfigApplicationContext
(AutoAppConfig.class, DiscountPolicyConfig.class)
- 등록하면 아 앱설정과, 할인 정보를 수동으로 빈으로 등록해줬구나!를 알수 있고
- 각각의 Config에 들어가서 보면, 어떤 할인정책들과 설정정보가 있는지를 알 수 있다
- 이렇게 다른 개발자가 딱 보고 이해가 되게 설정 파일들을 수동으로 설정해줘야 한다!!!
- 아마 필터, 인터셉터, AOP이런 것들도 잘 나눠서 수동으로 빈 등록을 해줘야 할 것 같다!