3

I have created a spring framework based application using AnnotationConfigApplicationContext.

One bean has an init method which creates a connection to an external service. This can be annotated with @PostConstruct to run automatically once the bean is initiated which works.

To handle any exceptions when creating this connection I want my init method to retry up to 5 times if an exception is caught. When annotating the method with both @PostConstruct and @Retryable I see the exception is thrown once and the program exits- It appears @Retryable has no effect.

I have used @EnableRetry in the configuration class correctly along with @Configuration. I have created another method B on the same bean which is annotated as retryable, if this method is called after the bean is instantiated I can see the method gets retried/behaves as expected when an exception is thrown.

My thoughts as to why this is not working is possibly something aspect related or the the postconstruct happens before the spring-retry element gets attached?

Is there actually a better way to have an initialization method that can handle exceptions and be retryable via annotations- instead of trying programmatically in the method?

Edit: I agree now that creating connections to external services should not be done via @Postconstruct. This can stop the whole context from initialising if the retries fail which can have detrimental effects.

However this does not yet answer the question of what part of the Spring Framework is not letting these two annotations work in conjunction.

UserF40
  • 3,533
  • 2
  • 23
  • 34

3 Answers3

3

To handle any exceptions when creating this connection I want my init method to retry up to 5 times if an exception is caught.

You should NEVER connect to resources in init methods; you should wait for the context to be created first.

It's much better to implement SmartLifecycle and connect in start(). That way, you can be sure that the whole context has been initialized before you start connecting to external resources.

That way, the start() method should be advised by the retry interceptor.

As @Naros suggested, the ContextRefreshedEvent is another alternative but you should, never, never do stuff like that in @PostConstruct.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Thanks for advice, Gary. But what to do next ? `connect in start()` – how to pass this connection then to specific bean? Before I read your answer I had been wanted to get connection (in my case: curr hazelcast instance) in `PostConstruct` with 5 tries. – Woland Jul 16 '19 at 13:43
  • Don't ask new questions in comments. Just move the code from the post construct method to `SmartLifecyle.start()`. If that's not enough information, ask a new question. – Gary Russell Jul 16 '19 at 19:12
2

I would suggest you actually apply an ApplicationListener that listens for a ContextRefreshedEvent and have this method annotated with your retry logic. These listeners are fired once the context is fully refreshed and all beans have been properly configured and wired.

Naros
  • 19,928
  • 3
  • 41
  • 71
  • 1
    Replacing PostConstruct with EventListener({ContextRefreshedEvent.class}) in combination with Retryable gives the behavior I want. I believe the Context can be refreshed more than once in an applications lifecycle so it may not be the solution I am looking for. The Method to create the connection should only ever run once with X retries. – UserF40 Jul 03 '16 at 21:28
  • Yes a context can be refreshed multiple times, but during each refresh beans are recreated in the spring context, so it should give you the behavior you want regardless. – Naros Jul 03 '16 at 21:48
2

'@Retryable' is an aspect applied by 'spring-retry'. This only attaches after the spring context is fully created, which is after all '@PostConstruct' annotated methods run thus the '@Retryable' has no effect.

UserF40
  • 3,533
  • 2
  • 23
  • 34
  • I think a better answer is the below https://stackoverflow.com/a/38175918/1470436 – Beto Neto Aug 19 '20 at 17:15
  • @BetoNeto, the easiest answer to any problem is to stop having that problem. I think the best answer is the one provided by the OP since he explained the internal parts. Now it's up to the reader to use what he thinks is better with all equipped knowledge. – skryvets Nov 06 '20 at 15:35