프로그램의 제어 흐름을 구현객체가 직접 담당하는게 아닌 외부에서 관리하는것
객체 생성 및 관리 의존관계 연결해주는 역할을 해주는게 DI컨테이너( = IoC컨테이너, 어셈블러, 오브젝트 팩토리)
DI 컨테이너 사용시, 별도의 설정 클래스를 만들어 DIP, OCP, SRP 원칙을 지킬 수 있음
-> 구현객체(구체클래스)의 생성/연결하는 책임을 설정클래스로 분리가능 - 일반 객체들은 자기 역할을 수행하는것만 집중 가능
-> 인터페이스에만 의존(DIP)
-> 설정클래스의 코드만 고치고 클라이언트 코드는 손댈게 없음(OCP)
자바 스프링을 이용하면 이게 쉽게 가능
제어의 흐름을 내가 작성한 코드가 담당한다면 라이브러리
내 코드를 대신 제어하고 실행한다면 프레임워크
인터페이스에 들어갈 수 있는 구체클래스들까지 표시
실제 new를 통해 실행시 동적으로 객체들의 연관관계가 어떻게 맺어지는지 나타낸것(인스턴스간의 관계 다이어그램)
스프링에 test 패키지에 코드 작성하면 배포 시 테스트코드는 자동으로 빼고 배포
HashMap은 동시성 이슈있음, 실무에선 ConcurrentHashMap주로 사용
설정클래스인스턴스.getBean(빈이름, 타입)
설정클래스인스턴스.getBean(타입)
해당 빈 없으면 'NoSuchBeanDefinitionException: No bean named 'xxxxx' available' 예외 발생
빈이 리턴하는 구체클래스를 타입변수로 넣어줘도 가능은함
-> 다만 구체클래스에 의존하는게 좋은 코드는 아님
해당 타입의 모든 빈을 조회
beanDefinition 정보를 받아옴
스프링 내부에서 사용하는 빈인지 내가 등록한 빈인지 역할을 알수 있는 메서드
BeanFactory(interface)
↑
ApplicationContext(interface)
↑
AnnotationConfigApplicationContext,
GenericXmlApplicationContext,
XxxApplicationContext
스프링 빈을 관리, 조회해줌
getBean()제공
BeanFactory뿐만 아니라 MessageSource, EnvironmentCapable, ApplicationEventPublisher, ResourceLoader등을 상속받아 어플리케이션 개발에 필요한 부가기능 제공
MessageSource
-> 한국에서 들어오면 한국어, 영어권은 영어로 출력해주는 등의 기능 제공
EnvironmentCapable
-> 로컬, 개발, 운영등을 구분해서 처리
ApplicationEventPublisher
-> 이벤트 발행 및 구독 모델을 편리하게 지원
ResourceLoader
-> 파일, 클래스패스, 외부등에서 리소스를 조회하는 기능
메타정보인 BeanDefinition으로 설정정보를 추상화시켜 다양한 설정형식 지원
빈 하나당 하나의 BeanDefinition이 생성됨
각각의 설정 형식에 대응하는 XXXDefinitionReader로 설정형식 읽어 BeanDefinition 생성
각 형식에 대응하는 BeanDefinitionReader사용
-> AnnotationConfigApplicationContext : AnnotatedBeanDefinitionReader
-> GenericXmlApplicationContext : XmlBeanDefinitionReader
새로운 설정 형식 추가하려면 XxxBeanDefinitionReader 만들어 BeanDefinition 생성하면 됨
BeanClassName
-> 빈의 클래스명
factoryBeanName
-> 팩토리 역할의 빈 이름 (ex: appConfig)
factoryMethodName
-> 빈을 생성할 팩토리 메서드 지정
Scope
-> 싱글톤(기본값)
lazyInit
-> 스프링컨테이너 생성시 빈을 같이 생성할지, 실제 빈 사용시 빈을 생성할지
InitMethodName
-> 메서드 주입으로 주입 시, 사용할 초기화 메서드 명
DestroyMethodName
-> 빈의 생명주기가 끝나 제거시 호출되는 메서드명
Constructor arguments, Properties
-> 의존관계 주입시 사용되는 정보
싱글톤 패턴이 아닌 DI컨테이너를 만들면, 요청때마다 객체를 새로 만들어 메모리 낭비
싱글톤 패턴 구현에 코드가 많이 들어감
private 생성자로 자식클래스 만들기 어려움
개체 생성을 외부에서 관여하기 어려움(생성자의 패러미터 전달 등)
-> 내부속성 변경 및 초기화 어려움
-> 테스트 어렵고, 유연성 떨어짐
스프링 컨테이너는 자동으로 객체를 싱글톤으로 생성해줌
-> 기본등록방식은 싱글톤, 매번 새로운 객체 생성하는 기능도 제공
싱글톤의 단점 해결 - DIP, OCP, 테스트 용이, private생성자 안해도됨
싱글톤 객체를 생성/관리 하는 기능을 '싱글톤 레지스트리'라고 함
인스턴스를 하나만 생성해 공유 -> stateless하게 설계해야 한다
-> 참고 : stateful한 설계란? https://bit.ly/3Whtyej
특정 클라이언트에 의존적, 값을 변경할 수 있는 필드 X
가급적 읽기만 가능
-> 정 필요하다면 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal등을 사용
스프링 빈의 필드에 공유값 있으면 큰 장애 발생가능성
@Configuration 설정시 자동으로 스프링빈(@Bean)을 싱글톤으로 생성
AppConfig 빈의 클래스 정보를 파라미터로 넘겨 출력해보기
->AnnotationConfigApplicationContext에 파라미터로 넘긴 값도 자동으로 스프링 빈으로 등록됨
->AppConfig.getClass()를 출력시 class hello.core.AppConfig$$EnhancerBySpringCGLIB$$bd479d70와 같이 나옴
실제로 빈에 등록되는건 CGLIB(바이트코드 조작 라이브러리) 사용해 AppConfig를 상속받은 클래스
-> 이를 통해 싱글톤 생성
@Bean 없이도 자동으로 스프링 빈을 등록해줌
자동으로 의존관계 주입을 위한 @Autowired 기능도 있음
@ComponentScan으로 설정 java파일을 만들면 @Component가 붙은 모든 클래스를 스프링 빈으로 등록
기본이름은 첫글자만 소문자로 바꾼 클래스명
빈 이름 지정하려면 @Component("name")
탐색 시작위치와 그 하위패키지 모두를 탐색
기본은 @ComponentScan 붙은 설정정보 클래스의 패키지가 시작위치
(일반적으로 시작위치 지정 안하고, 그냥 프로젝트 최상단에 설정정보를 둠)
아래와 같이 시작위치 설정가능
@ComponentScan(basePackages = "hello.core")
@ComponentScan(basePackages = {"hello.core", "hello.service"})
생성자, 메서드, 필드등에 @Autowired 붙이면 스프링 컨테이너가 해당 스프링빈을 자동으로 찾아서 주입
기본 조회전략은 같은 타입의 빈을 주입
@Autowired(required = false)
-> 주입 대상 없으면 메서드 호출 안됨
org.springframework.lang.@Nullable
-> 매개변수에 @Nullable 어노테이션 명시해 사용
-> 주입 대상 없으면 null이 입력
Optional<>
-> 주입 대상 없으면 Optional.empty가 입력
-> 매개변수 타입을 Optional<사용할 클래스타입>으로 받아서 사용
NoUniqueBeanDefinitionException 오류가 발생
아래 방법 사용
@Autowired 매칭 시 같은 타입 빈이 여러개면 필드/파라미터 의 이름으로 매칭
아래와 같이 사용, 여러개의 빈 중 @Qualifier로 지정해준 이름이 일치하는 빈 주입
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
package hello.core.annotataion;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@Component 지정된 클래스에 명시하면, 여러 빈이 매칭될때 우선권을 가짐
이때 @Quilifier의 우선권이 더 높음
-> 매인으로 사용하는 빈에는 @Primary를, 가끔 서브로 사용하는 빈에는 @Qualifier를 이용하면 코드 깔끔하게 유지 가능
아래 어노테이션들은 컴포넌트 스캔의 기본대상
(내부적으로 @Component 어노테이션이 명시됨, java의 상속관계가 아닌 spring이 자체적으로 지원하는 기능)
useDefaultFilters 옵션을 끄면 기본 스캔대상들이 제외됨
스프링 MVC 컨트롤러로 인식
스프링 데이터 접근 계층으로 인식
데이터 계층의 예외를 스프링 예외로 변환
앞서 보았듯이 스프링 설정 정보로 인식
스프링 빈이 싱글톤을 유지하도록함
따로 기능은 없음
핵심 비즈니스 로직이 있는곳에 주로 명시함
컴포넌트 스캔대상 설정
includeFilter, excludeFilter
기본값, 애노테이션을 인식
지정한 타입과 자식 타입을 인식
AspectJ 패턴 사용 (자바용 aop 확장기능)
정규 표현식
TypeFilter 이라는 인터페이스를 구현해서 처리
ConflictingBeanDefinitionException 예외 발생
수동 빈 등록이 우선권을 가짐 (수동빈이 자동 빈을 오버라이딩 해버림)
-> 에러 발생 가능성 다분, 최근 스프링 부트에선 오류가 발생토록 기본값
주로 생성자 주입 사용
-> 객체의 생성부터 소멸까지 데이터를 유효한 상태로 유지해야하는 객체지향의 방향성과 맞음
생성자 주입시 컴파일 단계에서 데이터 누락 파악 가능
-> 생성자 주입에선 주입 데이터가 누락되었을때 컴파일 오류 발생
-> 생성자 주입만 멤버변수에 final키워드 사용 가능
생성자에서 설정되지 않는 필드가 있으면 컴파일 시 오류로 파악가능
기본적으로 생성자 주입 사용, 필수가 아닌 값들에 대해 수정자 주입을 옵션으로 부여
불변, 필수 의존관계에 사용
생성자 호출 시점에 1번만 호출되는것이 보장됨
스프링 빈의 경우 생성자 딱 1개만 있으면 @Autowired 생략해도 자동 주입
롬복 라이브러리의 @RequiredArgsConstructor 사용하면 final붙은 필드를 모아 생성자를 자동으로 만듦
setter 통해 의존관계 주입
-> setter 작성하고 @Autowired지정하면 자동으로 의존관계 주입
선택, 변경가능성 있는 의존관계 사용
객체 설계관점에서 setter 남발하면 캡슐화 의미 사라짐...
변수앞에 @Autowired만 해주면 됨(생성자, 수정자 필요 X)
DI프레임 워크 없다면 아무것도 할 수 없으므로 왠만하면 쓰지말것
-> 자바 코드에서 테스트할 경우가 많은데, 이때 아무것도 할 수 없게됨 (멤버변수 값 넣기 불가능)
잘 사용 안함
필드를 주입받을 일반 메서드(void init())를 작성해 @Autowired 지정
클라이언트가 할인의 종류를 선택한다거나 할때 조회한 스프링 빈이 전부 필요
Map<String, DiscountPolicy> : DiscountPolicy 타입으로 조회한 모든 스프링 빈에 대하여,
키에 스프링 빈의 이름, 그 값으로 해당 스프링 빈을 담아준다.
List <DiscountPolicy> : DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.
해당 타입의 스프링 빈 없으면 빈 컬렉션이나 Map주입
스프링 이용시 전략 패턴을 간단하게 구현 가능
-> 한 클래스에 모든 빈을 자동 주입해 담은 다음 사용
편리한 자동 기능을 기본으로 사용
-> 자동써도 DIP. OCP 지켜짐
직접 등록하는 기술 지원 객체(공통관심사를 aop로처리시)는 수동 등록
-> 숫자가 적고, 문제 발생시 명확히 드러나지 않음
다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민
-> 다른 사람이 짠 클래스를 List, Map으로 주입받으면 어떤빈들이 주입되고, 각 빈들의 이름은 뭔지 파악하기 힘듦 - 여러 코드를 찾아봐야됨
-> 알아보기 쉽게 수동빈 등록, 또는 자동쓰려면 특정패키지에 같이 묶어둘것
스프링/스프링 부트가 자동으로 등록하는 빈들은 예외
-> 그냥 기본 사용법과 의도대로 잘 사용하는게 나음
@PostConstruct, @PreDestroy 애노테이션을 기본적으로 사용
코드 수정 불가한 외부 라이브러리 사용시 @Bean의 initMethod, destroyMethod 설정정보 사용해 콜백함수 지정
빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
빈이 소멸되기 직전에 호출
스프링은 크게 아래 3가지 방법의 빈 생명주기 콜백 지원
스프링 초기에 나온 방법, 나머지 두 방법이 더 좋아서 지금은 거의 사용X
afterPropertiesSet() 오버라이딩 통해 초기화 위한 콜백 지원
destroy() 오버라이딩 통해 소멸전 콜백 지원
@Bean(initMethod = "initMethodName", destroyMethod = "closeMethodName")
으로 지정해주면 빈 초기화 시 initMethodName(), 소멸시 closeMethodName() 호출함
빈 설정정보 이므로, 스프링에 의존X
코드가 아닌 설정정보를 어노테이션으로 지정하므로, 코드를 고칠 수 없는 외부 라이브러리 사용시에도 초기화 및 종료메서드 적용 가능
destroyMethod(종료메서드)는 따로 지정하지 않아도 close, shutdown 이라는 이름의 메서드가 있으면 자동으로 호출해줌 (접근제어자 설정상 접근가능할때 되는듯, 이렇게 할거면 public까먹지 말기)
종료메서드 추론기능을 쓰지 않으려면 destroyMethod = ""으로 지정하면 됨
JSR-250 자바표준의 어노테이션
최신 스프링에서 가장 권장 방법
자바 표준기술이라 스프링 아니어도 작동
다만 코드수정 불가한 외부라이브러리에 적용 못함
초기화 콜백 함수 지정
소멸전 콜백 함수 지정
스프링 컨테이너가 빈을 관리하는 범위를 의미
기본적으로 싱글톤 스코프로 생성됨
빈을 지정하는 어노테이션(@Bean, @Component)과 함께 @Scope("prototype")과 같이 입력하면 됨
스프링 컨테이너의 시작과 종료까지 유지되는 가정넓은범위의 스코프
스프링 컨테이너가 빈의 생성, 의존관계 주입까지만 관여
종료 메서드(@PreDestroy등)이 자동호출되지 않음
웹환경에서만 동작, 웹 라이브러리 추가후 사용
-> build.gradle에 implementation 'org.springframework.boot:spring-boot-starter-web' 로 웹 라이브러리 추가
-> spring-boot-starter-web라이브러리 추가시 스프링 부트는 내장 톰켓서버를 활용해 웹서버와 스프링을 함께 실행
-> 웹 라이브러리 있으면 AnnotationConfigServletWebServerApplicationContext 기반으로 어플 구동, 없으면 AnnotationConfigApplicationContext기반
기본포트인 8080을 다른곳에서 사용중이면 main/resources/application.properties에 server.port=9090입력해 포트를 9090으로 수정
웹 요청이 들어오고 나갈때 까지 유지되는 스코프
http 요청 하나당 빈 하나씩 생성됨
-> 서비스 계층에 매 http 요청마다 따로 요청정보를 파라미터 넘길필요 없음, 스프링이 알아서 http요청별로 관련 필드(ex: logger)를 주입해줌
-> 서비스 계층을 웹기술에서 분리시켜, 유지보수관점에서 좋음
request스코프에선 http요청이 들어올때 빈 생성
-> 먼저 생성되는 다른 빈들에서 request스코프의 빈 요청시 생성전이라 찾을 수 없음
ObjectProvider, Scope의 프록시 설정을 통해 해결
-> @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)를 통해 설정
-> 적용대상이 인터페이스라면 ScopedProxyMode.INTERFACES선택
-> ObjectProvider는 아래 내용 참고
웹 세션이 생성되고 종료될때까지 유지
웹 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
진짜 객체조회를 꼭 필요한 시점까지 지연하기위한 목적
애노테이션 설정만으로 사용가능
웹스코프 아니어도 사용가능
꼭 필요한 곳에만 최소한으로 사용
-> 이러한 특별한 scope는 무분별하게 남발하면 유지보수 힘들어짐
CGLIB 라이브러리 통해 바이트코드 조작, 원본 클래스를 상속받은 프록시 객체를 생성
-> 클래스 이름 출력하면 ClassName$$EnhancerBySpringCGLIB 이라고 뜸
스프링 컨테이너에 프록시 객체를 등록, 의존관계도 프록시 객체가 주입됨
프록시 객체에는 요청이 들어오면 원본 객체의 빈을 요청하는 위임로직
-> 다형성 통해 클라이언트는 상속받은 프록시 객체를 원본객체처럼 사용
직접 필요한 의존관계를 찾는것
-> 의존관계를 컨테이너에서 자동으로 주입받는것이 아님
매번 새로운 빈을 주입받아 사용해야한다면 DL 사용해야함
싱글톤 빈 내부에서, 어떤 기능 수행할때마다 새로운 프로토 타입 빈을 생성해 사용하려면 DL기능을 써야됨
-> 멤버변수로 자동 주입 받으면, 싱글톤 빈의 초기화가 한번만 이뤄지므로 프로토타입빈의 자동주입도 한번만 이뤄짐
-> 의도와 어긋날 가능성
그렇다고 스프링 컨테이너를 싱글톤 빈의 멤버 변수로 사용하면 안됨
-> 컨테이너에 종속적인 코드: 스프링이 아닌 다른컨테이너 사용시 코드 수정해야됨
-> 단위테스트 어려워짐 : 간략히 테스트 위해 mock만들때 ApplicationContext는 많은 상위인터페이스들의 메서도들을 다 구현해야 하지만, DL클래스들은 비교적 간단히 구현 가능
스프링이 제공하는 DL기능
ObjectFactory에 편의기능을 추가해 ObjectProvider가 만들어짐
ObjectProvider<TargetType> variableName; 으로 선언해 사용
내부적으론 스프링 컨테이너를 통해 해당 빈을 찾음
-> 하지만 기능이 단순해 mock이나 단위테스트 만들기는 훨씬 쉬움 (스프링 컨테이너를 직접사용하는 것에 비해)
getObject()를 통해 빈을 받음
JSR-330 자바 표준에서 제공하는 DL : javax.inject.Provider
-> 스프링 부트 3.0은 jakarta.inject.Provider을 사용
-> 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용 가능
gradle에 해당 라이브러리를 추가해서 사용
기능이 단순해 단위테스트, mock 코드를 만들기 쉬움
get()을 통해 빈을 받음
ObjectProvider는 DL 관련한 편의기능을 많이 제공해 편리함
-> 기본적으로 ObjectProvider 사용
스프링이 아닌 다른 컨테이너에서도 사용할수 있어야만 한다면 JSR-330 Provider 사용
보통 스프링과 자바 표준이 제공하는 기능이 겹치면, 스프링 쪽이 더 편리한 기능 제공함
-> 다른 컨테이너 쓸일 없다면, 스프링 기능 사용