Spring - 김영한 기본편 정리

choko's avatar
Jun 29, 2024
Spring - 김영한 기본편 정리
 
 

Solid 원칙

SRP(single Response Principle) - 단일 책임 원칙

한 클래스는 하나의 책임만 가져야 한다.

OCP (Open Closed Principle) - 개방-폐쇄 원칙

:소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.

LSP (Liscov Substitution Principle) - 리스코프 치환 원칙

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • ex) 자동차 인터페이스에서 엑셀은 앞으로 가는 기능이므로, 뒤로 가게 구현시 LSP의 위반이다.

ISP (Interface Seperate Principle) - 인터페이스 분리 원칙

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

DIP (Dependency Inversion Principle) - 의존관계 역전 원리

프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.
  • ex) 자동차에 의존해야지, 아반떼에 의존하면 안됨.
 
⇒ 다형성 만으로는 OCP, DIP를 지킬 수 없다. 뭔가 더 필요하다.
⇒ 스프링은 DI 등을 통해 OCP, DIP를 가능하게 할 수 있다.
 
 

스프링 컨테이너와 스프링 빈

@Configuration, @Bean

  • 스프링 컨테이너는 @Configuration이 붙은 class를 설정 정보로 사용한다.
  • @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. (스프링 빈)
    • 스프링 빈은 @Bean이 붙은 메서드명을 스프링 빈의 이름으로 사용한다.
  • 이렇게 등록을 하면, AppConfig를 사용해 직접 조회하는게 아닌, 스프링 컨테이너를 통해 필요한 객체를 찾아 사용할 수 있다. → applicationContext.getBean() 매서드 사용
 
 

스프링 컨테이너의 생성 과정

  1. 스프링 컨테이너 생성
//스프링 컨테이너 생성 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  1. 스프링 빈 등록
    1. 파라미터로 들어온 설정 클래스 정보를 사용해 스프링 빈을 등록한다.
  1. 스프링 빈 의존관계 설정 준비, 완료
 

스프링 빈 조회

ac.getBean(타입) ac.getBean(빈이름, 타입) // 같은 타입이 둘 이상 있을 시, 빈 이름 지정 ac.getBeansOfType(타입) // 특정 타입의 빈들을 모두 조회
  • 부모 타입으로 조회하면, 자식 타입도 함께 조회된다.
 
 

BeanFactoryApplicationContext

notion image
  • BeanFactory
    • 스프링 컨테이너의 최상위 인터페이스
    • 스프링 빈을 관리하고 조회하는 역할 담당
    • getBean()을 제공
  • ApplicationContext
    • BeanFactory 기능을 모두 상속받아서 제공
    • BeanFactory의 기능 뿐 아니라, 다음 기능들까지 제공한다.
      • 메세지 소스를 활용한 국제화 기능(ex: 한국어권 → 한국어 제공)
      • 환경 변수
      • 애플리케이션 이벤트
      • 리소스 조회
      •  
⇒ ApplicationContext는 BeanFacotry의 기능에 편리한 부과 기능을 제공한다
→ 그냥 ApplicationContext 쓰면 됨.
 
 

싱글톤 컨테이너

싱글톤 패턴

  • 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
  • 이미 만들어진 객체를 공유해 효율적 사용이 가능하다는 장점이 있다. 하지만 다음 문제점들이 있다.
  • 문제점
    • 싱글톤 패턴 구현 코드가 많이 들어감.
    • 클라이언트가 구체 클래스에 의존하게 된다. →. DIP 위반
    • DIP 위반으로 OCP 원칙도 위반할 가능성이 높다.
    • 테스트 하기 어렵다.
    • 내부 속성 변경, 초기화가 어렵다, 유연성이 떨어진다.
 

싱글톤 컨테이너

  • 스프링의 싱글톤 컨테이너를 사용하면, 싱글톤 패턴의 단점은 저절로 해결해 주고, 장점만 취할 수있다.
  • 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
    • @Configuration 태그를 달면, CGLIB를 이용해 바이트 코드를 조작한 클래스를 만들고, 그 클래스를 빈으로 등록한다.
    • @Configuration이 없으면, 객체들이 싱글톤으로 관리되지 않는다.
  • 싱글톤 방식의 주의점
    • 객체 상태를 유지하는 설계를 하면 안된다.
      • 특정 클라이언트에 의존적인 필드가 있으면 안된다,
      • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
      • 가급적 읽기만 가능해야 한다,
       
@Configuration 태그를 달면, 스프링 빈이 이미 스프링 컨테이너에 등록되어 있다면 해당 컨테이너를 찾아서 반환하기 때문에, 싱글톤이 보장된다.
 
 
 

컴포넌트 스캔

@ComponentScan

  • 등록해야 할 빈이 많아지면, 일일이 등록하기 귀찮고, 설정 정보가 커지고, 누락 등의 문제가 발생한다.
  • 그래서 스프링은 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
  • @Autowired이라는 의존관계 자동 주입 기능도 제공한다.
 
  • 사용법
    • 다음과 같이 @ComponentScan() 애노테이션을 붙여준다. 이제, @Component가 붙은 클래스들은 모두 스프링 빈으로 등록된다,
@Configuration @ComponentScan() public class AutoAppConfig { } }
 
@Component public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; @Autowired public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } }
 
  • @ComponentScan은 @Component 뿐만 아니라, 다음 내용들도 스캔에 포함한다.
    • @Controller : 스프링 MVC 컨트롤러에서 사용
    • @Service : 스프링 비즈니스 로직에서 사용
    • @Repository : 스프링 데이터 접근 계층에서 사용
    • @Configuration : 스프링 설정 정보에서 사용
    •  

@Autowired

  • (주로)생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아 주입한다.
  • 생성자에 파라미터가 많아도, 다 자동으로 찾아서 주입한다.
 

@Filter

  • includeFilters: 컴포넌트 스캔 대상을 추가로 지정한다.
  • excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.
 
 

의존관계 자동 주입

의존관계 주입에는 크게 4가지 방법이 있다.
  • 생성자 주입
  • 수정자 주입(setter)
  • 필드 주입
  • 일반 메서드 주입
 
→ 이 중 대부분 생성자 주입 방법을 사용한다.

생성자 주입

  • 생성자를 통해서 의존 관계를 주입한다.
    • 생성자 호출 시점에 딱 1번만 호출된다.
    • 불변, 필수 의존관계에 사용된다.
@Component public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; @Autowired public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } }
  • 생성자가 1개만 있으면 @Autowired를 생략할 수 있다.
 
 

옵션 처리

  • 주입 할 스트링 빈이 없어도 동작해야 할 때 required 옵션으로 처리할 수 있다. (default 값은 true임)
//호출 안됨 @Autowired(required = false) public void setNoBean1(Member member) { System.out.println("setNoBean1 = " + member); }
  • @Autowired(required = false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않는다.
 
 

생성자 주입을 선택해야 하는 이유

  • 대부분의 의존관계는 한번 일어나면 애플리케이션 종료시점까지 변경할 일이 없다.(오히려 불변해야 한다)
  • 누군가의 실수로 변경될 수 있다는 리스크가 있다.
  • 생성자 주입을 사용하면, 필드에 final 키워드를 사용할 수 있다.
    • 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
    • 생성자 주입 외 방식들은 모두 생성자 이후에 호출되므로, final 키워드를 사용할 수 없다.
 

롬복(lombok)

  • Lombok은 model 클래스나 Entity 같은 도메인 클래스 등에 반복되는 getter, setter, toString 등의 메소드를 자동으로 만들어주는 기능을 하는 라이브러리이다.
  • @RequiredArgsConstructor 기능을 사용하면, final이 붙은 필드를 모아 생성자를 자동으로 만들어준다.
@Component @RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; }
  • 눈으로 보이지는 않지만, 하단의 constructor 매서드를 호출 가능하고, 컴파일 시점 시 생성자 코드를 자동으로 생성해준다.
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; }
 
 
 

조회 빈이 2가지 이상

  • @Autowired는 타입으로 조회된다.
@Autowired private DiscountPolicy discountPolicy
  • 다음과 같은 DiscountPolicy 타입을 Autowired 할 경우
ac.getBean(DiscountPolicy.class)
  • 으로 조회하기 때문에, DiscountPolicy 타입의 빈이 2개 이상일 때 문제가 발생한다.
 

해결 방법

  1. @Autowired 필드 명 매칭
    1. 필드 명을 필드 명, 파라미터 명으로 빈 이름을 매칭한다.
  1. @Qualifier 사용
    1. Qualifer는 추가 구문자를 붙여주는 방법이다.
    2. // 주입 시 @Component @Qualifier("mainDiscountPolicy") public class RateDiscountPolicy implements DiscountPolicy {} // 생성자 주입 예시 @Autowired public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; } // 이 경우 mainDiscountPolicy를 매칭한다.
       
  1. @Primary 사용
    1. @Primary가 할당된 빈이 우선 순위를 가진다.
    2. @Component @Primary public class RateDiscountPolicy implements DiscountPolicy {} @Component public class FixDiscountPolicy implements DiscountPolicy {} // 이 경우 RateDiscountPolicy가 우선권을 가진다.
       
       
 

빈 생명주기 콜백

빈 생명주기 콜백 시작

  • 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면 객체의 초기화/종료 작업이 필요함
  • 스프링 빈은 다음과 같은 라이프사이클을 가진다.
    • 객체 생성 → 의존관계 주입
  • 스프링 빈은 의존관계 주입이 완료되면 초기화 시점을 알려주는 기능을 제공한다.
  • 또, 컨테이너 종료 직전 소멸 콜백도 제공한다.
  • 생명주기 콜백을 사용한다고 하면
    • 스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸전 콜백 → 스프링 종료
  • 스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.
    • 인터페이스(InitializingBean, DisposableBean)
    • 설정 정보에 초기화 메서드, 종료 메서드 지정
    • @PostConstruct, @PreDestroy 애노테이션 지원 → 최신 스프링에서 권장하는 방법
    • @PostConstruct public void init() { System.out.println("NetworkClient.init"); connect(); call("초기화 연결 메시지"); } @PreDestroy public void close() { System.out.println("NetworkClient.close"); disConnect(); }
 
 

빈 스코프

빈 스코프란

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

프로토타입 스코프

  • 싱글톤 스코프 : 항상 같은 인스턴스의 스프링 빈을 반환
  • 프로토타입 스코프 : 항상 새로운 인스턴스를 생성해서 반환, @Scope(”prototype”)으로 선언한다
    • 스프링 컨테이너는 프로토타입 빈 생성 → 의존관계 주입 → 초기화 까지의 과정만 처리한다.
    • 따라서 @PreDestroy 와 같은 종료 메서드는 실행되지 않는다. 종료 메서드에 대한 호출은 클라이언트가 해야 한다.
  • 프로토타입 빈 요청 시 과정
      1. 프로토타입 스코프 빈을 스프링 컨테이너에 요청
      1. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입.
      1. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환
      1. 이후 컨테이너에 같은 요청이 오면, 항상 새로운 프로토타입 빈을 생성하여 반환
       
       

싱글톤 빈과 함께 사용 시 Provider 사용

  • Provider를 사용해야 싱글톤 빈과 프로토타입 빈을 함께 사용 시 항상 새로운 프로토타입 빈을 생성할 수 있다.
  • ObjectProvier, javax.inject.Provider 가지 방법이 있다.
  1. ObjectProvider : 지정한 빈을 컨테이너에게 대신 찾아주는 DL(의존관계 조회) 서비스를 제공
@Autowired private ObjectProvider<PrototypeBean> prototypeBeanProvider; public int logic() { PrototypeBean prototypeBean = prototypeBeanProvider.getObject(); prototypeBean.addCount(); int count = prototypeBean.getCount(); return count; }
  • prototypeBeanProvider.getObject() 을 통해 항상 새로운 프로토타입 빈이 생성된다. 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아 반환한다.
  1. jakarta.inject.Provider : provider.get()을 통해 항상 새로운 프로토타입 빈을 생성한다.
import ( "jakarta.inject.Provider" ) @Autowired private Provider<PrototypeBean> provider; public int logic() { PrototypeBean prototypeBean = provider.get(); prototypeBean.addCount(); int count = prototypeBean.getCount(); return count; }
  • 자바 표준으로 기능이 단순하여 단위 테스트 시 용이하다.
 

프로토타입 빈을 언제 사용하는가?

  • 의존관계 주입이 완료된 새로운 객체가 필요하면 사용한다.
  • 하지만 프로토타입 빈을 직접적으로 사용하는 경우는 매우 드물다.
 
 

웹 스코프

  • 웹 환경에서만 동작한다.
  • 프로토타입 스코프와 다르게, 스프링이 해당 스코프의 종료시점까지 관리한다. → 종료 메서드가 호출된다.
  • 다양한 종류가 있다. → request, session, application, websocket
Share article

Tom의 TIL 정리방