Spring Framework - 프로토타입 범위 지정 Bean 처리

Spring Framework - 프로토타입 범위 지정 Bean 처리



소개



, 또는 기타 유사한 주석을 사용하여 빈을 생성할 때마다 클래스의 인스턴스를 생성하기 위한 레시피를 생성합니다. 이 레시피와 함께 해당 인스턴스의 범위를 정의할 수도 있습니다. 싱글톤, 프로토타입, 세션, 요청 또는 전역 세션 중 하나일 수 있습니다.

우리 대부분은 이미 이러한 빈 범위에 익숙합니다. 하지만 오늘은 프로토타입 범위가 여러 인스턴스를 생성하는지 아니면 단일 항목으로 취급되는지 이해하려고 합니다.

따라서 싱글톤 및 프로토타입 범위 Bean에 대한 요약부터 시작하겠습니다. 그런 다음 프로토타입이 싱글톤이 될 수 있는 문제와 가능한 솔루션을 살펴보겠습니다.

GitHub 레포



https://github.com/shethapurv/spring-boot/tree/main/lookup-scope-service

기초



EmployeeService 및 Department Service라는 두 개의 서로 다른 서비스 클래스를 정의해 보겠습니다. EmployeeService는 싱글톤 범위이고 DepartmentService는 프로토타입 범위입니다.

@Service
**@Scope("prototype")** // prototype scoped
public class **DepartmentService** {
    private Integer departmentId = new Random().nextInt(1000);

    public int getDepartmentId(){
        return departmentId;
    }
}

**@Service** // by default, singleton scoped
public class **EmployeeService** {
}


이제 테스트 케이스를 작성하고 위에서 정의한 각 서비스 bean의 해시코드를 비교해보자.

싱글톤 범위 bean "EmployeeService"의 여러 인스턴스는 동일한 해시 코드를 갖습니다.

@Test
public void singletonTest() {
    EmployeeService employeeService1 = applicationContext.getBean("employeeService", EmployeeService.class);
    EmployeeService employeeService2 = applicationContext.getBean("employeeService", EmployeeService.class);
    **Assertions._assertEquals_** (employeeService1.hashCode(), employeeService2.hashCode());
}


프로토타입 범위 bean "DepartmentService"의 여러 인스턴스는 다른 해시코드를 갖습니다.

@Test
public void prototypeTest() {
    DepartmentService departmentService1 = applicationContext.getBean("departmentService", DepartmentService.class);
    DepartmentService departmentService2 = applicationContext.getBean("departmentService", DepartmentService.class);
    **Assertions._assertNotEquals_(**departmentService1.hashCode(), departmentService2.hashCode());
}


예, 지금까지는 좋습니다. 테스트 결과는 예상대로입니다. 그렇지 않아!

문제



그래서, 여기에 문제가 있습니다. 싱글톤 범위의 빈에 프로토타입 범위의 빈을 주입할 때마다 프로토타입 범위의 빈은 싱글톤 빈으로 작동하기 시작합니다.

간단한 예를 들어 이해해 봅시다.

DepartmentService bean에서 위에서 관찰한 경우 부서 ID로 임의의 정수를 리턴하는 "departmentId"클래스 변수가 선언됩니다. 이제 우리는 DepartmentService가 프로토타입 범위의 빈이기 때문에 DepartmentMetnId를 얻으려고 시도할 때마다 두 개의 다른 부서 ID를 반환해야 하지만 실제로는 동일한 부서 ID만 반환할 것입니다.

@RestController
@RequestMapping(path = "/beanScope", method = RequestMethod._GET_)
public class **BeanScopeController** {

    @Autowired
    EmployeeService employeeService;

    @RequestMapping(path = "/prototypeTreatedAsSingleton", method = RequestMethod._GET_)
    public List<Integer> getDepartmentIdWithDeptTreatedAsSingleton() throws InterruptedException {
        return employeeService.getDepartmentIdWithDeptTreatedAsSingleton();
    }
}

// **EmployeeService**

// it will be treated as singleton even though its prototype scoped @Autowired 
DepartmentService **departmentService** ;

public List<Integer> getDepartmentIdWithDeptTreatedAsSingleton() throws InterruptedException {
    **int dep1 = departmentService.getDepartmentId();**
    Thread._sleep_(1000L);
    **int dep2 = departmentService.getDepartmentId();**
    return List._of_(dep1, dep2);
}




위에서 설명한 문제에 대해 명확했으므로 이제 이 문제에서 벗어나기 위한 다양한 접근 방식을 살펴보겠습니다.

해결책



프로토타입 범위의 bean을 실제 예상대로 취급합니다. 우리는 auto-wiring 프로토타입 scoped bean 대신에 3가지 접근 방식을 가지고 있습니다.

1. ApplicationContext 사용



애플리케이션 컨텍스트를 사용하여 프로토타입 범위의 bean 객체를 얻을 수 있습니다. 그러나 여기서 우리는 제어 역전의 원칙을 위반하고 있습니다. 객체를 생성하는 스프링 컨테이너 대신 객체를 생성하고 있습니다.

@Autowired
ApplicationContext **applicationContext** ;

public List<Integer> getDepartmentIdWithApplicationContext() throws InterruptedException {
    int dep1 = **applicationContext.getBean** (DepartmentService.class).getDepartmentId();
    Thread._sleep_(1000L);
    int dep2 = **applicationContext.getBean** (DepartmentService.class).getDepartmentId();
    return List._of_(dep1, dep2);
}


2. ObjectFactory 사용



객체 팩토리 인스턴스화를 사용하여 프로토타입 범위의 빈을 얻을 수 있지만 이 접근 방식의 문제점은 인스턴스가 즉시 초기화된다는 것입니다.

@Autowired
private ObjectFactory<DepartmentService> **departmentServiceObjectFactory** ;

public List<Integer> getDepartmentIdWithObjectFactory() throws InterruptedException {
    int dep1 = **departmentServiceObjectFactory.getObject** ().getDepartmentId();
    Thread._sleep_(1000L);
    int dep2 = **departmentServiceObjectFactory.getObject** ().getDepartmentId();
    return List._of_(dep1, dep2);

}


3. @LookUp 주석 사용



Spring에서 제공하는 주석을 사용할 수 있습니다. 이는 스프링 컨테이너 자체가 인스턴스 생성을 처리하기 때문에 옵션 1에서 직면한 제어 역전 문제를 해결하는 데 도움이 될 것입니다. 또한 옵션 2에서 본 것처럼 즉시 초기화되지 않습니다.

public List<Integer> getDepartmentIdWithLookUp() throws InterruptedException {
    int dep1 = **getDepartmentService** ().getDepartmentId();
    Thread._sleep_(1000L);
    int dep2 = **getDepartmentService** ().getDepartmentId();
    return List._of_(dep1, dep2);

}

@ **Lookup**
public DepartmentService **getDepartmentService** () {
    return null;
}


결론



프로토타입 범위 빈이 올바르게 처리되지 않으면 발생할 수 있는 문제를 살펴보았습니다. 우리는 또한 이러한 문제를 해결하기 위한 다양한 접근 방식을 보았습니다.

스프링 IoC 원칙을 위반하지 않고 빈이 열심히 초기화되지 않는 프로토타입 범위 빈을 처리하기 위해 주석을 사용하는 것이 좋습니다.

참조


  • 4.4 Bean scopes
  • Lookup (Spring Framework 5.3.22 API)

  • 이 게시물이 도움이 되었다면 박수를 몇 번 치거나 팔로우하여 제가 배운 내용을 공유할 수 있도록 지속적으로 동기를 부여해 주세요.

    함께 배우고, 나누고, 성장합니다.

    좋은 웹페이지 즐겨찾기