Solid 원칙SRP(single Response Principle) - 단일 책임 원칙OCP (Open Closed Principle) - 개방-폐쇄 원칙LSP (Liscov Substitution Principle) - 리스코프 치환 원칙ISP (Interface Seperate Principle) - 인터페이스 분리 원칙DIP (Dependency Inversion Principle) - 의존관계 역전 원리스프링 컨테이너와 스프링 빈@Configuration, @Bean스프링 컨테이너의 생성 과정스프링 빈 조회BeanFactory와 ApplicationContext
싱글톤 컨테이너싱글톤 패턴싱글톤 컨테이너컴포넌트 스캔@ComponentScan@Autowired@Filter의존관계 자동 주입옵션 처리생성자 주입을 선택해야 하는 이유롬복(lombok)조회 빈이 2가지 이상해결 방법빈 생명주기 콜백빈 생명주기 콜백 시작빈 스코프빈 스코프란프로토타입 스코프싱글톤 빈과 함께 사용 시 Provider 사용프로토타입 빈을 언제 사용하는가?웹 스코프
Solid 원칙
SRP(single Response Principle) - 단일 책임 원칙
한 클래스는 하나의 책임만 가져야 한다.
OCP (Open Closed Principle) - 개방-폐쇄 원칙
:소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.
LSP (Liscov Substitution Principle) - 리스코프 치환 원칙
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
- ex) 자동차 인터페이스에서 엑셀은 앞으로 가는 기능이므로, 뒤로 가게 구현시 LSP의 위반이다.
ISP (Interface Seperate Principle) - 인터페이스 분리 원칙
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
DIP (Dependency Inversion Principle) - 의존관계 역전 원리
프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.
- ex) 자동차에 의존해야지, 아반떼에 의존하면 안됨.
⇒ 다형성 만으로는 OCP, DIP를 지킬 수 없다. 뭔가 더 필요하다.
⇒ 스프링은 DI 등을 통해 OCP, DIP를 가능하게 할 수 있다.
스프링 컨테이너와 스프링 빈
@Configuration, @Bean
- 스프링 컨테이너는 @Configuration이 붙은 class를 설정 정보로 사용한다.
- @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. (스프링 빈)
- 스프링 빈은 @Bean이 붙은 메서드명을 스프링 빈의 이름으로 사용한다.
- 이렇게 등록을 하면, AppConfig를 사용해 직접 조회하는게 아닌, 스프링 컨테이너를 통해 필요한 객체를 찾아 사용할 수 있다. → applicationContext.getBean() 매서드 사용
스프링 컨테이너의 생성 과정
- 스프링 컨테이너 생성
//스프링 컨테이너 생성
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
- 스프링 빈 등록
- 파라미터로 들어온 설정 클래스 정보를 사용해 스프링 빈을 등록한다.
- 스프링 빈 의존관계 설정 준비, 완료
스프링 빈 조회
ac.getBean(타입)
ac.getBean(빈이름, 타입) // 같은 타입이 둘 이상 있을 시, 빈 이름 지정
ac.getBeansOfType(타입) // 특정 타입의 빈들을 모두 조회
- 부모 타입으로 조회하면, 자식 타입도 함께 조회된다.
BeanFactory와 ApplicationContext

- BeanFactory
- 스프링 컨테이너의 최상위 인터페이스
- 스프링 빈을 관리하고 조회하는 역할 담당
- getBean()을 제공
- ApplicationContext
- BeanFactory 기능을 모두 상속받아서 제공
- BeanFactory의 기능 뿐 아니라, 다음 기능들까지 제공한다.
- 메세지 소스를 활용한 국제화 기능(ex: 한국어권 → 한국어 제공)
- 환경 변수
- 애플리케이션 이벤트
- 리소스 조회
⇒ ApplicationContext는 BeanFacotry의 기능에 편리한 부과 기능을 제공한다
→ 그냥 ApplicationContext 쓰면 됨.
싱글톤 컨테이너
싱글톤 패턴
- 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
- 이미 만들어진 객체를 공유해 효율적 사용이 가능하다는 장점이 있다. 하지만 다음 문제점들이 있다.
- 문제점
- 싱글톤 패턴 구현 코드가 많이 들어감.
- 클라이언트가 구체 클래스에 의존하게 된다. →. DIP 위반
- DIP 위반으로 OCP 원칙도 위반할 가능성이 높다.
- 테스트 하기 어렵다.
- 내부 속성 변경, 초기화가 어렵다, 유연성이 떨어진다.
싱글톤 컨테이너
- 스프링의 싱글톤 컨테이너를 사용하면, 싱글톤 패턴의 단점은 저절로 해결해 주고, 장점만 취할 수있다.
- 스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
- @Configuration 태그를 달면, CGLIB를 이용해 바이트 코드를 조작한 클래스를 만들고, 그 클래스를 빈으로 등록한다.
- @Configuration이 없으면, 객체들이 싱글톤으로 관리되지 않는다.
- 싱글톤 방식의 주의점
- 객체 상태를 유지하는 설계를 하면 안된다.
- 특정 클라이언트에 의존적인 필드가 있으면 안된다,
- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
- 가급적 읽기만 가능해야 한다,
@Configuration 태그를 달면, 스프링 빈이 이미 스프링 컨테이너에 등록되어 있다면 해당 컨테이너를 찾아서 반환하기 때문에, 싱글톤이 보장된다.
컴포넌트 스캔
@ComponentScan
- 등록해야 할 빈이 많아지면, 일일이 등록하기 귀찮고, 설정 정보가 커지고, 누락 등의 문제가 발생한다.
- 그래서 스프링은 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
- @Autowired이라는 의존관계 자동 주입 기능도 제공한다.
- 사용법
- 다음과 같이 @ComponentScan() 애노테이션을 붙여준다. 이제, @Component가 붙은 클래스들은 모두 스프링 빈으로 등록된다,
@Configuration
@ComponentScan()
public class AutoAppConfig {
}
}
@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;
}
}
- @ComponentScan은 @Component 뿐만 아니라, 다음 내용들도 스캔에 포함한다.
- @Controller : 스프링 MVC 컨트롤러에서 사용
- @Service : 스프링 비즈니스 로직에서 사용
- @Repository : 스프링 데이터 접근 계층에서 사용
- @Configuration : 스프링 설정 정보에서 사용
@Autowired
- (주로)생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아 주입한다.
- 생성자에 파라미터가 많아도, 다 자동으로 찾아서 주입한다.
@Filter
- includeFilters: 컴포넌트 스캔 대상을 추가로 지정한다.
- excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.
의존관계 자동 주입
의존관계 주입에는 크게 4가지 방법이 있다.
- 생성자 주입
- 수정자 주입(setter)
- 필드 주입
- 일반 메서드 주입
→ 이 중 대부분 생성자 주입 방법을 사용한다.
생성자 주입
- 생성자를 통해서 의존 관계를 주입한다.
- 생성자 호출 시점에 딱 1번만 호출된다.
- 불변, 필수 의존관계에 사용된다.
@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;
}
}
- 생성자가 1개만 있으면 @Autowired를 생략할 수 있다.
옵션 처리
- 주입 할 스트링 빈이 없어도 동작해야 할 때 required 옵션으로 처리할 수 있다. (default 값은 true임)
//호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
- @Autowired(required = false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않는다.
생성자 주입을 선택해야 하는 이유
- 대부분의 의존관계는 한번 일어나면 애플리케이션 종료시점까지 변경할 일이 없다.(오히려 불변해야 한다)
- 누군가의 실수로 변경될 수 있다는 리스크가 있다.
- 생성자 주입을 사용하면, 필드에
final
키워드를 사용할 수 있다. - 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
- 생성자 주입 외 방식들은 모두 생성자 이후에 호출되므로, final 키워드를 사용할 수 없다.
롬복(lombok)
- Lombok은 model 클래스나 Entity 같은 도메인 클래스 등에 반복되는 getter, setter, toString 등의 메소드를 자동으로 만들어주는 기능을 하는 라이브러리이다.
@RequiredArgsConstructor
기능을 사용하면, final이 붙은 필드를 모아 생성자를 자동으로 만들어준다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
- 눈으로 보이지는 않지만, 하단의 constructor 매서드를 호출 가능하고, 컴파일 시점 시 생성자 코드를 자동으로 생성해준다.
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
조회 빈이 2가지 이상
- @Autowired는 타입으로 조회된다.
@Autowired
private DiscountPolicy discountPolicy
- 다음과 같은 DiscountPolicy 타입을 Autowired 할 경우
ac.getBean(DiscountPolicy.class)
- 으로 조회하기 때문에,
DiscountPolicy
타입의 빈이 2개 이상일 때 문제가 발생한다.
해결 방법
- @Autowired 필드 명 매칭
- 필드 명을 필드 명, 파라미터 명으로 빈 이름을 매칭한다.
- @Qualifier 사용
- Qualifer는 추가 구문자를 붙여주는 방법이다.
// 주입 시
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
// 생성자 주입 예시
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
// 이 경우 mainDiscountPolicy를 매칭한다.
- @Primary 사용
- @Primary가 할당된 빈이 우선 순위를 가진다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
// 이 경우 RateDiscountPolicy가 우선권을 가진다.
빈 생명주기 콜백
빈 생명주기 콜백 시작
- 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면 객체의 초기화/종료 작업이 필요함
- 스프링 빈은 다음과 같은 라이프사이클을 가진다.
- 객체 생성 → 의존관계 주입
- 스프링 빈은 의존관계 주입이 완료되면 초기화 시점을 알려주는 기능을 제공한다.
- 또, 컨테이너 종료 직전 소멸 콜백도 제공한다.
- 생명주기 콜백을 사용한다고 하면
- 스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸전 콜백 → 스프링 종료
- 스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.
- 인터페이스(InitializingBean, DisposableBean)
- 설정 정보에 초기화 메서드, 종료 메서드 지정
- @PostConstruct, @PreDestroy 애노테이션 지원 → 최신 스프링에서 권장하는 방법
@PostConstruct
public void init() {
System.out.println("NetworkClient.init"); connect();
call("초기화 연결 메시지");
}
@PreDestroy
public void close() {
System.out.println("NetworkClient.close");
disConnect();
}
빈 스코프
빈 스코프란
- 빈이 존재할 수 있는 범위를 뜻한다.
- 스프링은 다양한 스코프를 지원한다.
- 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작부토 종료까지 유지되는 가장 넓은 스코프
- 프로토타입 : 프로토타입 빈의 생성과 의존관계 주입까지만 관여하는 매우 짧은 범위의 스코프
- 웹 관련 스코프
- request: 웹 요청이 들어오고 나갈때까지 유지되는 스코프
- session: 웹 세션이 생성되고 종료될때까지 유지되는 스코프
- application: 웹의 서블릿 컨텍스트와 같은 범위로 유지
프로토타입 스코프
- 싱글톤 스코프 : 항상 같은 인스턴스의 스프링 빈을 반환
- 프로토타입 스코프 : 항상 새로운 인스턴스를 생성해서 반환,
@Scope(”prototype”)
으로 선언한다 - 스프링 컨테이너는 프로토타입 빈 생성 → 의존관계 주입 → 초기화 까지의 과정만 처리한다.
- 따라서
@PreDestroy
와 같은 종료 메서드는 실행되지 않는다. 종료 메서드에 대한 호출은 클라이언트가 해야 한다.
- 프로토타입 빈 요청 시 과정
- 프로토타입 스코프 빈을 스프링 컨테이너에 요청
- 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입.
- 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환
- 이후 컨테이너에 같은 요청이 오면, 항상 새로운 프로토타입 빈을 생성하여 반환
싱글톤 빈과 함께 사용 시 Provider 사용
- Provider를 사용해야 싱글톤 빈과 프로토타입 빈을 함께 사용 시 항상 새로운 프로토타입 빈을 생성할 수 있다.
- ObjectProvier, javax.inject.Provider 가지 방법이 있다.
- ObjectProvider : 지정한 빈을 컨테이너에게 대신 찾아주는 DL(의존관계 조회) 서비스를 제공
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
prototypeBeanProvider.getObject()
을 통해 항상 새로운 프로토타입 빈이 생성된다. 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아 반환한다.
- jakarta.inject.Provider : provider.get()을 통해 항상 새로운 프로토타입 빈을 생성한다.
import (
"jakarta.inject.Provider"
)
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
- 자바 표준으로 기능이 단순하여 단위 테스트 시 용이하다.
프로토타입 빈을 언제 사용하는가?
- 의존관계 주입이 완료된 새로운 객체가 필요하면 사용한다.
- 하지만 프로토타입 빈을 직접적으로 사용하는 경우는 매우 드물다.
웹 스코프
- 웹 환경에서만 동작한다.
- 프로토타입 스코프와 다르게, 스프링이 해당 스코프의 종료시점까지 관리한다. → 종료 메서드가 호출된다.
- 다양한 종류가 있다. → request, session, application, websocket
Share article