아래는 김영한 강사님의 스프링 핵심원리 강의를 듣고 중요한 내용 또는 어려운 내용을 정리한 것이다.
Section1
스프링의 핵심:스프링프레임워크
여러 스프링 기술 편리하게:스프링부트
스프링은 객체지향 언어(자바가 가진 강력한 특징을 살려내는 프레임워크 DI,IOC컨테이너)
스프링 프레임워크
핵심기술:DI 컨테이너,Aop 이벤트,기타
웹기술:스프링 MVC
데이터 접근 기술:트랜잭션 JDBC ORM 지원
기술통합: 캐시,이메일 ,원격접근 등
테스트
스프링부트
스프링을 편리하게 사용할 수 있도록 지원,기본으로 생성
스프링이란
스프링 DI 컨테이너 기술
스프링 프레임 워크
스프링 부트,스프링 프레임 워크를 모두 포함한 생태계
객체 지향 특징
"객체들의 모임" 객체는 메세지를 주고 받으며 협력 , 유연하고 변경용이
다형성이란?
클라이언트를 변경하지 않고 서버의 구현 기능을 유연하게 변경 할 수 있는게 다형성의 본질이다
스프링은 다형성을 편리하게 사용할 수 있도록 지원하는 기술이라 할 수 있음
SOLID
SRP:단일 책임 원칙:한 클래스는 하나의 책임만 가져야 한다.
OCP:개방 폐쇄 원칙:확장에는 열려잇으나 변경에는 닫혀 있어야 함
LSP:리코프 지환 원칙:프로그램의 객체는 하위타입의 인스턴스로 바꿀수 있어야 함.
ISP:인터페이스 분리 원칙:특정 클라이언트를 위한 Interface 여러개가 범용 Interface 하나보다 낫다
DIP:의존 관계 역전 원칙:추상화에 의존해야지 구체화에 의존하면 안된다.
결론
스프링은
DI(의존관계,의존성 주입),DI컨테이너 제공 함으로 다형성 +OCP,DIP 가능하게 지원한다.
Section2
변경 가능성이 높은 것들은 역할과 구현을 분리하는 인터페이스를 적극 활용할 수 있다
회원 클래스 다이어 그램과 회원 객체 다이어그램 중에 차이점 중 하나는 회원클래스 다이어그램은 인터페이스로 연결하여 실제로 무엇이 연결되는지 알수는 없으나 회원 객체 다이어그램은 클라리언트가 실제로 사용하는 것을 화살표로 표시한다
테스트코드에다가 테스트 하는 이유
실패했을 때 캐치가 쉽다
회원 도메인 설계의 문제점(순수 자바코드)
의존관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제점
ex)MeberService memberService=new MemberServiceImpl();
참고
sout order하면 자동으로 toString 출력된다
enumType은 ==비교가 가능하다
NULL이 들어갈 수 있다면 Long으로 그게 아니면 primitive type으로 해도 상관 없다(long)
Section3
문제점
우리가 만든 것에 새로운 정책을 추가하고 사용할 때 문제점이 발생한다
OrderServiceImpl에는 DiscountPolicy인터페이스 뿐만 아니라 FixDiscountPolicy인 구체 클레스도 함께 의존하고 있기에 DIP위반이다
정책을 변경하는 순간 OrderServiceImpl(클라이언트코드)도 함께 변경해야 하기 때문에 OCP위반이다
뿐만 아니라 OrderServiceImpl에서 무언가 생성하는 역할을 하는 것이 탐탁지 않다 단일 책임 원칙에 살짝 안맞는다
-->DIP를 위반하지 않게 하려면 인터페이스에만 의존하게 해야하는데?
해결방법
누군가가 클라이언트인 OrderServiceImpl에 구현 객체를 생성하고 주입해 주어야한다.
따라서 나의 애플리케이션 전체를 설정하고 구성하는 역할을 하는 AppConfig라는 것을 만들었다.
AppConfig에서 객체를 생성하고 주입해주므로(이 과정을 DI라고 함) 클라이언트 코드는 DIP를 완성하였다
클라이언트 코드에서 생성할 것이 없어지므로 단일책임원칙 까지 잘 지킨다
또한 클라이언트 코드를 변경하지 않아도 정책을 변경할 수 있으니 OCP를 잘 지킨다
--->AppConfig 같은게 필요하다
참고
테스트 작성할때 실패 테스트를 만들어야 한다.
AppConfig같은 설정 정보를 사용할 때는 역할과 구현이 잘 보이게 Refactoring 하는 것이 좋다
IoC(제어의 역전)이란 내가 무언가 호출하는 것이 아니라 framework같은 것이 내 코드를 대신 호출해 주는 것이라고 할 수 있다.
ex)Junit은 내가 짠 코드의 실행과 제어권을 가져서 Framework이다.
ApplicationContext는 Spring의 모든 것을 시작하면서 스프링 컨테이너의 역할을 한다. @Bean이라는 것을 다 관리한다라고 생각할것
결국
Spring이 DI컨테이너 역할을(DIP,단일책임원칙,OCP을 지킨다)를 하면서 추가적인 좋은 기능을 제공해주니 Spring을 쓴다.
Section4
컨테이너란?
사용하는 객체들을 담고 있는것,ApplicationContext가 컨테이너 역할을 함
참고
자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리됨.
빈 이름은 항상 다른 이름을 부여 해야 함
부모 타입으로 조회하면 자식 타입도 함꼐 조회하는 대원칙이 있다
스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계 되었다.
BeanDefinition이라는 추상화로 다양한 설정 형식을 지원
Section5
스프링 컨테이너의 필요성
객체 인스턴스가 JVM 안에 딱 하나만 있어야 하는 패턴을 싱글톤 패턴이라고 한다
스프링 없이 순수한 자바 코드로 new 를 한다면 서로 다른 객체가 생성될 것이다
우리는 요청할때마다 새로운 객체를 생성한다면 메모리 낭비가 심하므로 객체가 딱 1개만 생성되고 공유하도록 설계해야 한다
static 을 사용해서 자바 코드로 순수하게 설계할 수 있지만 DIP,OCP 위반 가능성이 커지며 코드가 지저분해지는 단점이 있다
싱글톤 컨테이너(스프링 컨테이너)를 사용한다면 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다
주의점
특정 클라이어느에 의존적인 필드가 있으면 안된다!
특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다!
가급적 읽기만 가능해야 한다(값을 수정하면 안된다)
@Configuration 과 싱글톤
@Configuration이 붙는다면 싱글톤을 보장해준다.
@Configuration 이 없다면 싱글톤이 보장이 안된다.
이 경우 New 해서 생성되는 객체는 Spring 이 관리를 안해준다
참고
테스트에서 isSameAs는 ==과 같고 , isEqualTo는 자바의 equal과 같다
객체 인스턴스를 생성하는데 비용이 1000이면 참조 가져오는 비용은 1정도로 엄청 작다
요청할 때마다 새로 꺼내는 경우, http request life cycle 에 맞추어서 빈 라이프 사이클을 맞추거나,
http session과 똑같은 life cycle 맞추어서 사용,고객이 들어올때 만들고 나갈때 죽이는 경우 만들거나
할때 Singleton 안쓰는 경우가 있다.(99% singleton)
Section6
요약
Autowired 는 마치 ac.getBean(MemberRepository.class)해주는 거랑 비슷하다
스프링 빈의 기본 이름은 클래스 명을 사용하되 맨 앞글자만 소문자를 사용한다.
기본 조회 전략: 타입이 같은 빈을 찾아서 주입
@Component 스캔이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
@SpringBootApplcation에 @ComponentScan 들어있다
Controller,@Service,@Repository,@Configuration 모두 @ComponentScan있다
자동 빈 등록 될때 괜히 이름 변경하지 말것
수동 빈 등록과 자동 빈 등록 시 스프링 부트는 에러 메세지를 띄운다.
참고
@Configuration 이 컴포넌트 스캔의 대상이 된 이유는 @Configuration 에 @ComponentScan애노테이션이 붙어있기 때문
Section7
의존관계 주입 방법
생성자 주입
생성자 호출시점에 딱 1번만 호출되는 것 보장
불변 필수 의존관계에 사용된다.
생성자 주입은 빈을 등록하면서 의존관계 주입도 함게 일어남
수정자 주입
선택,변경 가능성이 있는 의존관계에 사용됨
선택:@Autowired(required=false)로 선택가능
변경: 외부에서 강제로 호출가능
필드 주입
외부에서 변경이 불가능하고 순수한 자바코드 테스트를 못한다는 단점떄문에 사용하지 않는다.
일반 메서드 주입
일반 메서드를 통해서 주입할 수 있으나 잘 사용되지 않는다.
스프링 빈이 없어도 동작해야 할 경우
@Autowired(required=false)->자동 주입할 대상이 없으면 메소드 호출 안된다
@Nullable: 자동 주입할 대상이 없으면 NULL 입력
Optional:자동 주입할 대상이 없으면 Optional.empty 입력
생성자 주입이 좋은 이유
불변:대부분 의존 관계 주입은 한번 일어나면 종료시점까지 의존관계가 변경될 일이 없는데 이를 보장하는게 생성자 주입
누락: 수정자 주입같은 경우 데이터 누락시 컴파일 오류 없고 NULL pointer Exception 이 터지나 생성자 주입을 사용하면 주입 데이터를 누락하면 컴파일 오류선으로 해결이 가능하다.
final:생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아주는 장점이 있다.
정리
기본으로 생성자 주입 선택, 필수 값이 아닌 경우 수정자 주입 방식을 옵션으로 설정(생성자 주입과 설정자 주입 동시 사용가능)
롬복 사용 이유
@Getter,@Setter를 사용하면 자동으로 Getter,Setter를 만들어 준다
RequiredArgsConstructor로 final 이 붙은 것을 가지고 생성자를 자동으로 만들어 준다.
Autowired는 중복 타입 해결
@Autowired 는 타입으로 조회하기 때문에 두개 이상이 같은 타입일때 어떤 것을 끌고 올지 애매하다
해결방법
1)@Autowired 는 타입 매칭을 시도하고 여러 빈이 있다면 ,필드 이름 파라미터 이름으로 빈 이름을 추가로 매칭한다
따라서 DiscountPolicy discountPolicy 를 DiscountPolicy rateDiscountPolicy로 고쳐서 매칭 가능
2)@Qualifier: 클래스 위에 @Qualifier("이름")을 붙인 후 사용할 때도 @Qualifier("이름")으로 특정 빈 가져오기 가능하다
단, @Qualifier("이름")은 문자이기 떄문에 실수할 가능성이 높으므로 애노테이션을 따로 지정하는 것도 생각을 해볼 수 있어야 한다.
3)@Primary:@Primary가 붙은 클래스가 우선권을 가진다
정리하면...
자주 사용하는 것은 @Primary 로 사용 하고
그렇지 않은 것들은 @Qualifier로 따로 지정한다.
조회할 빈이 모두 필요할때:List,Map
할인 서비스를 제공하는데 클라이언트가 fix,rate 를 선택할 수 있다고 가정하자,이럴 떄 사용될 수 있다.
List '<'DiscountPolicy> policies 에 DiscountPolicy 타입으로 조회한 모든 스프링 빈 타입을 담는다
Map'<'String,DiscountPolicy> 에는 map에 키에 스프링 빈의 이름을 넣어주고 그 값으로 DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아줌.
자동vs수동
자동 기능을 우선으로 사용하되 기술적인 문제나 공통 관심사(AOP)를 처리할 떄 주로 사용하는 기술(데이터베이스 연결,공통 로그 처리) ->기술 지원 객체는 수동 빈으로 등록. 단 다형성을 활용하는 비즈니스 로직은 자동과 수동 등록을
고민해야 한다. 수동으로 등록하면 한눈에 빈의 이름, 어떤 빈들이 주입될지 알 수 있기 때문
참고
자바빈 프로퍼티:get,set 메소드 활용
new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class) 는 1) 컨테이너 생성 후 2)AutoAppConfig와 DiscountService 를 파라미터로 넘기면서 해당 클래스를 자동으로 스프링 빈 등록
Section8
빈 생명 주기 콜백
스프링 빈이 생성되거나 스프링 빈이 죽기 일보 직전에 스프링이 빈 안에 있는
메서드를 호출 할 수 있는 기능
주로 데이터베이스 커넥션 풀이나 ,네트워크 소켓처럼 애플리케이션 시작 지점에 필요한
연결을 할때 사용
객체 내부의 값을 세팅할때만 생성자를 활용하고
무거운 작업이나 외부 연결은 별도의 초기화 세팅으로 하는게 유지 보수 관점에서 좋다
초기화 작업은 의존관계 주입이 완료되고 난 다음에 호출하고 싶을떄 다음 세가지
방법을 사용한다.
인터페이스 InitiallizingBean,DisposableBean
스프링 전용 인터페이스이고 외부 라이브러리에 적용할 수 없기에 안쓴다
빈 등록 초기화 소멸 메서드 지정
@Bean(initMethod="init",destroyMethod="close")처럼 초기화 소멸 메서드 지정
외부 라이브러리에도 초기화 종료 메서드 지정 가능 하고 스프링 코드에 의존적이지 않다
종료 메서드는 close,shutdown 추론 가능
@PostConstructor,@PreDestroy
자바 표준이기에 스프링 이 아닌 다른 컨테이너에서도 동작
컴포넌트 스캔과도 어울린다.
외부라이브러리에 적용 불가
정리
빈 생성 시 메서드 호출, 종료시 메서드 호출 필요시
@PostConstructor,@PreDestroy 메서드 사용하되
코드를 고칠 수 없는 외부라이브러리라면 @Bean의 initMethod,destroyMethod를 사용할 것
Section9
스프링은 다양한 스코프 지원
싱글톤:스프링 컨테이너의 시작과 끝
프로토타입: 스프링 컨테이너는 빈의 생성, 의존관계 주입, 초기화 메서드 부르고 클라이언트에 던져버리고 끝
Request: 웹 요청이 들어오고 나갈때까지만 유지
session: 웹 세션이 생성되고 종료될떄 까지만 유지
프로토타입 스코프
싱글톤과 다르게 프로토타입 스코프의 빈을 스프링 컨테이너에 요청하면
->그 시점에 프로토타입 빈을 생성하고, 필요한 의존관계 주입
->생성한 프로토 타입 빈을 클라이언트에 반환하고, 클라이언트에게 책임을 넘김
->같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환
->종료 메서드 호출 X
싱글톤과 문제점
->싱글톤 안에서 프로토타입 빈을 사용한다면 의도와는 다르게
두 클라이언트가 프로토타입 클래스의 Count함수를 요청해도
총 Count가 2가 되는 문제점이 발생한다.
해결법
ObjectProvider
항상 새로운 프로토 타입 빈 생성가능
getObject를 호출하면 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아서 반환(DL)
JSR-330 Provider
라이브러리를 Gradle 에 추가해야함
기능 단순
프로토타입 빈 언제 사용?
매번 사용할때마다 의존관계 주입이 완료가 된 새로운 객체가 필요하면 사용
->거의 안씀
웹 스코프
request:HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다
동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵기에
request 스코프를 사용한다.
request 요청이 있을 때만 생성이 가능한 클래스가 있고 그 클래스를 가진 클래스가 있다면
빈의 생성 시점(정확히 말하면 스프링 컨테이너에 요청을 지연)을 지연하여 문제를 해결할 수 있다
해결법 1: ObjectProvider
해결법 2: 프록시
->가짜 프록시 객체를 만들어서 주입
->가짜 프록시 객체는 요청이 오면 그떄 내부에서 진짜 빈을 요청하는 로직이 있음
결국 중요한 것은 지연해서 처리한다는 것!
참고
AnnotationConfigServletWebServerApplicationContext로 웹과 관련된 애플리케이션 구동 가능
HttpServletRequest 는
1)HTTP 요청을 파라미터,헤더,쿠키,세션 등 액세스 가능
2)클라이언트의 IP주소,요청 메소드,요청 URL 등을 알 수 있다.
완료!