토비의 스프링 Vol2 1.3 프로토타입과 스코프

Soonwoo Kwon·2022년 6월 6일
0

토비의 스프링

목록 보기
9/11

1.3 프로토타입과 스코프

애플리케이션 컨텍스트마다 빈의 오브젝트는 한 개만 만들어진다. 사용자의 요청 마다 매번 애플리케이션의 로직을 담당하는 오브젝트를 생성하는 것은 낭비이다.
하지만 때로는 빈 오브젝트를 싱글톤이 아닌 다른 스코프로 관리해야 하는 경우가 있다.
싱글톤이 아닌 빈은 프로토타입 빈스코프 빈 두 가지가 존재한다.

1.3.1 프로토타입 스코프

싱글톤 빈은 컨텍스트 한 개당 하나의 빈만을 생성한다. 따라서 해당 빈을 DI나 DL할 때 마다 같은 오브젝트를 억게 된다.
프로토타입 빈은 DI나 DL 방식을 통해 컨테이너에게 빈을 요청할 때 마다 매번 새로운 오브젝트를 생성하여 돌려준다.

프로토타입 빈의 생명주기와 종속성

빈 오브젝트는 생성과 관리, 제거 등 모든 생명주기를 코드가 아닌 컨테이너가 관리하고 이를 IoC라고 한다.
하지만 프로토타입 빈은 컨테이너에 의해 생성될 뿐 빈을 제공한 뒤에는 컨테이너가 프로토타입 빈 오브젝트를 관리하지 않는다.
따라서 프로토타입 빈은 DI를 통해서 주입된 오브젝트에 종속적이다.

프로토타입 빈의 용도

사용자의 요청마다 독립적으로 오브젝트를 생성하려는 경우 도메인 오브젝트나 DTO를 new 키워드로 생성하여 파라미터로 전달하여 사용할 수 있다.
하지만 이러한 방식을 통해서는 DI를 사용할 수 없다. 요청에 따라 독립적인 오브젝트를 코드가 아닌 컨테이너를 통해 오브젝트를 생성하고 초기하여 DI를 하기 위해 프로토타입 빈을 사용한다.

Scope를 "prototype"으로 두어 ServiceRequest 오브젝트를 프로토타입 빈으로 생성할 수 있다.

@Component
@Scope("prototype")
public class ServiceRequest{
    Customer customer;
    String productNo;
    String description;
    @Autowired CutomerDao cutomerDao;

    public void setCustomerByCustomerNo(String customerNo){
        this.customer = customerDao.findCustomerByNo(customerNo);
    }
}

프로토타입 빈으로 생성된 ServiceRequest 오브젝트를 받아오기 위해서는 DI를 사용해야 한다.

@Autowired Application context;
public void serviceRequestFormSubmit(HttpServletReqeust request){
    ServiceRequest serviceRequest = this.context.getBean(Service.class);
    serviceRequest.setCustomerByCustomerNo(request.getParameter("custno"));
}

이와 같이 매번 새롭게 오브젝트를 생성하면서도 DI를 적용할 수 있다.
고급 AOP 기능을 사용하면 프로토타입 빈을 이용하지 않고 new 키워드로 생성하더라도 DI가 되도록 할 수 있다.

DI와 DL

위의 코드에서 ServiceRequest를 가져올 때 ApplicationContext를 생성하여 getBean() 메소드를 호출하는 DL 방식을 이용하였다.

@Autowired ServiceRequest serviceRequest;

public void setServiceRequestForSubmit(HttpServletRequest request){
    this.serviceRequest.setCustomerNo(request.getParameter("custno"));
    ...
}

이와 같이 ApplicationContext를 생성하지 않고 직접 DI를 받아 이용할 수 있지만, DI가 발생할 때 마다 새로운 serviceRequest 오브젝트가 생성되지 않는다. 따라서 요청 마다 같은 오브젝트가 공유되어 사용된다.
따라서 요청 마다 다른 오브젝트를 사용하기 위해서는 컨테이너에게 요청에 새로운 오브젝트를 생성한 뒤, DL 방식으로 이용해야 한다.

프로토타입 빈의 DL 전략

이전 DL을 이용하기 위해 ApplicationContext를 DI받은 뒤 getBean() 메소드를 이용하여 호출하였다.
간단한 방법이지만 스프링 컨테이너가 코드에 등장한다는 점과 단위 테스트를 위해 ApplcationContext라는 거대한 인터페이스의 목 오브젝트를 만들어야 하는 점이 단점이다.
따라서 DL을 받는 다른 방법을 알아보자.

ApplicationContext, BeanFactory

기존 사용된 방식으로 ApplicationContext를 DI 받은 뒤 getBean() 메소드를 호출해 빈을 가져온다.
간단하게 사용할 수 있지만 코드에 스프링 API가 등장한다는 단점이 있다.(POJO에 어긋남)

ObjectFactory, ObjectFactoryCreatingFactoryBean

ApplicationContext를 DI 받아서 getBean()을 호출해 원하는 프로토타입 빈을 가져오는 역할을 하는 오브젝트를 중간에 둔다.
스프링에서 제공하는 ObjectFactory 인터페이스는 위의 기능을 수행한다. getObject() 메소드를 통해 가져올 수 있다.

ObjectFactory<ServiceRequest> factory = ...;
ServiceRequest request = factory.getObject();

이 방법 또한 스프링에 제공하는 인터페이스이지만 ApplicationConext를 직접 사용하는 것 보다 깔끔하고 테스트하기 편하다.

ObjectFactory의 구현 클래스 또한 스프링이 제공하며 이는 ObjectFactoryCreatingFactoryBean이다.
다음과 같은 설정을 통해 ObjectFactory를 등록한다.
프로토타입 빈 이외에도 DL을 이용해야 하는 모든 경우에 적용 가능하다.

@Configuration
public class ObjectFactoryConfig {
    @Bean public ObjectFactoryCreatingFactoryBean serviceRequestFactory(){
        ObjectFactoryCreatingFactoryBean factoryBean =
                new ObjectFactoryCreatingFactoryBean();
        factoryBean.setTargetBeanName("serviceRequest");
        return factoryBean;
    }
}

ServiceLocatorFactoryBean

스프링이 미리 정의해 둔 ObjectFactory 인터페이스를 사용하지 않고 미리 정의된 인터페이스를 사용할 때 이용한다.
XML 파일을 설정한 뒤 이용하고, 이 팩토리의 타입은 범용적으로 사용되는 ObjectFactory가 아닌 등록된 인터페이스가 빈의 타입이 되기 때문에 @Autowired를 이용할 수 있다.

메소드 주입

일정한 규칙을 따르는 추상 메소드를 작성하면 ApplicationContext와 getBean() 메소드를 사용해서 프로토타입 빈을 가져오는 메소드를 런타임 시에 추가하는 기술이다.
스프링 API에 독립적이기 때문에 컨테이너 없이 단위 테스트를 할 수 있다.
하지만 추상 클래스는 상속을 통해 오버라이딩하여 사용해야 하므로 단위 테스트가 많이 작성된다면 단점이 많은 방법이다.

Provider

가장 최근에 소개된 기술로 ObjectFactory 와 유사하지만 ObjectFactoryCreatingFactoryBean을 이용해 빈을 등록하지 않아도 된다
Provider 인터페이스를 @Inject, @Autowired, @Resource 중 하나로 DI 되도록하면 스프링이 자동으로 Provider를 구현한 오브젝트를 생성하여 주입한다. 팩토리 빈을 XML이나 @Configuration으로 정의하지 않아도 된다.

1.3.2 스코프

스코프의 종류

스프링에는 싱글톤, 프로토타입, 요청, 세션, 글로벌세션, 애플리케이션 이라는 스포크가 제공된다.

요청 스코프

  • 요청 스코프 빈은 하나의 웹 요청 안에서 만들어지고 해당 요청이 끝나면 제거된다. DL을 사용하는 것이 편하지만 DI도 사용 가능하다.
  • 존재 범위와 특징이 도메인 오브젝트나 DTO와 유사하여 요청 스코프 빈을 자주 사용하지는 않는다.
  • 애플리케이션 코드에서 생성한 정보를 프레임워크 레벨의 서비스나 인터셉터에 전달한다.

세션 스코프, 글로벌세션 스코프

  • HTTP 세션과 같은 존재 범위를 갖는 빈으로 만들어주는 스코프이다.
  • HTTP 세션은 사용자 별로 브라우저를 닫거나 세션 타임동안 유효하기 때문에 로그인 정보나 사용자별 옵션을 저장하는데 유용하다.
  • 글로벌세션 스코프는 포틀릿에만 존재하는 글로벌 세션에 저장되는 빈이다

포틀릿(Portlet)
포틀릿은 웹 기반 컨텐츠, 애플리케이션, 기타 자원에 대한 액세스를 제공하는 재사용가능 웹 모듈입니다.

애플리케이션 스코프

  • 애플리케이션 스코프는 서블릿 컨텍스트에 저장되는 빈 오브젝트이다.
  • 서블릿 컨텍스트는 웹 애플리케이션 마다 만들어지고, 웹 애플리케이션 마다 스프링 애플리케이션 컨텍스트가 생성된다. 따라서 싱글톤 스코프와 비슷한 존재 범위를 갖는다.
  • 웹 애플리케이션과 애플리케이션 컨텍스트의 존재 범위가 다른 경우 사용된다.

스코프 빈의 사용 방법

스코프 빈은 프로토타입 빈 처럼 DL 방식으로 사용해야 한다. DI를 사용할 경우 스프링이 제공하는 특별한 방법을 이용할 수 있다.
직접 스코프 빈을 DI하는 것이 아니라 스코프 빈에 대한 프록시를 DI 해주는 것이다.

클라이언트는 싱글톤으로 스코프 빈 오브젝트를 DI 받아 이용해야 한다. 하나의 스코프 빈 만으로 할당할 수 있어 사용자 마다 다른 정보를 저장할 수 없다.
이 때 스코프 프록시를 적용하게 되면 각 요청에 연결된 HTTP 세션 정보에 따라 다른 스코프 빈 오브젝트를 사용하게 해준다. 이처럼 프록시 방식의 DI를 적용하면 스코프 빈을 싱글톤 빈처럼 편하게 사용할 수 있다.

커스텀 스코프와 상태를 저장하는 빈 사용하기

스프링이 제공하는 스코프 이외에도 임의의 스코프를 구현하여 사용할 수 있다. 이 때 Scope 인터페이스를 구현할 수도 있지만 프레임워크를 이용한 방법도 가능하다.

0개의 댓글