- 구조
MemberService(인터페이스) ←MemberServiceImpl(구현체)MemberRepository(인터페이스) ←MemoryMemberRepository(메모리 저장소)
- 기능
- 회원 가입/조회
- 등급: BASIC, VIP
- 구조
OrderService←OrderServiceImplDiscountPolicy(인터페이스) ←FixDiscountPolicy(고정 할인)
- 흐름
- 주문 생성 → 회원 조회 → 할인 적용 → 결과 반환
- 인터페이스 분리
- 저장소:
MemoryMemberRepository→ 추후 DB 저장소로 교체 가능 - 할인 정책:
FixDiscountPolicy→ 정률 할인 정책으로 확장 가능
- 저장소:
- 테스트
- JUnit으로 회원 가입/주문 생성 테스트
- 예시: VIP 회원의 고정 할인(1000원) 적용 검증
-
DIP 위반: **
OrderServiceImpl**이FixDiscountPolicy구현체에 직접 의존 -
OCP 위반: 할인 정책 변경 시 클라이언트 코드 수정 필요
→ 해결 방안: AppConfig 도입 (관심사 분리)
💡 학습 목표: 인터페이스 설계와 다형성을 활용해 확장성 있는 구조를 이해합니다.
- 인터페이스 분리: 구현체 변경이 용이하도록 설계
- 관심사 분리: 객체 생성/연결 책임과 실행 책임 분리
- 다형성 활용: 확장성 있는 시스템 구축
| 원칙 | 적용 포인트 |
|---|---|
| 단일 책임(SRP) | 객체 생성/연결 책임과 실행 책임 분리 |
| 개방-폐쇄(OCP) | 기능 확장 시 기존 코드 수정 최소화 |
| 의존 역전(DIP) | 추상화에만 의존하는 구조 구현 |
- IoC 컨테이너: 객체 생명주기 관리
- 의존성 주입(DI): 런타임 시 의존 관계 자동 연결
- 설정 클래스: **
@Configuration**으로 애플리케이션 구성 관리
- 변경에 유연한 설계
- 인터페이스 중심 개발
- 구현체 변경이 필요할 때 구성 클래스만 수정
- 테스트 용이성
- 의존성 주입을 통한 모킹(mocking) 용이
- 단위 테스트 작성 편리
- 스프링 컨테이너 활용
- 객체 생성/관리 자동화
- 설정을 통한 유연한 환경 구성
- 객체 지향 설계 원칙의 실제 적용 사례 이해
- 스프링의 핵심 기능이 해결하는 문제 인식
- 유지보수성 높은 코드 작성 능력 향상
- ApplicationContext가 스프링 컨테이너의 핵심 인터페이스
- **
new AnnotationConfigApplicationContext(AppConfig.class)**로 생성 - 구성 정보를 기반으로 빈 등록 및 의존관계 주입 수행
- 기본 조회
ac.getBean("빈이름", 타입)ac.getBean(타입)- ❗ 조회 실패 시
NoSuchBeanDefinitionException발생
- 동일 타입 다중 빈
- 동일 타입이 여러 개일 경우 빈 이름 지정 필요
- **
ac.getBeansOfType()**로 해당 타입 전체 조회
- 상속 관계
- 부모 타입 조회 시 자식 타입 포함
Object타입으로 모든 스프링 빈 조회 가능
| 인터페이스 | 설명 |
|---|---|
| BeanFactory | 빈 관리/조회 기본 기능 (저수준) |
| ApplicationContext | BeanFactory + 부가 기능 (실무 표준) |
- 자바 코드:
@Configuration클래스 (권장) - XML:
GenericXmlApplicationContext(레거시 시스템용) - 모든 설정은 내부적으로 **
BeanDefinition**으로 변환 처리
- 빈 생성 방식, 스코프, 생명주기 메서드 등 메타정보 저장
- 스프링 컨테이너의 추상화 핵심 개념
💡 실무 Tip:
- **
ApplicationContext**를 주로 사용하며, **BeanDefinition**은 거의 직접 다루지 않음- XML보다 자바 기반 설정이 현대적이고 유지보수 용이
- 웹 애플리케이션과 싱글톤
- 싱글톤 패턴
- 싱글톤 컨테이너
- 싱글톤 방식의 주의점
- @Configuration과 싱글톤
- @Configuration과 바이트코드 조작
- 스프링은 기업용 온라인 서비스 지원을 위해 탄생
- 대부분의 스프링 애플리케이션은 웹 기반
- 동시 다수 사용자 요청 처리 필요
- 요청마다 객체 생성 → 심각한 메모리 낭비
- 해결책: 싱글톤 패턴 적용
- 단일 인스턴스 생성 보장
- private 생성자로 외부 접근 차단
- 복잡한 구현 코드
- DIP/OCP 위반 가능성
- 테스트 어려움
- 스프링의 기본 빈 관리 방식
- 싱글톤 패턴 단점 해결
- 무상태(stateless) 설계 필수
- 공유 필드 사용 금지
- 지역변수/파라미터 사용 권장
- CGLIB로 바이트코드 조작
- 싱글톤 보장 및 중복 호출 방지
- @Bean 단독 사용시 문제 발생
- 스프링은 싱글톤 문제점 해결
- @Configuration 필수 사용
- 무상태 설계 원칙 준수
💡 실무 팁:
- ApplicationContext 주력 사용
- 자바 설정 방식 권장
- 스프링 빈을 자동으로 등록하는 컴포넌트 스캔 기능 제공
- 의존관계도 자동으로 주입하는
@Autowired기능 제공 @ComponentScan을 설정 정보에 추가하면 자동 스캔 시작@Configuration도 사실은@Component가 붙어 있어 스캔 대상이 됨- 의존관계 자동 주입 시
@Autowired사용 - 스프링 빈 기본 이름: 클래스명 첫 글자만 소문자
- 빈 이름을 직접 지정하고 싶으면
@Component("빈이름")
- *
basePackages*로 탐색 시작 위치를 지정할 수 있음 - 여러 개 시작 위치 지정 가능
- *
basePackageClasses*로 특정 클래스 기준 지정 가능 - 지정하지 않으면 **
@ComponentScan*이 붙은 설정 정보 클래스의 패키지가 시작 위치 - 권장 방법: 메인 설정 클래스를 프로젝트 루트에 두고
basePackages생략
@Component: 컴포넌트 스캔 대상@Controller: 스프링 MVC 컨트롤러로 인식@Service: 비즈니스 로직 계층으로 인식@Repository: 데이터 접근 계층 + 예외 변환 처리@Configuration: 설정 정보로 인식, 싱글톤 유지 처리
💡 참고: 애노테이션끼리는 상속이 없지만, 스프링이 내부적으로 인식함
includeFilters: 추가로 스캔할 대상을 지정excludeFilters: 제외할 대상을 지정
| 옵션 | 설명 |
|---|---|
| ANNOTATION | 애노테이션을 인식해서 동작 (기본값) |
| ASSIGNABLE_TYPE | 지정 타입과 그 자식 타입까지 포함 |
| ASPECTJ | AspectJ 패턴 사용 |
| REGEX | 정규 표현식 사용 |
| CUSTOM | TypeFilter 인터페이스 직접 구현 |
📌 참고: 보통 @Component면 충분하고, excludeFilters만 가끔 사용
- 컴포넌트 스캔 중 같은 이름의 빈이 등록되면 스프링이 오류 발생
- 발생 예외:
ConflictingBeanDefinitionException
- 수동 등록 빈이 자동 등록 빈을 오버라이딩함
- 예상치 못한 충돌로 디버깅 어려운 버그 발생 가능
- 스프링 부트에서는 수동-자동 충돌 시 오류 발생이 기본
- 생성자 주입
- 수정자 주입 (setter 주입)
- 필드 주입
- 일반 메서드 주입
- 생성자 호출 시점에 1번만 호출되는 것이 보장
- 불변, 필수 의존관계에 적합
- 생성자가 1개만 있으면
@Autowired생략 가능
- 선택, 변경 가능성이 있는 의존관계에 사용
@Autowired(required = false)로 주입 대상이 없어도 동작 가능
- 필드에 바로 주입
- 코드가 간결하지만, 테스트 어려움
- 실무에서는 사용 비추천
- 한번에 여러 필드를 주입 가능
- 잘 사용하지 않음
- 스프링 빈에서만 동작
- 주입할 빈이 없어도 동작 가능하도록 설정
| 방법 | 설명 |
|---|---|
@Autowired(required = false) |
주입할 대상 없으면 메서드 자체 호출 안됨 |
@Nullable |
주입할 대상 없으면 null 주입 |
Optional<> |
주입할 대상 없으면 Optional.empty 주입 |
- 대부분의 의존관계는 변경되면 안 되므로 불변하게 설계해야 함
- 생성자 주입은 컴파일 시점에 오류를 잡을 수 있음
- 필드에
final키워드를 사용할 수 있음
✅ 항상 생성자 주입을 기본으로 사용하고, 옵션이 필요한 경우에만 수정자 주입을 사용하자!
- *
@RequiredArgsConstructor*를 이용하면 생성자 자동 생성 - 코드를 매우 간결하게 유지 가능
- 생성자가 하나뿐이라면
@Autowired생략 가능
build.gradle에 Lombok 의존성 추가- 플러그인 설치 및 Annotation Processing 활성화
@Autowired는 타입으로 먼저 조회- 동일 타입의 빈이 여러 개 있으면
NoUniqueBeanDefinitionException발생
- 타입 매칭 후 필드 이름으로 빈 이름 매칭
- 빈 등록과 주입 시
@Qualifier("이름")지정 - 명확하게 특정 빈을 선택 가능
- 여러 빈이 매칭될 때 우선권 부여
- 특별한 빈만
@Qualifier로 구체 지정
⚡ @Primary는 기본값처럼, @Qualifier는 세밀하게 동작
- 커스텀 애노테이션으로
@Qualifier를 대체할 수 있음 - 타입 안전성을 높이고, 코드 가독성 향상
List<타입>으로 빈 목록 주입 가능Map<String, 타입>으로 빈 이름과 함께 주입 가능- 전략 패턴 등을 쉽게 구현 가능
-
업무 로직 (Controller, Service, Repository 등):
→ 편리한 컴포넌트 스캔 + 자동 주입 사용
-
기술 지원 로직 (DB 연결, AOP 등):
→ 명확한 수동 등록 추천
-
다형성을 적극 활용하는 경우(전략 패턴 등):
→ 수동 등록 또는 특정 패키지로 구분
💡 편리한 자동 기능을 기본으로 사용하고, 예외적인 경우 수동 등록을 고려하자!
-
애플리케이션 시작 시점에 필요한 작업 (예: DB 연결, 네트워크 연결)
-
종료 시점에 자원을 정리하는 작업이 필요
-
스프링은 초기화 콜백, 소멸 전 콜백 기능 제공
-
생명주기 단계:
객체 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸 전 콜백 → 스프링 종료
💡 객체 생성과 초기화는 명확하게 분리하는 것이 유지보수에 좋음
afterPropertiesSet(): 초기화 작업 수행destroy(): 소멸 작업 수행
- 스프링 전용 인터페이스에 의존함
- 메서드 이름 변경 불가
- 외부 라이브러리에는 적용 불가
⛔ 스프링 초창기 방식, 현재는 거의 사용하지 않음
@Bean(initMethod = "init", destroyMethod = "close")처럼 사용
- 메서드 이름 자유롭게 지정 가능
- 스프링에 의존하지 않음
- 외부 라이브러리에도 사용 가능
- 기본값
(inferred)로 되어 있어close,shutdown메서드를 자동 호출 - 추론 원치 않으면
destroyMethod = ""로 명시 가능
@PostConstruct: 의존관계 주입 완료 후 초기화 작업 실행@PreDestroy: 빈이 소멸되기 직전 정리 작업 실행
- 최신 스프링에서 가장 권장하는 방식
- 컴포넌트 스캔과 잘 어울림
- JSR-250 표준, 스프링 외 다른 컨테이너에서도 사용 가능
- 외부 라이브러리에는 적용 불가 → 이 경우
@Bean(initMethod, destroyMethod)사용
| 구분 | 특징 | 장점 | 단점 |
|---|---|---|---|
| 인터페이스 | InitializingBean, DisposableBean | 구조 명확 | 스프링 의존, 이름 고정 |
| 설정 정보 | @Bean(initMethod, destroyMethod) |
이름 자유, 외부 코드 적용 가능 | 설정 위치에서 관리 |
| 애노테이션 | @PostConstruct, @PreDestroy |
간편, 표준, 컴포넌트 스캔 호환 | 외부 코드에는 적용 불가 |
✅ 일반적으로는 @PostConstruct, @PreDestroy 사용!
🔧 외부 라이브러리 초기화/종료는
@Bean(initMethod, destroyMethod)사용!
- 기본적으로 스프링 빈은 싱글톤 스코프로 컨테이너 시작부터 종료까지 유지됨.
- 스코프는 빈이 존재하는 범위를 정의함.
- 주요 스코프:
- 싱글톤: 애플리케이션 전체에서 하나만 존재.
- 프로토타입: 요청할 때마다 새로 생성.
- request: HTTP 요청마다 하나씩 생성.
- session: HTTP 세션마다 하나씩 생성.
- application: 서블릿 컨텍스트와 동일 범위.
- websocket: 웹소켓 연결 동안 유지.
- 매번 새로운 인스턴스가 필요할 때 사용.
- 생성, 의존관계 주입, 초기화까지만 스프링이 관리하고 소멸은 직접 관리해야 함.
@PreDestroy등 소멸 메서드가 호출되지 않음.
- 싱글톤 빈에 프로토타입 빈을 주입하면, 주입 시점에 생성된 인스턴스를 계속 사용하게 되어 의도와 다르게 동작함.
- 사용 시점마다 새로운 인스턴스를 원하면 직접 조회하거나 Provider 사용 필요.
- ApplicationContext를 통해 직접
getBean()호출 (DL 방식) - ObjectProvider 사용 – 스프링이 제공하는 간단한 DI 도구
- JSR-330 Provider 사용 – 자바 표준 인터페이스 (외부 라이브러리 필요)
- 웹 환경에서만 동작함.
- HTTP 요청, 세션 등과 같은 범위에서 빈을 관리함.
- 종료 시점까지 스프링이 빈을 관리하므로
@PreDestroy호출 가능. - 주요 스코프:
- request
- session
- application
- websocket
- 요청마다 UUID를 발급하여 로그를 구분.
- 웹 요청 URL도 함께 로깅하여 요청 추적 가능.
- 로그 클래스를
request스코프로 설정하여 요청당 하나의 인스턴스 유지.
- ObjectProvider 사용: 필요할 때 빈을 조회하여 사용.
- 프록시 모드 사용 (
ScopedProxyMode.TARGET_CLASS):- 가짜 프록시 객체를 미리 주입.
- 요청 시 실제 빈을 찾아서 위임 처리.
- 싱글톤처럼 사용할 수 있으면서 실제 동작은 요청마다 다르게 됨.
| 구분 | 특징 | 장점 | 단점 |
|---|---|---|---|
| 싱글톤 | 컨테이너 전체 생명주기 | 효율적 | 유연성 부족 |
| 프로토타입 | 요청마다 새 인스턴스 | 독립성 | 소멸 관리 필요 |
| request | HTTP 요청마다 생성 | 웹 요청 분리 | 웹 환경에서만 가능 |
| Provider | 사용 시점에 조회 | 유연성 ↑ | 코드 복잡도 ↑ |
| 프록시 | DI 시점에 프록시 주입 | 깔끔한 코드 | 내부 동작 이해 필요 |
✅ 대부분의 상황은 싱글톤으로 해결, 특별한 경우에만 스코프 확장 사용
🔧 프로토타입 사용 시 Provider나 프록시 방식 적극 활용 권장