이번엔 인프런이라는 교육 사이트 중 우아한형제들 김영한님의 스프링 핵심 원리 - 기본편 를 듣고 메모한 내용을 포스팅 해봅니다.
읽기 좋게 풀어쓴 글이 아니라 단순히 메모를 옮겨 두워 핵심 키워드 정도만 파악할 수 있는 점 참고 부탁드립니다.
객체지향의 특징
추상화, 캡슐화, 상속, 다형성
객체지향 프로그램
컴퓨터 프로그램을 여러개의 독립된 단위, 즉 ‘객체’들의 모임으로 파악한 것
각 객체는 메세지를 주고받고, 데이터를 처리한다. (협력)
프로그램을 유연하고 변경이 용이하게 만든다
컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법
다형성 : 역활과 구현으로 세상을 구분
실세계와 객체지향은 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 이 붙은 메서드 명을 이름으로 사용
- 여기서
스프링 컨테이너 생성
- 스프링 컨테이너 생성
- 스프링 빈 등록
- 스프링 빈 의존관계 설정 - 준비
- 스프링 빈 의존관계 설정 - 완료
정리
- 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가 우선 순위를 가진다.
- @Autowired 필드명 매칭
자동, 수동의 올바른 실무 운영 기준
- 자동기능을 기본으로 사용
- 수동빈을 언제 사용하면 좋을까?
- 기술지원 빈 : 기술적인 문제나 공통 관심사(AOP)를 처리할 때 사용
- 비지니스 로직 중에서 다형성을 적극 활용 할때
빈 생명주기와 콜백
- 스프링 빈의 이벤트 라이프 사이클
- 스프링 컨테이너 생성 > 스프링 빈 생성 > 의존관계 주입 >
초기화 콜백
> 사용 >소멸전 콜백
> 스프링 종료
- 스프링 컨테이너 생성 > 스프링 빈 생성 > 의존관계 주입 >
- 객체의 생성과 초기화를 분리하자
- 스프링의 빈 생명 주기 callback 3가지 방법
- 인터페이스(InitializingBean, DisposableBean)
- 설정 정보에 초기화 메소드, 종료 메소드 지정
- @PostConstructor, @PreDestroy 애노테이션 지원
인터페이스 사용
- 초기화, 소멸 인터페이스 단점
- 스프링 전용 인터페이스에 의존
- 초기화, 소멸 메서드의 이름을 변경할 수 없다.
- 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.
빈 등록 초기화, 소멸 메서드
- 설정정보에 @Bean(initMethod, destroyMethod) 처럼 초기화, 소멸 메소드를 지정할 수 있다.
- 설정정보 사용의 특징
- 메서드 이름을 자유롭게 줄 수 있다.
- 스프링 빈이 스프링 코드에 의존하지 않는다.
- 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드 적용 가능
- 종료메서드 추론
- @Bean destroyMethod 속성은 별도로 기입하지 않을 경우, close, shutdown 이름의 메서드를 종료메서드로 사용한다.
- default 값이 inferred(추론) 으로 등록
- @Bean destroyMethod 속성은 별도로 기입하지 않을 경우, close, shutdown 이름의 메서드를 종료메서드로 사용한다.
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 : 웹 소켓과 동일한 생명주기를 가지는 스코프
마무리
그간 스프링을 사용하면서 스프링 기초에 대해서 잘 모른다고 늘 생각했는데, 조금이나마 깊이있게 알 수 있는 계기가 된 것 같네요.
추후 고급편도 강의를 듣고 포스팅 예정이니, 많이 기다려 주세요.
그럼 이만. 🥕👋🏼🖐🏼