17 Ocak 2020 Cuma

SpringContext BeanFactoryAware Arayüzü - Bean Yüklendikten Sonra Çalışır

Giriş
Şu satırı dahil ederiz
import org.springframework.beans.factory.BeanFactoryAware;
Aware kelimesi ile biten bazı Spring arayüzleri var. Bunlar şöyle.
ApplicationContextAware
BeanFactoryAware
BeanNameAware
Aware kelimesi ile biten bu arayüzleri gerçekleştiren sınıflar setter() metodları ile Spring'in iç mekanizmasına erişebiliyorlar. Yani kendi sınıfım FooAware arayüzünü gerçekleştiriyorsa - Foo Spring içindeki bir mekanizma olsun - setFoo() şeklindeki bir metod ile Foo'ya erişebiliyorum.

Bu sınıf ile BeanFactory nesnesi atanıyor.

Lifecycle
Bean Lifecycle yazısına bakabilirsiniz.

setBeanFactory metodu
Açıklaması şöyle.
Beans might need access to the bean factory that created it, say to call any service from the bean factory.
Should you need to obtain a reference to the bean factory, implement the BeanFactoryAware interface. This interface provides the setBeanFactory() method.
Örnek
Şöyle yaparız.
@Component
public class BeanPropertiesUtil  implements InitializingBean, BeanFactoryAware {

  private BeanFactory beanFactory;
  
  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    this.beanFactory = beanFactory;
  }
  ...
}
Örnek
Şöyle yaparız.
public class TestBeanFactoryAware implements BeanFactoryAware {
  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    System.out.println("[TestBeanFactoryAware] " + 
      beanFactory.getBean(TestBeanFactoryAware.class).getClass().getSimpleName());
  }
}



SpringMVC @RestControllerAdvice Anotasyonu

Giriş
Exception döndürmek için 3 yöntem var
- @ControllerAdvice + @ExceptionHandler : Global Exception handling yapar
- Using HandlerExceptionResolver
- Using ResponseStatusException
JSON döndüren REST noktası exception fırlatırsa, exception'ı içeren JSON döndürebilmeyi sağlar. Bu sınıfın kardeşi @ControllerAdvice anotasyonu. Açıklaması şöyle. Yani bu anotasyon yerine @ControllerAdvice kullanılabilir
@RestControllerAdvice is just a syntactic sugar for @ControllerAdvice + @ResponseBody ...

Kullanım
Şeklen şöyle


1. Metodumuz @ExceptionHandler(...) anotasyonuyla işaretlenir. Böylece servis sınıfı bu exception'ı fırlatırsa ilgili metod tetiklenir.
2. Genellikle kendi tanımladığımız bir exception sınıfı ResponseEntity ile sarmalanır ve ResponseEntity döndürülür.
3. ResponseEntity döndürmek yerine Metodumun üzerine @ResponseStatus anotasyonu eklersem de olur
4. Sınıfım ResponseEntityExceptionHandler sınıfından kalıtabilir. Böylece gerekirse Spring tarafından hazırlanan ResponseEntity gerekirse override edilebilir. 

Ben kendi kullanımımda
org.springframework.dao.InvalidDataAccessResourceUsageException'ları yakaladım

1. Exception'ı Decorate Etmek
Bazı kodlarda şöyle yapılıyor. Yani @ResponseStatus kullanılarak veya exception ResponseStatusException'dan kalıtarak bir HTTP sonucu ile exception ilişkilendiriliyor. Ancak bence bu doğru değil. Çünkü exception ve HTTP kodları farklı şeyler.
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NoSuchElementFoundException extends RuntimeException {
  ...
}
veya şöyle yapılıyorResponseStatusException yazısına bakabilirsiniz
public class NoSuchElementFoundException extends ResponseStatusException {

  public NoSuchElementFoundException(String message){
    super(HttpStatus.NOT_FOUND, message);
  }

  @Override
  public HttpHeaders getResponseHeaders() {
      // return response headers
  }
}
ResponseEntity Döndürmek
Ben bu yöntemi çok kısıtlı bilgi döndürdüğü için tercih etmiyorum
Örnek
Şöyle yaparız
@RestControllerAdvice
public class ExceptionAdvice {

  @ExceptionHandler(value = NotFoundException.class)
  public ResponseEntity<CustomErrorResponse> handleException(NotFoundException e) {

    CustomErrorResponse err = new CustomErrorResponse("NOT_FOUND_ERROR", e.getMessage());
    err.setTimestamp(LocalDateTime.now());
    err.setStatus((HttpStatus.NOT_FOUND.value()));
    return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
   }  
}
Örnek
Şöyle yaparız.
@RestControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(value = {Exception.class})
  public ResponseEntity<ExceptionResponse> unknownException(Exception ex) {
    Foo resp = new Foo (ex, level); // my custom response object
    return new ResponseEntity<ExceptionResponse>(resp, resp.getStatus());
  }
}
Örnek
Şöyle yaparız
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElementFoundException(
  NoSuchElementFoundException exception
) {
  return ResponseEntity
    .status(HttpStatus.NOT_FOUND)
    .body(exception.getMessage());
}
@ResponseStatus Kullanmak
Bu yöntem bence en iyisi. Düzgün bir CustomErrorReponse nesnesi tanımlanırsa bayağı detaylı bilgi dönülebilir.
Örnek
Şöyle yaparız.
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public CustomErrorResponse handle(Exception e) {

  return new CustomErrorResponse(e.getMessage());
}

16 Ocak 2020 Perşembe

14 Ocak 2020 Salı

SpringRetry RetryTemplate Sınıfı

Giriş
Şu satırı dahil ederiz. 
import org.springframework.retry.support.RetryTemplate;
- RetryOperations arayüzünden kalıtır. Bu arayüzün 4 tane overload edilmiş execute() metodu var. Bu metodlar parametre olarak RetryCallback, RecoveryCallback, RetryState nesneler alırlar.
RetryCallback yapılması istenen işi temsil eder.

- Bu sınıfı bir bean yapmak iyi bir fikir olabilir.

Stateless retry interceptor
Açıklaması şöyle. Yani thread'i bloke eder
... all retry attempts happen without exiting the interceptor call, so the interceptor is called only once regardless of the number of retries. Any required state (e.g. number of attempts) is kept on the stack.
Stateful retry interceptor
Açıklaması şöyle. Yani thread'i bloke etmez
Spring Retry also provides a stateful retry interceptor in which case the Exception is propagated outside of the interceptor. In other words the interceptor is called once per retry, therefore it needs to somehow remember the state of the retries (hence it is called stateful). The main use case for a stateful retry interceptor is so that it can be used in Hibernate transactions (through the Spring @Transactional interceptor), where if the Hibernate Session throws an exception, the transaction must be rolled back and the Session discarded. This means that upon failure the call has to exit the retry interceptor, so that the transaction interceptor can close the session and open a new one for each retry attempt.
execute metodu - RetryCallback
RetryCallback exception fırlatırsa backoffpolicy ve retrypolicy müsaade ediyorsa, tekrar çalıştırılır.

RetryCallback sınıfının doWithRetry() metodu RetryContext parametresi alır. Açıklaması şöyle
The method parameter for the RetryCallback is a RetryContext. Many callbacks ignore the context. However, if necessary, you can use it as an attribute bag to store data for the duration of the iteration.

A RetryContext has a parent context if there is a nested retry in progress in the same thread. The parent context is occasionally useful for storing data that needs to be shared between calls to execute.
Örnek - lambda
Şöyle yaparız
retryTemplate.execute(arg -> callMyFunction());
Örnek
Şöyle yaparız.
public Object getSomething(@PathVariable("id") String id) throws Exception{

  return retryTemplate.execute(new RetryCallback<Object, Exception>() {
    @Override
    public Object doWithRetry(RetryContext arg0) {
      Object o = restTutorialClient.getEmployeesList(id);
      return o;
    }
});
Örnek
RetryCallback arayüzü yerine lambda kullanmak için şöyle yaparız.
@Service
public class TestService {

  @Autowired
  private RetryTemplate retryTemplate;

  public String testService() {

    //Retryable
    String result = retryTemplate.execute(context -> {
      System.out.println("Inside the Method, Retry = " + context.getRetryCount());
      if (context.getRetryCount() == 0)
        throw new RuntimeException("Something went wrong");
      return "Successfully Completed";
    });

    //Result
    System.out.println("FINAL Result = " + result);
    return result;
  }
}  
execute metodu - RetryCallback + RecoveryCallback
Örnek
Şöyle yaparız
Foo foo = template.execute(new RetryCallback<Foo>() {
  public Foo doWithRetry(RetryContext context) {
    // business logic here
  },
  new RecoveryCallback<Foo>() {
    Foo recover(RetryContext context) throws Exception {
          // recover logic here
    }
});
registerListener metodu
RetryListenerSupport sınıfı yazısına taşıdım

setBackOffPolicy metodu
FixedBackOffPolicy kullanılabilir.

setPolicyMap metodu
Örnek
Şöyle yaparız.
@Bean(name="myRetryTemplate")
public RetryTemplate retryTemplate() {

  RetryTemplate retryTemplate = new RetryTemplate();
  SimpleRetryPolicy simpleRetryPolicyCheck = new SimpleRetryPolicy();
  simpleRetryPolicyForOkta.setMaxAttempts(3);

  Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<>();
  policyMap.put(Exception1.class, simpleRetryPolicyCheck );
  policyMap.put(Exception2.class, simpleRetryPolicyCheck );

  ExceptionClassifierRetryPolicy retryPolicy = new ExceptionClassifierRetryPolicy();
  retryPolicy.setPolicyMap(policyMap);
  retryTemplate.setRetryPolicy(retryPolicy);
  return retryTemplate;
}
setRetryPolicy metodu
SimpleRetryPolicy kullanılabilir. Belirtilen sayı kadar tekrar dener
TimeoutRetryPolicy kullanılabilir;