2carrot84
by 2carrot84
9 min read

Categories

  • lecture

Tags

  • spring
  • 김영한
  • 스프링
  • 우아한형제들

이번엔 인프런이라는 교육 사이트 중 우아한형제들 김영한님스프링 핵심 원리 - 기본편 를 듣고 메모한 내용을 포스팅 해봅니다.

읽기 좋게 풀어쓴 글이 아니라 단순히 메모를 옮겨 두워 핵심 키워드 정도만 파악할 수 있는 점 참고 부탁드립니다.

객체지향의 특징

추상화, 캡슐화, 상속, 다형성

객체지향 프로그램

컴퓨터 프로그램을 여러개의 독립된 단위, 즉 ‘객체’들의 모임으로 파악한 것

각 객체는 메세지를 주고받고, 데이터를 처리한다. (협력)
프로그램을 유연하고 변경이 용이하게 만든다
컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법

다형성 : 역활과 구현으로 세상을 구분

실세계와 객체지향은 1:1 매핑 되지 않는다.
구현이 변경되어도 영향을 주지 않아야 한다.
대체 가능 해야 한다.

역활과 구현을 분리

장점

  • 클라이언트는 대상의 역활(interface)만 알면 된다.
  • 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.
  • 클라이언트는 구현 대상의 내부 구조가 변경 되어도 영향을 받지 않는다.
  • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.

자바 언어의 다형성 활용

  • 역활 = 인터페이스
  • 구현 = 인터페이스를 구현한 클래스, 구현 객체

객체 설계 시 역활과 구현을 명확히 분리해야 하며, 역활을 먼저 부여하고, 그 역활을 수행하는 구현 객체를 만들자

객체의 협력이라는 관계부터 생각

  • 클라이언트 : 요청, 서버 : 응답

수 많은 객체 클라이언트와 수많은 객체 서버는 서로 협력 관계를 가진다.

자바 언어의 다형성

  • 오버라이딩 된 메소드가 실행
  • 다형성으로 인터페이스를 구현(or 상속)한 객체를 실행시점에 유연하게 변경

다형성의 본질

  • 인터페이스를 구현한 인스턴스를 실행 시점에 유연하게 변경할 수 있다.
  • 협력이라는 객체사이의 관계에서 시작
  • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.

정리

  • 실세계의 역활과 구현이라는 편리한 컨셉을 다형성을 통해 객체 세상으로 가져올 수 있음
  • 유연하고 변경이 용이
  • 확장 가능한 설계
  • 클라이언트에 영향을 주지 않고 변경 가능
  • 인터페이스를 안정적으로 잘 설계하는 것이 중요

한계

  • 역활 자체가 변하면, 클라이언트, 서버 모두에게 큰 변경이 발생한다.
  • 인터페이스를 잘 설계 하는것이 가장 중요

스프링과 객체지향

  • 다형성이 가장 중요하다
  • 스프링은 다형성을 극대화 할 수 있게 도와 준다 (스프링 컨테이너 역활)
  • IoC, DI는 다형성을 활용해서 역활과 구현을 편리하게 다룰 수 있도록 지원

좋은 객체지향 설계의 5가지 원칙(SOLID) - 로버트 마틴

  • SRP : 단일 책임의 원칙 (Single Responsibility Priciple)
  • OCP : 개방/폐쇄 원칙 (Open/Closed Priciple)
  • LSP : 리스코프 치환 원칙 (Liskov Substitution Priciple)
  • ISP : 인터페이스 분리 원칙 (Interface Segregation Priciple)
  • DIP : 의존관계 역전 원칙 (Dependency Inversion Priciple)

SRP

  • 한 클래스는 하나의 책임만 가져야 한다.
    • 모호한 기준 (책임은 클수도, 작을 수도 있다, 문맥과 상황에 따라 다르다)
  • 중요한 기준은 변경
    • 변경이 있을때 파급 효과가 적어야 한다.

OCP

  • 확장에는 열려 있으나, 변경에는 닫혀 있어야 한다.
  • 다형성을 활용
  • 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능 구현
  • 문제점
    • 구현객체를 변경하려면 클라이언트 코드 변경
    • 다형성을 사용했지만 OCP 원칙을 지킬 수 없다
    • 객체를 생성하고, 연관 관계를 맺어주는 별도의 조립, 설정자가 필요 (Spring Container - IoC, DI)

LSP

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스를 바꿀 수 있어야 한다.
  • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다.
    • 자동차 I/F 의 엑셀은 앞으로 가라는 기능, 뒤로 가게 구현하면 LSP 위반

ISP

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

DIP

  • 추상화에 의존, 구체화에 의존하면 안된다.
  • 클래스에 의존 하지 말고, 인터페이스에 의존 해라
  • 구현이 아닌 역활에 의존해야 한다.

정리

  • 객체지향 핵심은 다형성
  • 다형성만으로는 구현 객체를 변경할때 클라이언트 코드도 함께 변경됨
    • OCP, DIP를 지킬 수 없다.

객체지향설계와 스프링

  • 스프링은 다음 기술로 다형성 + OCP, DIP를 가능하게 지원
    • DI (Dependency Injection) : 의존관계, 의존성 주입
    • DI 컨테이너 제공
  • 클라이언트 코드의 변경 없이 기능 확장

정리

  • 모든 설계에 역활과 구현을 분리하자
  • 이상적으로는 모든 설계에 인터페이스를 부여하자
  • 실무 고민
    • 인터페이스를 도입하면 추상화라는 비용이 발생
    • 기능을 확장할 가능성이 없다면, 구체 클래스를 직접 사용
      • 필요시에 리팩토링 해서 인터페이스 도입

IoC

  • 구현객체는 자신의 로직을 실행하는 역활만 담당 (추상화에만 의존)
  • 제어의 흐름은 다른 객체가 가져간다.
  • 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전 이라 한다.

프레임워크 VS 라이브러리

  • 프레임워크 : 내가 작성한 코드를 제어하고, 대신 실행
  • 라이브러리 : 내가 작성한 코드가 직접 제어의 흐름을 담당

DI

  • 정적인 클래스 의존관계와 실행시점에 동적인 객체(인스턴스) 의존관계를 분리해서 생각해야 한다.
  • 정적인 클래스 의존관계
    • 애플리케이션을 실행하지 않고 의존관계를 쉽게 판단할 수 있다.
      • 실제 어떤 객체가 주입될지 알 수 없다.
  • 동적인 객체(인스턴스) 의존관계
    • 실행시점에 외부에서 실제구현 객체를 생성하고, 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.
      • 의존관계 주입을 사용하면, 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

IoC 컨테이너, DI 컨테이너

  • 객체 생성하고 관리, 의존관계를 연결해 주는 것
  • 어셈블러, 오브젝트 팩토리

스프링 컨테이너

  • ApplicationContext를 스프링 컨테이너라 한다.
  • 스프링 컨테이너는 @Configuration 이 붙은 객체를 설정(구성) 정보로 사용
    • 여기서 @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록(스프링 빈) 한다.
    • 스프링빈은 @Bean 이 붙은 메서드 명을 이름으로 사용

스프링 컨테이너 생성

  1. 스프링 컨테이너 생성
  2. 스프링 빈 등록
  3. 스프링 빈 의존관계 설정 - 준비
  4. 스프링 빈 의존관계 설정 - 완료

정리

  • Application Context 는 Bean Factory의 기능을 상속 받는다.
  • Application Context 는 빈 관리기능 + 편리한 부가 기능을 제공한다.
  • Bean Factory 를 직접 사용할 일은 거의 없다. 부가기능이 포함된 Application Context를 사용한다.
    • Bean Factory, Application Context 를 스프링 컨테이너라고 한다.

스프링 빈설정 메타정보 - Bean Definition

  • 스프링이 다양한 설정 형식을 지원할 수 있는 이유는 ‘Bean Definition’이라는 추상화가 있다
    • 역활과 구현을 개념적으로 나눈 것
    • 스프링 컨테이너는 xml인지 자바코드인지 모르고, 오직 ‘Bean Definition’만 알면 된다.

웹어플리케이션과 싱글턴

  • 스프링은 태생이 기업용 온라인 서비스 기술을 지원하기 위해 탄생
  • 웹어플리케이션은 보통 여러 고객이 동시에 요청한다.
  • 스플링 없는 순수 DI 컨테이너(AppConfig)는 요청할 때 마다 객체를 새로 생성
    • 고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고, 소멸된다 (메모리 낭비가 심하다)
    • 해결방안은 해당 객체가 딱 1개만 생성되고, 공유하도록 설계하면 된다. (싱글턴 패턴)

싱글턴 패턴

  • 고객이 요청할 때마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 사용
  • 문제점
    • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
    • 의존관계상 클라이언트가 구체 클래스에 의존 -> DIP 위반
    • 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
    • 테스트 하기 어렵다.
    • 내부 속성을 변경하거나 초기화 하기 어렵다.
    • private 생성자로 자식 클래스를 만들기 어렵다
    • 유연성에 떨어진다

싱긍톤 컨테이너

  • 스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다.
    • 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다 -> 싱글톤 레지스트리
    • DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.

싱글톤 방식의 주의점

  • 무상태(Stateless) 로 설계해야 한다.
    • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    • 특정 클리어언트가 값을 변경할 수 있는 필드가 있으면 안된다.
    • 가급적 읽기만 가능해야 한다.
    • 필드 대신에 자바에 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용

@Configuration

  • AnnotationConfigApplicationContext 에 파라미터로 넘긴 값은 스프링 빈으로 등록
    • 해당 스프링빈 클래스 정보를 보면 클래스명에 xxxCGLIB 이 붙음
    • 스프링이 CGLIB 이라는 바이트코드 조작 라이브러리를 사용 AppConfig 클래스를 상속받은 임의의 다른 클래스 생성 후 빈에 등록
  • @Bean 이 붙은 메서드 마다 이미 스프링빈이 존재하면 빈을 반환하고, 스프링 빈이 없으면 생성해서 빈으로 등록하고 반환하는 코드가 동적으로 만들어진다.
    • 싱글톤이 보장됨
    • @Configuration 생략시 싱글톤이 보장되지 않음 (의존관계 주입이 필요해서 메서드를 직접 호출할 때)

컴포넌트 스캔

  • @ComponentScan 은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록
  • 스프링빈의 기본 이름은 클래스명을 사용하되 맨앞글자만 소문자로 사용
    • 빈 이름을 직접 부여할 수도 있음

@Autowired

  • 생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링빈을 찾아 주입
    • 기본조회 전략은 타입이 같은 빈을 찾아서 주입

탐색위치와 기본 스캔 대상

  • 탐색할 패키지의 시작 위치 지정
    • 필요한 위치 부터 탐색하도록 지정할 수 있다(basePackage, basePackageClasses)
    • 별도 지정하지 않으면 @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
  • 컴포넌트 스캔 기본 대상
    • @Component
    • @Controller : spring mvc 컨트롤러
    • @Service
    • @Repository : Data 계층 예외를 추상화된 스프링 예외로 변환
    • @Configuration : 스프링 설정 정보로 인식, 스프링 빈이 싱글톤을 유지

필터

  • FilterType 옵션
    • annotation : 기본값, 애노테이션을 인식해서 동작
    • ASSIGNABLE-TYPE : 지정된 타입과 자식 타입을 인식해서 동작
    • ASPECT : Aspect 패턴 사용
    • REGEX : 정규식 사용
    • CUSTOM : ‘TypeFilter’ 라는 인터페이스를 구현해서 처리

빈 중복등록과 충돌

  • 자동 빈 등록 VS 자동 빈 등록
    • 이름이 같은 경우 스프링은 오류를 발생시킨다
  • 자동 빈 등록 VS 수동 빈 등록
    • 수동빈이 우선권을 가짐
    • 스프링부트의 경우 오류를 발생시킨다.

의존관계 주입방법

  • 생성자 주입 : 생성자 호출 시점에 딱 1번만 호출되는 것이 보장, ‘불변, 필수’ 의존관계에 사용
  • 수정자 주입 (setter 주입) : ‘선택, 변경’ 가능성이 있는 의존관계에 사용
  • 필드 주입 :
    • 외부에서 변경이 불가하여, 테스트 하기 힘들다.
    • DI 프레임워크가 없으면 아무것도 할 수 없다.
  • 일반 메서드 : 한번에 여러 필드 주입, 거의 사용 하지 않음

옵션 처리

  • 주입된 빈이 없어도 동작해야 할 때가 있다.
    • @Autowired 는 require 옵션이 true가 기본값으로 되어 있어 자동주입 대상이 아니다.
    • @Nullable : 자동주입 대상이 없으면 null이 입력
    • Optinal<> : 자동주입 대상이 없으면 Optional, Empty

생성자 주입을 선택하자

  • 불변
    • 대부분 의존관계 주입은 한번 일어나면 애플리케이션 종료 시점까지 의존관계를 변경할 수가 없다.
      • 오히려 대부분 의존관계는 애플리케이션 종료 전까지 변화면 안된다.
    • 수정자 주입은 set 메서드를 public 으로 열어두어, 누군가 실수로 변경할 수 있다.
  • 누락
    • 생성자 주입은 주입 데이터를 누락 할 경우 ‘컴파일 오류’가 발생한다.
      • 수정자 주입은 runtime 에러 발생
  • final 키워드
    • 생성자 주입시 final 키워드를 사용할 수 있어, 혹시라도 값이 설정되지 않는 오류를 ‘컴파일’ 시점에 알려준다.
  • 정리
    • 생성자 주입은 순수한 자바언어의 특징을 잘 살리는 방법
    • 기본적으로 생성자 주입을 사용, 필수값이 아닌 경우 수정자 주입 방식을 옵션으로 부여
      • 필드 주입은 사용하지 말라.
  • 조치 대상 빈이 2개 이상일 경우
    • @Autowired 필드명 매칭
      • 타입 매칭 > 타입매칭 결과가 2개 이상 일때 > 필드명, 파라미터 명으로 빈 이름 매칭
    • @Quilifier 사용
      • 주입시 추가 구분자를 붙혀주는 방법 (빈 이름 변경 x)
      • @Quilifier 끼리 매칭 > 빈 이름 매칭
    • @Primary 사용 : 우선순위를 정하는 방법 @Autowired 시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
      • @Quilifier와 같이 사용시 @Quilifier가 우선 순위를 가진다.

자동, 수동의 올바른 실무 운영 기준

  • 자동기능을 기본으로 사용
  • 수동빈을 언제 사용하면 좋을까?
    • 기술지원 빈 : 기술적인 문제나 공통 관심사(AOP)를 처리할 때 사용
    • 비지니스 로직 중에서 다형성을 적극 활용 할때

빈 생명주기와 콜백

  • 스프링 빈의 이벤트 라이프 사이클
    • 스프링 컨테이너 생성 > 스프링 빈 생성 > 의존관계 주입 > 초기화 콜백 > 사용 > 소멸전 콜백 > 스프링 종료
  • 객체의 생성과 초기화를 분리하자
  • 스프링의 빈 생명 주기 callback 3가지 방법
    1. 인터페이스(InitializingBean, DisposableBean)
    2. 설정 정보에 초기화 메소드, 종료 메소드 지정
    3. @PostConstructor, @PreDestroy 애노테이션 지원

인터페이스 사용

  • 초기화, 소멸 인터페이스 단점
    • 스프링 전용 인터페이스에 의존
    • 초기화, 소멸 메서드의 이름을 변경할 수 없다.
    • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

      빈 등록 초기화, 소멸 메서드

    • 설정정보에 @Bean(initMethod, destroyMethod) 처럼 초기화, 소멸 메소드를 지정할 수 있다.
    • 설정정보 사용의 특징
      • 메서드 이름을 자유롭게 줄 수 있다.
      • 스프링 빈이 스프링 코드에 의존하지 않는다.
      • 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드 적용 가능
    • 종료메서드 추론
      • @Bean destroyMethod 속성은 별도로 기입하지 않을 경우, close, shutdown 이름의 메서드를 종료메서드로 사용한다.
        • default 값이 inferred(추론) 으로 등록

Annotation @PostConstruct, @PreDestroy

  • 이 두 annotation을 사용하면 가장 편리하게 초기화와 종료를 실행할 수 있다.
  • @PostConstruct, @PreDestroy
    • 최신 스프링에서 가장 권장하는 방법
    • 매우 편리하다
    • 자바 표준기술로 스프링이 아닌 다른 컨테이너에서도 동작한다.
    • 컴포넌트 스캔과 잘 어울린다.
    • 단점은 외부 라이브러리에 적용 할 수 없다.
      • 외부 라이브러리 초기화, 종료 시 @Bean 의 기능 사용

정리

  • @PostConstruct, @PreDestroy annotation을 사용하자
  • 코드 수정이 불가한 외부 라이브러리를 초기화, 종료해야 하면, @Bean(initMethod, destroyMethod) 를 사용하자

빈 스코프란?

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

프로토타입 스코프

  • 싱글톤빈과 다르게 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환
  • 스프링 컨테이너는 프로토 타입 빈을 생성하고, 의존관계 주입, 초기화 까지만 처리
    • 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다.
    • @PreDestroy 메소드가 호출되지 않는다.

프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점

  • 싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만 싱글톤 빈과 함께 계속 유지되는 것이 문제다.

프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결

  • ObjectFactory, ObjectProvider
    • 지정한 빈을 컨테이너에서 대신 찾아주는 DL서비스를 제공
  • JSR-330 Provider
    • 자바표준을 사용하는 방법, 별도의 라이브러리 필요, 스프링이 아닌 다른 컨테이너에서 사용가능
  • 웹스콜프
    • 웹 환경에서만 동작, 스프링 종료 시점까지 관리
    • 웹 스코프의 종류
      • request : HTTP 요청 하나가 들어오고 나갈때까지 유지, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.
      • session : HTTP Session과 동일한 생명주기를 가지는 스코프
      • application : 서블릿 컨텍스트와 동일한 생명주기를 가지는 스코프
      • websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프

마무리

그간 스프링을 사용하면서 스프링 기초에 대해서 잘 모른다고 늘 생각했는데, 조금이나마 깊이있게 알 수 있는 계기가 된 것 같네요.

추후 고급편도 강의를 듣고 포스팅 예정이니, 많이 기다려 주세요.

그럼 이만. 🥕👋🏼🖐🏼