25 Kasım 2020 Çarşamba

SpringContext @Scope Anotasyonu - Prototype Scope - Her Kullanımda Yeni Bir Bean Yaratılır

Giriş
Her çağrıda yeni bir bean yaratır.

value Alanı
- ConfigurableBeanFactory.SCOPE_PROTOTYPE sabiti veya
- BeanDefinition.SCOPE_PROTOTYPE sabiti veya
- normal string kullanılabilir.

BeanDefinition.SCOPE_PROTOTYPE sabiti şöyledir
/**
 * Scope identifier for the standard prototype scope: {@value}.
 * <p>Custom scopes can be added via {@code registerScope}.
 * @see #registerScope
 */
String SCOPE_PROTOTYPE = "prototype";
Aynı sonuca varmak için @Scope anotasyonu yerine @Lookup anotasyonu da kullanılabilir.

Örnek - ConfigurableBeanFactory Sabiti
Şöyle yaparız
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
       proxyMode = ScopedProxyMode.TARGET_CLASS)
Örnek - normal string Sabiti
Şöyle yaparız.
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CompX { ....}
Scope Bean ve Singleton
Singleton bean içine Scope bean inject edilse bile singleton bean hep aynı nesneyi kullanır. 

Çözüm 1
Şöyle yaparız
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ProductPurchasedEvent {
  ...
}

@Service
public class PurchaseService {
  private ProductPurchasedEvent productPurchasedEvent;

  @Autowired
  public PurchaseService(ProductPurchasedEvent productPurchasedEvent) {
    this.productPurchasedEvent = productPurchasedEvent;
  }
  ...
}
Açıklaması şöyle. Burada dikkat edilmesi gereken şey şu. Bu prototype bean'in herhangi bir metod çağrılınca yeni bir nesne yaratılır. Yani iki setter() çağırırsak iki nesne yaratılır. Dolayısıyla prototype bean stateful ise tehlikeli bir yöntem
when a singleton has a prototype dependency (unless we customize it). It eagerly creates and injects all dependent objects at the application startup level
...
Spring supports proxy pattern with injecting prototype beans which means instead of injecting a real bean itself we inject a proxy object that delegates all the work to the real created beans
...
If we enable TARGET_CLASS proxy mode on our prototype class, spring creates a new instance for every method call of prototype
Çözüm 2
Her seferinde yeni bir nesne üretmek için şöyle yaparız
@Service
public class FactoryUtil implements ApplicationContextAware {

  private static ApplicationContext context;

  public FactoryUtil() {
  }

  public static <T> T getBean(Class<T> beanClass) {
    return context.getBean(beanClass);
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) 
    throws BeansException {
    context = applicationContext;
  }
}
Kullanmak için şöyle yaparız
@Component
public class ShapeFactory {
  private Map<ShapesType, Shape> myServiceCache = new HashMap<>();

  private final List<Shape> handlers;

  @Autowired
  private ShapeFactory(List<Shape> handlers) {
    this.handlers = handlers;
  }

  public static Shape getShapeImplementation(ShapesType shapesType) {

    Shape service = myServiceCache.get(shapesType);
    if (null == service) {
      throw new RuntimeException("Unknown shape type: " + shapesType);
    }
    return FactoryUtil.getBean(service.getClass());
  }
}

Scope Bean'ler İçin Pool
Örnek
Elimizde bir bean olsun.
@Component("sheet")
@Scope(value = "prototype")
public class Sheet {
  // Implementation goes here
}
Bu bean için bir havuz oluşturalım.
public class SheetPool {

  private List<Sheet> sheets;

  public List<Sheet> getSheets() {
    return sheets;
  }

  public Sheet getObject() {
    int index = ThreadLocalRandom.current().nextInt(sheets.size());
    return sheets.get(index);
  }

}
Her çağrıda bu bean'den yeni bir tane oluşturmak için şöyle yaparız.
@Configuration
public class ApplicationConfig {

  @Autowired
  ApplicationContext applicationContext;

  @Bean
  public SheetPool sheetPool() {
    SheetPool pool = new SheetPool();

    IntStream.range(0, 10).forEach(e -> {
      pool.getSheets().add((Sheet) applicationContext.getBean("sheet"));
    });

    return pool;
  }
}

Hiç yorum yok:

Yorum Gönder