객체 지향 설계의 5가지 원칙 (SOLID)
- SRP: 단일 책임 원칙(single responsibility principle)
- OCP: 개방-폐쇄 원칙 (Open/closed principle)
- 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 함
- LSP: 리스코프 치환 원칙 (Liskov substitution principle)
- 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위 한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면, 이 원칙이 필요
- ISP: 인터페이스 분리 원칙 (Interface segregation principle)
- 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 나음
- DIP: 의존관계 역전 원칙 (Dependency inversion principle)
- 추상화에 의존해야지, 구체화에 의존하면 안됨
- 구현 클래스에 의존하지 말고, 인터페이스에 의존 (역할(Role)에 의존하게 해야 함)
스프링 컨테이너
- ApplicationContext를 스프링 컨테이너라 함
- @Configuration이 붙은 AppConfig를 구성 정보로 사용, @Bean이라 적힌 메서드를 호출해서 반환된 객체를 스프링 컨테이너에 등록 → 스프링 컨테이너에 등록된 객체를 스프링 빈
싱글톤 컨테이너
- 싱글톤 패턴
- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
- private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 함
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리
- 무상태(stateless)로 설계해야 함
- 특정 클라이언트에 의존적인 필드가 있으면 안됨
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안됨
- 가급적 읽기만 가능해야 함
- 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 함
컴포넌트 스캔
- 컴포넌트 스캔을 사용하려면 먼저 @ComponentScan을 설정 정보에 붙여주면 됨
- 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component 애노테이션을 붙여줘야 함
- @ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록
- 컴포넌트 스캔 기본 대상
- @Component : 컴포넌트 스캔에서 사용
- @Controlller : 스프링 MVC 컨트롤러에서 사용
- @Service : 스프링 비즈니스 로직에서 사용
- @Repository : 스프링 데이터 접근 계층에서 사용
- @Configuration : 스프링 설정 정보에서 사용
- 생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입
의존관계 주입 방법
- 생성자 주입
- 수정자 주입 (setter 주입)
- 필드 주입
- 일반 메서드 주입
대표적 : 생성자 주입
- 생성자를 통해서 의존 관계를 주입 받는 방법
- 생성자 호출시점에 딱 1번만 호출되는 것이 보장
- 불변, 필수 의존관계에 사용
- 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입 됨
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
- 생성자 주입을 권장하는 이유
- 불변
- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없음
- 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안됨 (불변해야 함)
- 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 하는데, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아님
- 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없음 → 불변하게 설계 가능
- 누락
- 생성자 주입을 사용하면 다음처럼 주입 데이터를 누락 했을 때 컴파일 오류가 발생
- IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있음
- final 키워드
- 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있음
- 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아줌
- 불변
lombok
- 롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어줌
- 최근에는 생성자를 딱 1개 두고, @Autowired 를 생략하는 방법을 주로 사용
- Lombok 라이브러리의 @RequiredArgsConstructor 함께 사용하면 기능은 다 제공하면서, 코드는 깔끔하게 사용할 수 있음
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
조회 빈이 두 개 이상일 때
1. @Autowired 필드 명 매칭
- 기존 코드
@Autowired
private DiscountPolicy discountPolicy
- 필드 명을 빈 이름으로 변경
@Autowired
private DiscountPolicy rateDiscountPolicy
2. @Qualifier 사용
- 빈 등록시 @Qualifier를 붙여 줌
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
- 주입시에 @Qualifier를 붙여주고 등록한 이름을 적어줌
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Qualifier는 @Qualifier를 찾는 용도로만 사용하는게 명확하고 좋음
3. @Primary 사용
- @Autowired 시에 여러 빈이 매칭되면 @Primary가 우선권을 가짐
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
애노테이션 직접 만들기
- @Qualifier("mainDiscountPolicy") 이렇게 문자를 적으면 컴파일시 타입 체크가 안됨
- 다음과 같은 애노테이션을 만들어서 문제를 해결할 수 있음
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}
- 애노테이션에는 상속이라는 개념이 없음
- 이렇게 여러 애노테이션을 모아서 사용하는 기능은 스프링이 지원해주는 기능임
자동, 수동의 올바른 실무 운영 기준
- 편리한 자동 기능을 기본으로 사용
- 업무 로직 빈: 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리등이 모두 업무 로직, 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경
- 기술 지원 빈: 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용됨, 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들
- 애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 설정 정보에 바로 나타나게 하는 것이 유지보수 하기 좋음
- 수동이 아닌 자동으로 등록할 경우 특정 패키지에 같이 묶어두기
빈 생명주기 콜백
- 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요
- 스프링 빈의 이벤트 라이프사이클
- 스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸전 콜백 → 스프링 종료
- 스프링의 빈 생명주기 콜백을 지원하는 3가지 방법
- 인터페이스(InitializingBean, DisposableBean)
- 코드가 스프링 전용 인터페이스에 의존
- 초기화, 소멸 메서드의 이름 변경 불가
- 코드를 고칠 수 없는 외부 라이브러리에 적용 불가
- 거의 사용 X
- 설정 정보에 초기화 메서드, 종료 메서드 지정
- 설정 정보에 @Bean(initMethod = "init", destroyMethod = "close") 처럼 초기화, 소멸 메서드를 지정
- 메서드 이름 자유롭게 지정 가능
- 스프링 빈이 스프링 코드에 의존하지 않음
- 설정 정보를 사용하여 외부 라이브러리에도 초기화, 종료 메서드 적용 가능
- @PostConstruct, @PreDestroy 애노테이션 지원
- 최신 스프링에서 가장 권장하는 방법
- 애노테이션 하나만 붙이면 되므로 편리
- 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준 → 스프링이 아닌 다른 컨테이너에서도 동작
- 컴포넌트 스캔과 잘 어울림
- 외부 라이브러리에는 적용하지 못함 → 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능을 사용
- 인터페이스(InitializingBean, DisposableBean)
빈 스코프
- 싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
- 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
- 웹 관련 스코프
- request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프
- session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
- application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
프로토타입 스코프
- 프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환
- 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리
- 클라이언트에 빈을 반환하고, 이후 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않음
- 종료 메서드가 호출되지 않음. 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 하므로 종료 메서드에 대한 호출도 클라이언트가 직접 해야함
1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청
2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입
3. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환
4. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환
프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결
- ObjectFactory, ObjectProvider
- prototypeBeanProvider.getObject() 을 통해서 항상 새로운 프로토타입 빈이 생성, 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환 (DL)
- ObjectFactory: 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존
- ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리등 편의 기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존
- JSR-330 Provider 사용
- provider.get() 을 통해서 항상 새로운 프로토타입 빈이 생성, 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환 (DL)
- 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있음
웹 스코프
- 스프링이 해당 스코프의 종료시점까지 관리, 종료 메서드가 호출됨
- 종류
- request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리됨
- session: HTTP Session과 동일한 생명주기를 가지는 스코프
- application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
- websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
참고 강의:
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
'Spring Boot' 카테고리의 다른 글
Spring Boot - MVC 기능 (0) | 2023.06.30 |
---|---|
Spring Boot - MVC 구조 (0) | 2023.06.25 |
Spring Boot, 웹 MVC, DB 접근 기술 (1) | 2023.05.13 |
Spring Boot 예외 처리 (0) | 2023.01.22 |
Spring Boot (0) | 2022.12.27 |