인프런 스프링 핵심원리 강의를 듣고 중요한 개념 요약 정리
프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것
메타데이터 AppConfig(xml, java code)를 통해 객체(빈)를 생성하고 관리하면서 의존관계를 연결해 주는 것
//스프링 컨테이너 생성
//AnnotationConfigApplicationContext는 ApplicationContext의 구현체이다.
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
스프링 컨테이너는 @Configuration 이 붙은 AppConfig를 설정(구성) 정보로 사용한다. 여기서 @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.
이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤(1개만 생성)으로 관리한다. 이를 통해 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유 해서 효율적으로 재사용할 수 있다.
싱글톤 패턴 단점 - 참고
- 싱글톤을 구현하는 코드가 많이 들어간다. (thread-safe 고려)
- 클래스 내부에서 객체를 직접 생성하기 때문에 객체 지향 설계원칙인 OCP, DIP를 위배한다.
- 테스트가 어렵다.
자바 코드의 @Bean이나 XML의 <bean'> 등을 통해서 직접 스프링 빈을 등록할 수 있었다. 그러나 등록할 빈이 수십, 수백개가 된다면?
스프링은 자동으로 빈을 등록하는 기능인 컴포넌트 스캔과 @Autowired라는 편리한 기능을 제공한다.
@ComponentScan
@Autowired
크게 4가지 방법
1. 생성자 주입 (추천)
2. 수정자 주입(setter 주입)
3. 필드 주입
4. 일반 메서드 주입
결론 생성자 주입을 사용해라!
- 불변
- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
- 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계 할 수 있다.
- 누락
- 생성자 주입을 사용하면 다음처럼 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다. 그리고 IDE에서 바로 어떤 값을 필수로 주입해야 하는지 알 수 있다.
- 생성자 주입만 필드에 final 키워드를 사용할 수 있다. 컴파일 오류를 사전에 방지할 수 있다.
스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다.
스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원
1. 인터페이스(InitializingBean, DisposableBean)
2. 설정 정보에 초기화 메서드, 종료 메서드 지정
3. @PostConstruct, @PreDestory 애노테이션 지원(이 방법 추천)
빈이 존재할 수 있는 범위
@Scope("prototype")
@Component
public class bean {
...
}
스프링 빈 스코프는 싱글톤, 프로토타입 두 가지가 있다.
스프링에서는 일반적으로 싱글톤 빈을 사용!
그래서 싱글톤 빈이 프로토타입 빈을 사용하게 된다.
싱글톤빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제다.
프로토타입 빈을 주입 시점에만 새로 생성하는 것이 아니라 사용할 때 마다 새로 생성하려면?
싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
//getObject() 를 호출하면 내부에서는 스프링 컨테이너를 통해
//해당 빈을 찾아서 반환한다. (Dependency Lookup)
PrototypeBean prototypeBean =
prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
'javax.inject:javax.inject:1' gradle 추가 필수
자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
웹 환경에서만 동작하는 스코프로 스프링이 종료시점까지 관리한다.
@Scope(value = "request") 사용
request 스코프 빈을 생성하면 requestURL과 같은 웹과 관련된 정보를 웹과 관련없는 서비스 계층에도 깔끔하게 넘겨줄 수 있다. 참고로 웹과 관련된 부분은 컨트롤러까지만 사용해야 한다. 서비스 계층은 웹 기술에 종속되어서는 안된다.
request 스코프 빈은 애플리케이션이 실행될 때 생성해서 주입하는 싱글톤 빈과 달리 실제 고객의 요청이 와야 생성할 수 있다. 따라서 위에서 언급한 Provider 또는 프록시모드 속성을 사용하면 된다.
ObjectProvider.getObject()를 호출하는 시점까지 request 스코프 빈의 생성을 지연할 수 있다. 호출 시점에는 당연히 HTTP 요청이 진행중으로 정상 작동한다. 또한 여러 곳에서 빈을 호출해도 동일한 빈이 반환된다.
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
request 스코프 빈의 가짜 프록시 클래스를 만들어서 HTTP 요청과 상관없이 가짜 프록시 객체를 미리 주입해 둘 수 있다.
동작 원리
1. 해당 속성이 정의되어 있으면 스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서 request 스코프 빈을 상속받은 가짜 프록시 객체를 생성한다.
2. 스프링컨테이너에 진짜 객체 대신 가짜 프록시 객체를 등록하고 의존관계 주입도 이 가짜 프록시 객체를 주입한다. 싱글톤처럼 동작한다.
3. 가짜 프록시 객체에는 실제 요청이 오면 진짜 객체를 요청하는 위임로직이 들어있어서 진짜 객체를 호출한다.(다형성)
따라서 프록시 객체를 사용하면 싱글톤 빈을 사용하듯이 request 스코프 빈을 사용할 수 있다.
Provider와 프록시 객체 모두 진짜 객체를 실제 사용하는 시점까지 지연처리 하는 것이 핵심 아이디어이다.