프로토 타입과 스코프
기본적으로 스프링의 빈은 싱글톤으로 만들어진다.
싱글톤-> Application Context 마다 Bean의 오브젝트가 한 개만 만들어진다.
사용자의 요청이 있을 때마다 매번 애플리케이션 로직을 담은 오브젝트를 만드는 건 비효율적이기 때문이다.
하지만, 하나의 빈 오브젝트에 동시에 여러 스레드가 접근하기 때문에 상태값을 인스턴스 변수에 저장해두고 사용할 수는 없다.
그래서 보통
싱글톤의 필드에는 의존관계에 있는 빈에 대한 레퍼런스 or 읽기전용 값만 저장해두고
오브젝트의 변하는 상태를 저장하는 인스턴스 변수는 두지 않는다.
애플리케이션 로직((ex) 사용자 요청 저장해서 전달하는 폼 지원 오브젝트, DB나 비즈니스 로직에 의해 새로 만들어지는 DTO]
을 담은 오브젝트는 싱글톤 빈으로 만들면 충분하다.
근데, Bean 당 하나의 오브젝트만을 만드는 싱글톤 방식 대신
하나의 빈 설정으로 여러 개의 오브젝트를 만들어야 하는 경우도 생긴다.
싱글톤이 아닌 빈은
1. 프로토타입 빈
2. 스코프 빈
이다.
싱글톤, 프로토타입도 각각 스코프의 한 종류이지만,
이 두가지는 나머지 스코프들과는 성격이 크게 달라 따로 구분해 내어 생각하는 것이 좋다.
즉, 싱글톤, 프로토타입 그리고 나머지 스코프 빈들이라고 생각하는 것이 좋다.
🔬스코프
- "존재할 수 있는 범위"
- 빈의 스코프는 빈 오브젝트가 만들어져 존재할 수 있는 범위를 뜻함.
- 빈 오브젝트 생명주기는 스프링 컨테이너가 관리하기에 대부분 정해진 범위(스코프)의 끝까지 존재한다.
빈(Bean) : 스프링이 관리하는 오브젝트.
생성, 다른 빈에 대한 의존관계 주입, 초기화(메소드 호출), DI/DL을 통한 사용, 제거까지 모든 오브젝트의 생명주기를 컨테이너가 관리한다.
빈에 대한 정보, 오브젝트에 대한 레퍼런스를 계속 컨테이너가 갖고 있어서 필요할 때마다 요청해서 빈 오브젝트를 얻을 수 있다.
싱글톤 스코프
: Application Context 마다 Bean의 오브젝트가 한 개만 만들어진다.
하나의 빈을 여러 개의 빈에서 DI / DL하더라도 매번 동일한 오브젝트가 주입 된다.
IoC의 컨테이너
스프링 프레임워크는 객체에 대한 생성 / 생명주기를 관리할 수 있는 기능을 제공한다.
IoC 컨테이너가 이 기능을 담당하고 있는데,
기존에는 다음과 같은 순서로 객체가 생성되고 실행되었다.
- 객체 생성
- 의존성 객체 생성 - 클래스 내부에서 생성
- 의존성 객체 메서드 호출
하지만, 스프링에서는 다음과 같은 순서로 객체가 생성되고 실행된다.
- 객체 생성
- 의존성 객체 주입 - 제어권이 스프링에게 위임됨. 스프링 컨테이너에서 만들어 놓은 객체를 주입받음
- 의존성 객체 메서드 호출
즉, IoC의 기본 개념은
애플리케이션을 구성하는 핵심 오브젝트를 코드가 아니라 컨테이너가 관리한다는 것
이를 DL(Dependency Lookup)과 DI(Dependency Injection) 을 통해 이루는데,
DL(Dependency Lookup) 이란?
- 컨테이너에 직접 요청 .getbean()등의 방법으로 의존객체를 조회한다.
DI(Dependency Injection)이란?
- "의존성 주입"이다. 각 클래스 간의 의존성을 자신이 아닌 외부(컨테이너) 에서 주입 받는다.
- 내부에서 만든 변수를 외부에서 넣어주게 하면 된다. (@Autowired)
프로토 타입 스코프는
컨테이너에게 빈을 요청할 때마다 매번 새로운 오브젝트를 생성한다.
그리고, IoC의 기본 원칙을 따르지 않는다.
- 요청이 있을때마다 컨테이너가 생성/초기화/DI는 하는데, 빈을 제공후에는 컨테이너가 빈 오브젝트를 관리하지 않음.
- DL을 통해 가져간 코드나, DI로 주입 받은 다른 빈이 프로토 타입 빈을 관리하게 됨.
- 따라서, 빈을 주입받은 오브젝트에 종속적이게 된다 => 스코프가 주입받은 빈을 따라간다.
프로토타입 스코프가 왜 필요할까요?
사실, 대부분의 애플리케이션 로직은 싱글톤 빈으로 만들어두고 사용자별로 상태를 따로 갖지 않는게 일반적이다.
서버가 요청에 따라 독립적으로 오브젝트를 생성해서 상태를 저장해둬야 하는 경우는
DTO를 new 키워드로 생성하고 파라미터로 전달해서 사용하면 된다.
🤔그럼 왜?🤔
코드에서 new로 오브젝트 생성하는 것을 대신하기 위함.
사용자의 요청별로 독립적인 정보나 작업 상태를 저장해둘 오브젝트를 만들 필요가 있을때!
+ DI를 통해 다른 빈을 사용할 수 있어야 한다면!
사용자의 요청에 따라 매번 독립적인 오브젝트를 만들어야 하는 경우 +
매번 새롭게 만들어지는 오브젝트가 컨테이너 내의 빈을 사용해야 하는 경우
물론 new를 사용한 키워드, 팩토리를 사용해 코드 안에서 직접 오브젝트를 만들 수 있다
하지만, 드물긴 해도 컨테이너가 오브젝트를 만들고 초기화해줘야 하는 경우가 존재한다.(DI때문에!)
프로토 타입 빈은 오브젝트의 생성과 DI 작업까지 마친 후에 컨테이너가 돌려준다.
매번 새로운 오브젝트가 필요하면서 DI를 통해 다른 빈을 사용할 수 있어야 한다면!!!!!!
프로토타입 빈이 가장 좋다.
new를 사용한 키워드, 팩토리를 사용해 코드 안에서 직접 오브젝트를 만드는 방법보다,
조금 더 오브젝트 중심적이고 유연한 확장을 고려한다면 프로토 타입 빈을 이용하는 편이 좋다.
프로토 타입과 DI/DL - 무엇을 사용하는 것이 좋을까?
결론부터 말하자면, DL을 사용하는 것이 좋다.
웹 컨트롤러는 싱글톤.
따라서, 단 한 번만 만들어진다.
DI작업은 빈 오브젝트가 처음 만들어질 때 단 한 번만 진행된다.
@Component
@Scope("prototype")
public class ServiceRequest{
...
}
.
@Autowired ApplicationContext context; // 타입에 의한 DI를 이용해 애플리케이션 컨텍스트를 받아둔다
public void serviceRequestFormSubmit(HttpServletRequest request) {
ServiceRequest serviceReqest = this.context.getBean(ServiceRequest.class);
serviceRequest.setCustomerByCustomerNo(reqeust.getParameter("custno"));
...
Request가 일어나는 빈을 프로토타입으로 만들더라도
컨트롤러에 DI하기위해 컨테이너에 요청할 때
딱 한번만 오브젝트가 생성되고 더이상 새로운 오브젝트는 만들어지지 않는다.
-> 여러 사용자가 동시에 요청을 보내면 오브젝트 하나가 공유되어 서로 데이터를 덮어써 버리는 문제가 발생한다.
프로토 타입 빈은 DI될 대상이 여러 군데라면 각기 다른 오브젝트가 생성된다.
하지만, 같은 컨트롤러에서 매번 요청이 있을 때마다 새롭게 오브젝트가 만들어져야 하는 경우에는 적합하지 않다.
new 키워드를 대신하려고 프로토타입을 사용한다면, DI는 프로토타입을 사용하기에 적합한 방법은 아니다.
코드 내에서 필요할 때마다 컨테이너에 요청해서 새로운 오브젝트를 만들어야 한다. 즉, DL 방식을 사용해야 한다.
프로토타입 빈의 DL전략
스프링은 DL 방식을 코드에서 사용해야 할 경우를 위해 ApplicationContext를 이용하는 것 외에도 다양한 방법을 제공한다.
- ApplicationContext, BeanFactory : getBean() 메소드를 호출해서 빈을 가져오는 방법. 간단하지만 스프링 API가 등장한다는 단점이 있음
- ObjectFactory, ObjectFactoryCreatingFactoryBean : ApplicationContext를 ID 받아서 getBean()을 호출해 원하는 프로토타입 빈을 가져오는 방식으로 동작하는 팩토리를 하나 만들어서 빈으로 등록해두고, 이 팩토리를 DI 받아서 필요할 때 getObject()와 같은 메소드를 호출
- 직접 인터페이스를 정의하고, 해당 인터페이스의 구현체를 구현하는 방법
- 스프링이 제공하는 ObjectFactory 인터페이스와 ObjectFactory 인터페이스를 구현한 ObjectFactoryCreatingFactoryBean를 사용하는 방법.
ObjectFactoryCreatingFactoryBean는 FactoryBean이기 때문에 실제 빈의 오브젝트는 ObjectFactory 타입이 된다. - ObjectFactory는 DL을 이용해 빈을 가져와야 하는 모든 경우에 적용할 수 있다.
- ServiceLocationFactoryBean : ObjectFactory처럼 스프링이 미리 정의해둔 인터페이스를 사용하지 않아도된다. 대신 직접 정의한 팩토리 인터페이스를 이용해서 ObjectFactory 처럼 사용할 수 있다.
- 메소드 주입 : 메소드를 통해 DI 받는게 아니라, 메소드 코드 자체를 주입하는 것을 말한다. 메소드 주입은 일정한 규칙을 따르는 추상 메소드를 작성해두면 ApplicationContext와 getBean() 메소드를 사용해서 새로운 프로토타입 빈을 가져오는 기능을 담당하는 메소드를 런타임시에 추가해주는 기술이다.
- Provider<T> : JSR-330에 추가된 표준 인터페이스이다. ObjectFactory와 거의 유사하다. 하지만 ObjectFactoryCreatingFactoryBean을 이용해 빈을 등록해주지 않아도 되기 때문에 사용이 편리하다.
위의 내용은 프로토타입 외에도 Dl 방식의 코드 내에서 필요한 경우라면 언제든 사용 가능하다.
책에서는 ApplicationContext를 직접 사용하는 것은 피하고 Provider가 가장 깔끔한 선택이라고 한다.
2. 스코프
스프링은 싱글톤, 프로토타입 외에 요청, 세션, 글로벌세션, 애플리케이션이라는 4가지 스코프를 기본적으로 제공한다.
이 스코프는 웹 환경에서만 의미가 있다.
4가지 스코프 중에서 application을 제외한 나머지 세가지 스코프는 싱글톤과 다르게 독립적인 상태를 저장해두고 사용하는데 필요하다.
상태를 저장해둘 수 있는 이유는 사용자마다 빈이 만들어지는 덕분이다.
요청 스코프
- 하나의 웹 요청 안에서 만들어지고 해당 요청이 끝날때 제거된다.
- 각 요청별로 독립적인 빈이 만들어지기 때문에 빈 오브젝트에 상태값을 저장해도 된다.
- 요청 스코프 빈의 주요 용도는 애플리케이션 코드에서 생성한 정보를 프레임워크 레벨의 서비스나 인터셉터 등에 전달하는 것이다.
세션 스코프
- HTTP 세션과 같은 존재 범위를 갖는 빈으로 만들어주는 스코프다.
- HTTP 세션은 사용자별로 만들어지고 브라우저를 닫거나 세션 타임이 종료될 때까지 유지되기 때문에 로그인 정보나 사용자별 선택옵션 등을 저장해두기에 유용하다. 하지만 세션을 직접 이용하는 건 매우 번거롭고, 웹 환경 정보에 접근할 수 있는 계층에서만 가능한 작업이다.
- 세션 스코프를 이용하면 HTTP 세션에 저장되는 정보를 모든 계층에서 안전하게 이용할 수 있다.
애플리케이션 스코프
- 싱글톤 스코프와 비슷한 존재 범위를 갖는다.
- 웹 애플리케이션과 애플리케이션 컨텍스트의 존재 범위가 다른 경우가 있는 경우 사용한다.
스코프 빈의 사용 방법
- 하나 이상의 오브젝트가 만들어져야 하기 때문에 싱글톤에 DI 해주는 방법으론 사용할 수 없다.
- 스코프 빈은 프로토타입 빈과 마찬가지로 Provider나 ObjectFactoryt 같은 DL 방식으로 사용해야 한다.
- 스코프 빈은 싱글톤에서 일반적인 방법으로 DI 하는것은 불가능하다. 대신 직접 스코프 빈을 DI하지 않고 스코프 빈에 대한 프록시를 DI해주는 방식이 있다. 그러면 마치 평범한 싱글톤 빈을 이용하듯이 스코프 빈을 쓸 수 있다.
- @Scope 애노테이션의 proxyMode 엘리먼트를 사용해서 프록시 모드를 결정한다.
- 클라이언트는 스코프 프록시 오브젝트를 실제 스코프 빈처럼 사용하면 프록시에서 현재 스코프에 맞는 실제 빈 오브젝트로 작업을 위임해준다.
- 스코프 프록시는 각 요청에 연결된 세션 정보를 참고해서 사용자마다 다른 오브젝트를 사용하게 해준다.
- 스코프 프록시는 실제 스코프 오브젝트를 상속하고 있다.
- 프록시 방식의 DI를 적용하면 스코프 빈이지만 마치 싱글톤 빈을 사용하듯이 편하게 쓸 수 있다는 장점이 있다.
- 반면에 주입되는 빈의 스코프를 모르면 코드를 이해하기가 어려울 수도 있다.
커스텀 스코프와 상태를 저장하는 빈 사용하기
- 스프링이 기본적으로 제공하는 스코프 외에도 임의의 스코프를 만들어 사용할 수 있다.
- 싱글통 외에 스코프를 사용한다는 건 기본적으로 빈에 상태를 저장해두고 사용한다는 의미다.
- 스프링에서는 Scope 인터페이스를 구현해서 새로운 스코프를 작성할 수 있다.
- 빈에 상태를 저장해두는 방식을 선호하지 않는다면 상태 정보를 URL 파라미터, 쿠키, 폼 히든 필드, DB, HTTP 세션등에 분산해 저장해두고 코드로 관리하면 된다