'Spring Retry: How to make all methods of a @Bean retryable?

I would like to create a @Bean of a third party service like Keycloak (or any other) which may or may not be reachable at any given time. This object should retry all methods of the resulting Keycloak bean.

I have tried the following:

@Configuration
@EnableRetry
class KeycloakBeanProvider() {

    @Bean
    @Retryable
    fun keycloak(oauth2ClientRegistration: ClientRegistration): Keycloak {
        return KeycloakBuilder.builder()
            .serverUrl(serverUrl)
            .realm(oauth2ClientRegistration.clientName)
            .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
            .clientId(oauth2ClientRegistration.clientId)
            .clientSecret(oauth2ClientRegistration.clientSecret)
            .build()
    }
}

But this way only the bean creation will be retried, not actual method calls on the bean. I know @Retryable can be used on class level but I don't own the Keycloak class so I can't add it there.

How can I make the methods of the resulting Keycloak bean retryable?



Solution 1:[1]

You have to annotate the Keycloak with @Retryable.

@SpringBootApplication
@EnableRetry
public class So70593939Application {

    public static void main(String[] args) {
        SpringApplication.run(So70593939Application.class, args);
    }

    @Bean
    ApplicationRunner runner(Foo foo) {
        return args -> {
            foo.foo("called foo");
            foo.bar("called bar");
        };
    }

}

@Component
@Retryable
class Foo {

    public void foo(String in) {
        System.out.println("foo");
        throw new RuntimeException("test");
    }

    public void bar(String in) {
        System.out.println("bar");
        throw new RuntimeException("test");
    }

    @Recover
    public void recover(Exception ex, String in) {
        System.out.println(ex.getMessage() + ":" + in);
    }

}
foo
foo
foo
test:called foo
bar
bar
bar
test:called bar

If you can't annotate the class (e.g. because it's from another project), you need to use a RetryTemplate to call its methods instead of using annotation-based retry.

Solution 2:[2]

You can manually instrument your class. Check documentation.

@Bean
public Keycloak myService() {
    ProxyFactory factory = new ProxyFactory(RepeatOperations.class.getClassLoader());
    factory.setInterfaces(Keycloak.class);
    factory.setTarget(createKeycloak());

    Keycloak keycloak = (Keycloak) factory.getProxy();
    JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
    pointcut.setPatterns(".*.*");

    RetryOperationsInterceptor interceptor = new RetryOperationsInterceptor();

    ((Advised) keycloak).addAdvisor(new DefaultPointcutAdvisor(pointcut, interceptor));

    return keycloak;
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gary Russell
Solution 2 Juan Rada