스프링 기본 원리

황규혁·2023년 7월 14일

Spring 원리

목록 보기
5/5

스프링 컨테이너와 빈

개념

  • 스프링 컨테이너는 스프링 프레임워크의 핵심 컴포넌트임.
  • 스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공함.
  • ApplicationContext(인터페이스)를 스프링 컨테이너라고 함.
  • AnnotationConfigApplicationContext가 구현체임.
  • 스프링에서는 자바 객체를 빈(Bean)이라 함.

  • 생성과정

    1. 스프링 컨테이너의 생성
      • 스프링 컨테이너 생성 시 구성 정보 지정해야 함. ex) AppConfig.class

    2. 스프링 빈 등록
      • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용하여 스프링 빈을 등록함.
      • 빈 이름은 메서드 이름을 사용.
      • 빈 이름 직접 부여 가능(보통 메서드 이름 그대로 사용)

    3. * 빈 이름은 중복되게 만들지 않아야 함!
    4. 스프링 빈 의존관계 설정
      • 스프링 컨테이너는 설정 정보를 참고하여 의존관계 주입(DI)
      • 스프링 컨테이너는 싱글톤 패턴 보장

    스프링 빈 조회

    1. 기본 조회 방법
      • ac.getBean(beanName, type)
      • ac.getBean(type)
      조회 대상 없으면 NoSuchBeanDefinitionException 발생

    2. 동일한 타입 둘 이상
      • ac.getBeansOfType()

    3. 상속 관계
      • 부모 타입으로 조회하면 자식 타입도 함께 조회
      • 따라서 Object 타입으로 조회하면, 모든 스프링 빈을 조회함

    싱글톤 컨테이너

    웹 애플리케이션과 싱글톤

    순수 자바 코드로 만든 DI컨테이너의 문제점
    : 요청을 할 때마다 객체를 새로 생성하여 메모리 낭비가 심해짐
    해결책1 : 해당 객체가 한 번만 생성되는 싱글톤 패턴을 활용하기
    1. static 영역에 객체 딱 1 개만 생성해둠.
      private static final Singleton instance = new Singleton();
    2. public으로 열어서 static 메서드를 통해서만 조회하도록 허용
      public staic Singleton getInstance() {
          	return instance;
          }
    3. 생성자를 private으로 선언해서 외부에서 객체 생성 할 수 없게 막음
      private Singleton(){}
    문제점
    • 싱글톤 패턴 구현 코드 자체가 많이 들어감
    • DIP를 위반함(클라이언트가 구체 클래스에 의존)
    • OCP원칙을 위반할 가능성이 높아짐
    • getInstance()를 계속 사용해야해서 테스트하기 어려워짐
    • 즉, 유연성이 떨어짐
    해결책2 : 앞서 살펴본 싱글톤 컨테이너 사용하기

    스프링 컨테이너는 싱클톤 패턴의 문제점을 해결하며, 객체 인스턴스를 싱글톤으로 관리함.

    • 스프링 컨테이너는 싱글톤 컨테이너 역할을 함
    • 싱글톤 패턴을 구현할 때 사용한 지저분한 코드들이 필요 없음
    • DIP, OCP, 테스트, private 생성자로부터 자유로움
    주의점
    • 하나의 같은 객체 인스턴스를 공유하기에 무상태하게 설계해야함
    • 특정 클라이언트에 의존적인 필드가 없어야함
    • 특정 클라이언트가 값을 변경할 수 없어야 함

    싱글톤이 보장되는 이유

    @Configuration은 설정파일을 만들기 위한 애노테이션 or Bean을 등록하기 위한 애노테이션으로 이것이 구성정보@CGLIB로 만들고 싱글톤 패턴이 적용되도록 한다.

    *따라서 @Configuration을 적용하지 않고 @Bean만 적용하면 싱글톤이 보장되지 않는다.


    컴포넌트 스캔

    컴포넌트 스캔과 의존관계 자동 주입

    • @Componentscan 은 @Component어노테이션 및 streotype(@Service, @Repository, @Controller, @Repository, @Configuration)어노테이션이 부여된 Class들을 자동으로 Scan하여 Bean으로 등록해주는 역할을 하는 어노테이션임

    • @Autowired는 필요한 의존 객체의 “타입"에 해당하는 빈을 찾아 자동으로 주입해주는 것

    탐색 위치와 기본 스캔 대상

    • 탐색할 패기지의 시작위치 지정
          	@ComponentScan{
              	basePackages = "~~~~",
              }
          
      basePackage : 탐색할 패키지의 시작 위치 지정
    • * 권장 - 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트에 최상단에 두어 default값 이용하기

    중복 등록과 충돌

    1. 자동 빈 등록 vs 자동 빈 등록
      • 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데 스프링은 이에 대해 오류 발생시킴
        ConflictingBeanDefinitionException 예외 발생
    2. 수동 빈 등록 vs 자동 빈 등록
      • 수동 빈 등록이 우선권을 가짐
        * 스프링 기능 대부분은 단순한 것보다 복잡한 것이 우선권을 가짐

    다양한 의존관계 주입 방법(@Autowired)

    1. 생성자 주입
      • 생성자를 통해서 의존 관계 주입
      • 생성자 호출시점에 딱 1 번만 호출되는 것 보장
        why? 생성자는 한 번만 호출되기 때문
      • 불변, 필수 의존관계에 사용
      • 생성자 주입 시 final 키워드 꼭 붙이기
      * 생성자가 딱 1 개만 있으면 @Autowired 생략 가능(자주 사용하니 꼭 기억하기)
    2.   @Component
        public class XXXServiceImpl implements XXXService{
        
        	private final XXXRepository XXXrepository;
          
          	@Autowired //생성자 한 개여서 생략 가능
              public XXXServiceImpl(XXXRepository XXXrepository) {
              	this.XXXrepository = XXXrepository;
              }
        }
        
    3. 수정자 주입(setter 주입)
      • setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존 관계 주입
      • 선택, 변경 가능성이 있는 의존 관계에 사용
      • 자바빈 프로퍼티 규약의 수정자 메서드 방식 사용
      * @Autowired는 주입할 대상이 없으면 오류 발생
      (주입 대상 없이 오류발생하지 않는 법 뒤에서 다룸)
    4.   @Component
        public class XXXServiceImpl implements XXXService{
        
        	private final XXXRepository XXXrepository;
         
          	@Autowired
              public void setXXXRepository(XXXRepository XXXrepository) {
              	this.XXXrepository = XXXrepository;
              }
        }
        
    5. 필드 주입
      • 이름 그대로 필드에 바로 주입하는 방법
      • 코드가 간결하지만 외부 변경이 불가능해 테스트하기 힘듦
      • 사용하지 않을 것
      •   @Component
          public class XXXServiceImpl implements XXXService{
          	
            @Autowired
          	private final XXXRepository XXXrepository;
        

        }

    6. 주의 : 순수한 자바 테스트 코드에는 @Autowired동작 안함
    7. 일반 메서드 주입
      • 일반 메서드를 통해 주입 받을 수 있음
      • 한 번에 여러 필드 주입 받을 수 있음
      • 사용 그냥 거의 안함

    옵션 처리

    주입할 스프링 빈이 없어도 동작해야할 때 해결책

    • @Autowired(required=false) : 자동 주입할 대상 없으면 수정자 메서드 자체가 호출 안됨
    • @Nullable : 자동 주입할 대상이 없으면 null이 입력
    • Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력

    생성자 주입 사용하기

    최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장함
    이유
      불변
    • 대부분의 의존관계 주입은 한 번 일어나면 변경할 일 없음
    • 수정자 주입을 사용하면, setter를 public으로 열어두어야 함
    • 변경하면 안되는 메서드를 열어두는 것은 좋은 설계방법이 아님(데이터 은닉)
    • -> 생성자 주입은 객체를 생성할 때 딱 1 번 호출되니 불변 설계 가능
    • 누락
    • 생성자 주입을 사용하면 주입 데이터 누락 시 final 키워드에 의해 컴파일 오류 발생
    • 최고의 오류는 컴파일 오류임

    조회 빈이 2 개 이상일 때

    @Autowired는 type으로 조회하기에 같은 타입이 여러 개 존재하여 오류가 발생할 수 있음
    NoUniqueBeanDefinitionException오류 발생
    해결책
    1. @Autowired 필드 명 매칭
      • @Autowiredsms 타입 매칭을 시도하고, 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭함.
      • 타입 매칭을 시도하고 그 결과에 여러 번이 있을 때 추가로 동작하는 것
    2. @Qualifier 사용
        @Qualifier는 추가 구분자를 붙여주는 방법
      • 빈 등록시 @Qualifier를 붙임
                @Component
                @Qualifier("aaaXXXServicePolicy")
                public class XXXServicePolicy implements XXXPolicy {} 
                
      • 주입 시 @Qualifier를 붙여주고 등록한 이름 적어줌
                @Autowired
                public XXXServiceImpl(@Qualifier("aaaXXXServicePolicy")
                				XXXPolicy  xxxPolicy) {
               		this.xxxPolicy = xxxPolicy;           
               }
                
      • 만약 이 경우에 aaXXXServicePolicy을 찾지 못하면 aaXXXServicePolicy이름의 스프링 빈을 추가로 찾음
    3. @Primary 사용(자주 사용함)
        @Primary는 우선 순위를 벙하는 방법으로 여러 빈이 매칭되면 @Primary가 우선권을 가짐
                @Component
                @Primary
                public class AAAPolicy implements Policy{}
                
                @Component
                public class BBBPolicy implements Policy{}
                
      • 이 경우에 오류가 발생하지 않고 AAAPolicy가 우선권을 갖고 선택됨
    * @Primary vs @Qualifier
    • @Qualifier은 사용할 때마다 @Qualifier을 붙여줘야하기에 간편하게 사용할 수 있는 @Primary가 주로 사용되지만 앞서 설명한 것처럼 동시에 사용할 때 복잡한 @Qualifier이 우선권을 가진다.


    빈 생명주기 콜백

    스프링 빈의 라이프사이클 - 객체 생성 -> 의존 관계 주입
    * but, 생성자 주입은 예외
    더 정확한 스프링 빈의 이벤트 라이프 사이클
    스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료

    • 초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
    • 소멸전 콜백 : 빈이 소멸되기 직전에 호출

    스프링 빈 생명주기 콜백 지원 방법

    1. 인터페이스(InitializingBean, DisposableBean)
        단점
      • 이 인터페이스는 스프링 전용 인터페이스여서 코드가 스프링 전용 인터페이스에 의존함
      • 초기화, 소멸 메서드의 이름을 변경할 수 없음
      • 외부 라이브러리에 적용할 수 없음
      • -> 따라서 사용하지 않음
    2. 설정 정보에 초기화 메서드, 종료 메서드 지정
        설정 정보에 @Bean(initMethod = "init", destroyMethod = "close")처럼 초기화, 소멸 메서드를 지정할 수 있음
        특징
      • 메서드 이름을 자유롭게 줄 수 있음
      • 스프링 빈이 스프링 코드에 의존하지 않음
      • 코드가 아니라 설정정보를 사용하기에 외부 라이브러리에 적용 가능
      • ->3번 때문에 이 방식도 사용하지 않음
    3. @PostConstruct, @PreDestroy 애노테이션 지원
        @PostConstruct, @PreDestroy 이 두 애노테이션을 사용하면 가장 편리하게 초기화와 종료를 실행할 수 있음
        특징
      • 애노테이션만 붙이면 되어서 편리함
      • 자바 표준이기에 스프링이 아닌 다른 컨테이너에서도 동작
      • 외부라이브러리에 적용하지 못한다는 것이 유일한 단점

    빈 스코프

    개념

    빈 스코프 : 빈이 존재할 수 있는 범위

    종류

    • 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 법위의 스코프
    • 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
      • 스프링 컨테이너에 요청할 때마다 새로 생성
      • 종료 메서드가 호출되지 않음
      • 따라서 프로토타입 빈은 프로토타입 빈을 조회환 클라이언트가 관리해야함.(종료도 클라이언트가 직접)
    • 웹 관련 스코프
        웹 환경에서만 동작하고 프로토타입과 다라ㅡ게 스프링이 해당 스코프의 종료시점까지 관리함.(종료메서드가 따라서 호출됨)
      • request : 웹 요청이 들어오고 나갈 때까지 유지되는 스코프
      • session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
      • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

    profile
    백엔드 개발자를 꿈꾸는 대학생입니다

    1개의 댓글

    comment-user-thumbnail
    2023년 10월 18일

    좋은 정보 얻어갑니다.
    깔끔한 정리가 압권이네요.

    답글 달기