스프링 핵심원리 기본편

존스노우·2021년 12월 14일
0

스프링

목록 보기
12/22

이어서 기본편을 작성.

비즈니스 요구사항과 설계

임시로 메모리 회원 저장소 로 만든다

HashMap은 동시성 이슈가 발생할 수 있으니 ConcurrentHashMap 사용하면 해결 가능하다.

회원 도메인 실행과 테스트

초창기 이슈

OCP 원칙지키지 않음.

다른 로직을 개발 하면서 차차 수정 .

주문과 할인 도메인 설계

역할과 구현을 분리해서 자유롭게 객체를 조립하게 설계 한다.

회원 / 할인정책 / 주문(서비스)

주문

구현체 부분은 수업 완료후 완성된 부분이지만 원래 이과정에서는 DI 없이 new로 사용자가 직접
코드로 주입해 주었다.

스프링 핵심 원리 이해2 객체 지향 원리 적용

할인 정책 확장

비율로 할인해주는 정책 클래스 확장

테스트 코드는 생략.

새로운 할인 정책 적용과 문제점

할인 정책을 변경하려면 OrderServiceImpl 코드를 고쳐야됨..

역활 과 구현을 분리하고 다형성을 활용해 구현 객체를 분리했지만 OCP DI 를 지키지 않았다.

변경하려면 OrderServiceImpl 고쳐야 되기 때문!

수정은 제한해야 된다..

인터페이스에만 의존하도록 설계한다.

해결방안..

누군가 구현 객체를 대신 생성하고 주입해줘야되지..

AppConfig 등장 !

토비의 스프링에선 factory가 담당했던 부분.

실제작성한 코드부분 현재 진행진도 코드부분 은 밑으로

생성자 형태로 주입해준다.

이런식으로 설계 변경을 함으로써 인터페이스에만 의존하도록 변경 하였다.

Appconfig 로 인해 외부에서 주입해주는 제어의 역전이 된다.

Appconfig 리팩터링

함수로 분리함으로써 중복을 제거했다.

수정이 일어났을경우 함수 부분만 수정 해주면 된다.

새로운 구조와 할인 정책 적용

공연기획자 appconfig 메소드 만 변경해주면된다.

전체 흐름 정리

다형성 덕분에 새로운 정률 할인 정책 코드 추가 개발 문제 없음.

새로운 할인 정책 적용과 문제점.
클라이언트 코드인 주문 서비스 구현체도 함께 변경해야함.
주문 서비스 클라이언트가 인터페이스인 DiscountPolicy 뿐만아니라

구체 클래스인 FixDiscountPolicy도 의존 .. DIP 위반임.

AppConfig 리펙터링 함으로서 해결

새로운 구조와 할인 정책 적용 !

사용 영역과 구성하는 영역 분리..

좋은 객체 지향 설계의 5가지 원칙 적용

SRP 단일 책임의 원칙

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

ex) AppConfig는 구현 객체를 생성하고 연결하는 역활
클라인트 객체는 실행하는 책임만 담당.

DIP 의존관계 역전 ㅂ원칙

프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.
의존성 주입은 이 원칙을 따르는 방법 중 하나임.

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

다형성 사용하고 클라이언트가 DIP를 지킴
애플리케이션을 사용 영역과 구성 영역으로 나눔.
소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀 있다.

Ioc, DI, 컨테이너

제어의 역전 IoC(Inversion of Control)

프로그램의 제어 흐름을 외부에서 관리하는 것을 제어의 역전 이라고 함.

구현 객체를 만들고 인터페이스는 어떤 객체들이 실행될지 모르는 대 그 흐름을 외부에 맡김

프레임워크 VS 라이브러리

프레임워크가 내가 작성한 코드를 제어하고 대신 실행하면 그것은 프레임워크다.
내가 작성한 코드가 직접 제어의 흐름을 담당한다면 라이브러리

의존관계 주입 DI

의존관계는 정적인 클래스 의존 관계와 실행 시점에 결졍되는 동적인 객체(인스턴스)의존 관계 둘을
분리해서 생각해야 함.

정적인 클래스 의존관계

동적인 객체 인스턴스 의존 관계

애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계

정적인 의존관계는 인터페이스까지 의존관계를 알 수 있음.
동적인 의존관계는 실행시점에서 보여지는 것이기 때문에 구현체 까지 실제로 어떤
객체에 의존하는 것 까지 알 수 있다.

실제 의존관계가 주입되는 것을 의존관계 주입이라고 한다.

IoC컨테이너 , DI 컨테이너

AppConfig 처럼 객체를 생성하고 의존관계를 연결해 주는걸 IoC컨테이너 DI 컨테이너라고 함.
최근에는 DI컨테이너라고 함.
또는 어샘블러, 오브젝트 팩토리라 함

스프링으로 전환하기

Configuration / bean 붙여주기.

스프링 컨테이너에 스프링 빈으로 등록한다.

ex)

스프링 컨테이너

Applicationontext를 스프링 컨테이너라 함.

이제부턴 스프링 컨테이너를 통해서 사용됨.

스프링 컨테이너를 통해 필요한 스프링 빈 을 찾아야 함.

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

ApplicationContext 는 스프링 컨테이너 이며 인터페이스임

스프링 컨테이너 생성 과정

빈 이름은 항상 다른 이름 부여 같은 이름을 부여하면 하나의 빈이 무시되는 경우가 있거나 덮어씀

  • 빈 이름을 직접 설정 할 수 도 있다.

스프링 빈 의존관계 설정 - 준비

스프링 빈은 이름으로 조회할경우 중복 일 수도 있음으로

스프링 빈의 이름을 지정하는 방법으로 해결 가능하다.

또한 빈의 부모로도 자식 빈을 조회 할 수 있다.

beanFactory

스프링 컨테이너의 최상위 인터페이스

스프링 빈을 조회하고 관리하는 역할 담당

ApplicationContext

BeanFactroy 기능을 모두 상속 받아 제공

빈을 관리하고 검색하는 기능을 BeanFactory가 제공해줌

xml 파일 예제 참고만.

스프링 빈 설정 메타 정보 BeanDefinition

스프링은 다양한 설정 형식을 지원할 수 있던 이유는
BeanDefinition 이라는 추상화 덕분.

역활과 구현을 개념적으로 나눈 것 이다.

메타정보 라고도 한다.

싱글톤 컨테이너

스프링은 태생이 기업용 온라인 서비스 기술을 지원하기 위함.
스프링은 웹 애플리케이션이지만 애플리케이션 도 가능함.
웹 애플리케이션은 여러 고객 동시 요청

기존 스프링 없이 할 경우 무스한 객체 생성 소멸 -> 메모리 낭비

싱글톤 패턴을 이용해 한 개의 객체만 생성 및 공유 하도록 설계

싱글톤 패턴

클래스의 인스턴스가 1개만 생성되는 것을 보장하는 디자인 패턴.

그래서 private 생성자를 사용해 외부에서 임의로 new 키워드를 재사용하지 못하게.

싱글톤 패턴 문제점

싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
의존관계상 클라이언트가 구체 클래스에 의존한다.
DIP를 위반한다. 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
테스트하기 어렵다.
내부 속성을 변경하거나 초기화 하기 어렵다.
private 생성자로 자식 클래스를 만들기 어렵다.
결론적으로 유연성이 떨어진다.
안티패턴으로 불리기도 한다.

싱글톤 컨테이너

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서 , 객체 인스턴스를 싱글톤(1개만 생성)으로
관리 한다.

지금까지 학습한 스프링 빈이 싱글톤으로 관리됨

싱글톤 컨테이너

스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
이전에 설명한 컨테이너 생성 과정을 자세히 보자.
컨테이너는 객체를 하나만 생성해서 관리한다.
스프링 컨테이너는 싱글톤 컨테이너 역할을 한다.
이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.
스프링 컨테이너의 이런 기능 덕분에 싱글턴 패턴의
모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수있다.
싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.

요청이 들어올 때 마다 이미 만들어진 객체를 공유해서 효율적으로 재사용할수 있다.

싱글톤 방식의 주의점

싱글톤 패턴이든 ,스프링 같은 싱글톤 컨테이너를 사용하든. 객체 인스턴스를 하나만 생성해서

공유하는 방식은 여러 클라이언트가 하나의 같은 인스턴스를 공유하기 때문에 싱글톤 객체 상태는
유지(stateful)하게 설계하면 안된다.

무상태(stateless)로 설계해야 한다.

특정 클라이언트에 의존적인 필드가 있으면 안됨.
특정 클라이언트가 값을 변경할 수 있는 필드가 있음 안됨
가급적 읽기만
필드 대신 자바에서 공유되지 않는 지역변수 파라미터 ThreadLoca 등 사용해야 한다.
스프링 빈의 필드에 공유 값을 설정하면 장애 발생..

위에사항은 최정적으로 20000원 결과만 나옴..

공유 필드는 항상 진짜 조심 스프링 빈은 항상 무상태로 설계!

@Configurationh과 싱글톤

memberSerivce 와 orderService를 호출하면 각각 new MemoryMemberRepository() 생성...

뭔가 이상함.. 싱글톤이 깨지는 것 처럼 보임.. 해결해야 된다.

테스트 ㄱ

다 같은 걸로 싱글톤이 유지되면서 호출됨..

@Configuration과 바이트코드 조작의 마법

스프링 컨테이너는 싱글톤 레지스트리.

스프링 빈이 싱글톤이 되도록 보장해 줘야됨.

근디 위에 자바 코드는 3번 호출되야 맞는대 그게 아님

스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용함.

모든 비밀은 @Configuration AppConfig에 있음

컴포넌트 스캔

컴포넌트 스캔과 의존관계 자동 주입 시작하기.

지금까지 스프링 빈 등록할 때 자바 코드 @Bean 등을 통해서 설정정보에 직접 나열함.
예제가 몇개 안되었지만. 만약 빈이 수십 수백개가 되면 ....?

그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔을 제공함

또한 의존관계도 자동으로 주입하는 @Autowird 라는 기능을 제공함.

코드

컴포넌트 스캔을 사용하려면 @ComponentScan을 설정 정보에 붙여주자.
기존 AppConfig와는 다르게 @Bean 등록한 클래스가 하나도 없음.

이제는 클래스안에서 의존관계 주입을 해결해야 한다.

@Autowrid를 사용하면 생성자에서 여러 의존관계도 한번에 주입받을 수 있다.

AutoAppConfig 작동 확인.

탐색 위치와 기본 스캔 대상

필요한 위치부터 탐색 시작

기본적으론 설정 정보 클래스 위치를 프로젝트 최상단에 두는것이 좋다.

컴포넌트 스캔 기본대상

@Component
@Controller
@Service

등등..

커스텀 애노테이션을 만들어서

스캔할 컴포넌트와 스캔되지 않을 컴포넌트를 만들어서

적용하고 테스트 해본다.

중복 등록과 충돌.

컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까?

  1. 자동 빈 등록 vs 자동 빈 등록
  2. 수동 빈 등록 vs 자동 빈 등록

자동 빈 등록 vs 자동 빈 등록

오류 발생.

수동 빈 vs 자동 빈 등록

의존관계 자동 주입

생성자 주입
수정자 주입
필드 주입
일반 메서드 주입

생성자 주입

수정자 주입은 생략 잘안쓰는거 같다.

필드 주입

코드가 간결해서 좋지만 외부에서 변경이 불가능해 테스트하기 힘들다.

DI 프레임 워크가 없으면 아무것도 할 수 없다.

사용안하는게 좋음... 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 사용..

테스트 코드에선 오토와이어드가 동작하지 않아요.
@SpringBootTest 처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능..

final 키워드

생성자 주입을 사용하면 필드에 final 키워드 사용 가능.
그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 막아줌.

롬복과 최신 트랜드

생성자가 1개 있으면 오토와이어드 생략가능..

이제 롬복을 적용해보면

@RequiredArgsConstructor

final 이 붙은 필드를 모아서 생성자를 자동으로 만들어 준다.

조회 빈이 2개 이상 일때?

@Autowired는 타입으로 조회

ex) ac.getBean(DiscountPolicy.class)

에러가 발생하게 됨.

@Autowird 필드 명 , @Qualifier, @Primary

해결 방법을 알아보자

조회 대상 빈이 2개 이상일때?

  1. Autowird 필드 명 매칭
  2. Qualifier -> @Qualifer끼리 매칭 -> 빈 이름 매칭
  3. Primary 사용

@Autuowird 타입 매칭.

오토와이어드는 타입 매칭을 시도 하고 여러 빈이 있다? 필드 이름, 파라미터 이름으로 빈을 추가매칭.

타입 매칭 -> 필드명 매칭.. 파리미터명으로..

Qualifier 사용

추가 구분자를 붙여주는 방식.

추가적인 방법을 부여하지 빈 이름을 변경하는 것이 아니다.

Primary 사용

우선순위 정하는 방법 Autowird 시 여러 빈이 매칭되면 우선권을 가진다

우선순위는 Qualifer가 더높다.

보통 메인 디비는 Primary 서브 디비는 Qualifier

애노테이션 직접 만들기.

Qualifier("mainDiscountPolicy) 문자를 적으면 컴파일시 타입 체크가 안된다.

조횧한 빈이 모두 필요할 때, List , Map

의도적으로 해당 타입의 빈이 전부 필요할 때가 있음

코드 정리하면서 요약본..

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

편리한 자동 기능을 기본으로 사용하자.

어떤 경우에 컴포넌트 스캔과 자동 주입/ 어떤 경우에 설정 정보를 통해 수동 빈등록
의존관계도 수동으로 ?

결론부터 얘기하면 -> 자동으로 선호하는 추세

그럼 수동빈은 언제?

업무 로직 빈 : 웹을 지원하는 컨트롤러 비즈니스 로직이 있는 서비스 , 데이터 계층 로직 처리하는 리포지토리 업무 로직/ 보통 비즈니스 요구사항 개발 될때 추가나 변경.

기술 지원 빈: 기술저깅ㄴ 문제나 공통 관심사(AOP)를 처리할 대 주로 사용 됨.

데이터베이스 연결이나 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들.

업무로직은 자동 기능 적극 사용 권장.

기술 지원 로직은 수가 매우적고 애플리케이션 전반에 걸쳐서 광범위하게 영향을 미침.
가급적 수동 빈 등록.

비즈니스 로직 중에서 다형성을 적극 활용?

의존관계 자동주입 - 조회한 빈이 모두 필요할때 List Map 부분

내가보기엔 쉽지만 남이 보기엔 어렵다.

내가 직접 기술 지원 객체를 스프링 빈으로 등록하면 수동으로 등록해서

명확하게 들어내는게 좋다.

빈 생명주기 콜백

스프링을 통해 초기화 작업과 종료가 어떻게 진행될까?

간단하가 외부 네트워크에 미리 연결하는 객체를 생성한다 했을때..

객체생성단계 -> url 없음 생성된 뒤 외부 수정자에의해 url 생성됨.

스프링 빈은 다음과 같은 라이프 사이클을 가짐

객체 생성 - > 의존관계 주입

스프링 빈은 객체를 생성하고 의존관계 주입이 다 끝나야 필요한 데이터를 사용할 준비를 마친다.

따라서 초기화 작업은 의존관계 주입이 다 완료되고 호출해야됨.

그런대 의존관계 주입 완료 시기를 어떻게? 알까?

스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는

다양한 기능을 제공 함. 스프링 컨테이거나 종료되기 직전 소멸 콜백도 알려줌

스프링 빈 이벤트 라이플 사이클

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백

-> 사용 -> 소멸전 콜백-> 스프링 종료

초기화 콜백 : 빈이 생성되고 빈의 의존관계 주입이 완료되고 호출

소멸전 콜백: 빈이 소멸되기 직전에 호출

스프링은 다양한 방식으로 생명주기 콜백을 지원한다.

객체의 생성과 초기화를 분리하자

스프링 빈은 3가지 삔 생명주기 콜백을 지원한다

인터페이스
설정 정보에 초기화 메서드 종료 메서드 지정
@PostConstruct, @PreDestroy 애노테이션 지원

IntializingBean 은 afterPropertiesset() 메서드로 초기화를 지원함.

DisposableBean은 destroy() 메서드로 소멸을 지원함..

초기화 소멸 인터페이스 단점

이 인터페이스는 스프링 전용 인터페이스, 해당 코드가 스프링 전용 인터페이스의 의존

초기화 소멸 메소드 이름 변경 못함

내가 코드를 고칠수 없는 외부 라이브러리에 적용할 수 없음.

요즘은 거의 사용 안함.

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

설정 정보 사용 특징

메서드 이름 자유롭게

스프링 빈이 스프링 코드에 의존하지 않음.

코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화
종료 메서드를 적용 할 수 있다.

@PostConstruct, @PreDestory

특징

최신 스프링에서 가장 권하는 법
애노테이션 하나만 붙이면되서 굉장히 간편
패키지를 잘 보면 스프링의 종속적인 기술이 아니라 JSR-250 자바 표준 따라서 다른 컨테이너에도 동작

컴포넌트 스캔과 잘 어울림

유일한 단점은 외부 라이브러리에는 적용하지 못함/ 외부라이브러리를 초기화 종료 해야 되면
@Bean의 기능을 사용하자

빈 스코프

스코프는 빈이 존재할 수 있는 범위를 뜻함.

스프링이 지원하는 스코프

싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위 스코프
프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 , 더는
관리하지 않는 매우 짧은 범위 스코프

웹 관련 스코프 request 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
session: 웹 세션이 생성되고 종료될때까지 유지되는 스코프
application: 웹 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

프로토타입 스코프

싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈 반환

반면 프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스 생성해서 반환함.

싱글톤 빈 요청.

핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리함.
프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다.

그래서 @PreDestory 같은 종료 메서드가 호출 되지 않음

싱글톤 스코프 빈 테스트

프로토타입 스코프 빈 테스트

싱글톤 빈은 스프링 컨테이너 생성 시점에 초기화 메서드 실행

프로토 타입 스코프의 빈은 스프링 컨테이너에서 빈을 조회할 때 생성 되고 초기화 메서드도 실행된다.

프로토타입 빈을 2번 조회했으므로 완전히 다른 스프링 빈이 생성되고 초기화도 2번 실행 된 것을 확인 할 수 잇다.

싱글톤 빈은 스프링 컨테이너가 관리함 그래서 스프링 컨테이너가 종료 될때 빈의 종료 메서드 실행

프로토타입 빈은 스프링 컨테이너가 생성과 의존관계 주입 그리고 초기화 까지만 관여
더는 관혀하지않아 종료 메서드가 실행되지 않는다.

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

싱글톤 빈과 같이 사용하면 문제점이 발생.

각각 새로운 빈을 반환해주기 때문에 두 빈의 count 는 1이다.

싱글톤에서 프로토타입 빈 사용

싱글톤을 사용하게되면 프로토빈을 받는다해도 빈이 새로 생기긴 하지만 싱글톤빈과 함께 유지되는 문제.

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

싱글톤 빈과 프로토타입 빈을 함께 사용할 때 어떻게 하면 사용할 때 마다 항상 새로운 프로토 타입
빈을 생성할 수 있을까?

스프링 컨테이너에 요청

가장 간단한 방법은 싱글톤 빈이 프로토타입을 사용 할때마다 스프링 컨테이너에 새로 요청

ac.getBean 통해서 항상 새로운 프로토타입 빈 생성

직접 의존관계를 찾는 것을 DL 의존관계 조회 탐색 라 함

그런대 이러면 종속적인 코드가 되고 테스트가 어려워짐

DL 정보만 기능을 제공하는것은?

ObjectFactory, ObjectProvider

지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스는 ObjectProvider
오브젝트 팩토리가 있지만 여러가지 기능을 추가한게 ObjectProvider

prototypeBeanProveider.getObject() 항상 새로운 프로토타입 빈이 생성됨

getObject를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환DL

JSR_330 Provider

정리

프로토타입 빈 사용은 언제? 매번 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가

필요할 때. 그러나 실무에선 대부분 싱글톤 빈으로 해결 함. 그래서 프로토타입빈 직접 사용 하는 경우
드믐.

웹 스코프

특징
웹 환경에서만 동작
해당 스코프의 종료시점까지 관리해서 종료 메서드가 호출

종류

request 스코프 예제 만들기

예제개발

request 스코프 빈은 아직 생성되지않아서... 실제 고객 요청이 와야 생성됨.

해결방법!

Provider

스코프와 프록시

프록시 방식 이용!

이렇게하면 가짜 프록시 클래스를 만들어 두고 HTTP request와 상관 없이 가짜 프록시 클래스를

다른 빈에 미리 주입 해 둘 수 있다.

나머지 코드는 프로바이더 이전 코드로 복귀.

실행 해보면 잘 동작 한다.

왜 이렇게 됬을까?

웹 스코프와 프록시 동작 원리

profile
어제의 나보다 한걸음 더

0개의 댓글