Spring Retry: How to make all methods of a #Bean retryable? - spring

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?

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.

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;
}

Related

Mockito Spy quartz MethodInvokingJobDetailFactoryBean target job bean failed

Spring 6, Quartz, and a SimpleTrigger based scheduled task.
#Component
#Slf4j
public class Greeting {
public void sayHello() {
log.debug("Hello at {}:", LocalDateTime.now());
}
}
Quartz config:
#Configuration
class QuartzConfig{
#Bean
MethodInvokingJobDetailFactoryBean greetingJobDetailFactoryBean() {
var jobFactory = new MethodInvokingJobDetailFactoryBean();
jobFactory.setTargetBeanName("greeting");
jobFactory.setTargetMethod("sayHello");
return jobFactory;
}
#Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
SimpleTriggerFactoryBean simpleTrigger = new SimpleTriggerFactoryBean();
simpleTrigger.setJobDetail(greetingJobDetailFactoryBean().getObject());
simpleTrigger.setStartDelay(1_000);
simpleTrigger.setRepeatInterval(5_000);
return simpleTrigger;
}
#Bean
public SchedulerFactoryBean schedulerFactoryBean() {
var factory = new SchedulerFactoryBean();
factory.setTriggers(
simpleTriggerFactoryBean().getObject(),
cronTriggerFactoryBean().getObject()
);
return factory;
}
And I tried to use awaitility to check the invocations.
#SpringJUnitConfig(value = {
QuartzConfig.class,
Greeting.class
})
public class GreetingTest {
#Autowired
Greeting greeting;
Greeting greetingSpy;
#BeforeEach
public void setUp() {
this.greetingSpy = spy(greeting);
}
#Test
public void whenWaitTenSecond_thenScheduledIsCalledAtLeastTenTimes() {
await()
.atMost(Duration.ofSeconds(10))
.untilAsserted(() -> verify(greetingSpy, atLeast(1)).sayHello());
}
}
Running the tests, it is failed.
org.awaitility.core.ConditionTimeoutException: Assertion condition defined as a com.example.demo.GreetingTest
Wanted but not invoked:
greeting.sayHello();
-> at com.example.demo.GreetingTest.lambda$whenWaitTenSecond_thenScheduledIsCalledAtLeastTenTimes$0(GreetingTest.java:36)
Actually, there were zero interactions with this mock.
within 10 seconds.
In the jobDetailFactorBean, I used jobFactory.setTargetBeanName("greeting"); to setup the target beans here, it should pass the Greeting bean directly.
Updated: resolved myself, check here.
You're creating a spy that in no way interacts with the actual code:
#BeforeEach
public void setUp() {
this.greetingSpy = spy(greeting);
}
This would have to be injected into the Spring context as a bean and used everywhere, where greeting is used. Spring actually provides such functionality: #SpyBean.
Instead of autowiring a greeting and wrapping it with a spy that does not interact with anything in the context, replace the #Autowired with #SpyBean annotation. Thanks to that a spy bean will be created and injected within the Spring context:
#SpyBean
Greeting greeting;
I created a commit in GitHub repository, where you can see the whole code - the test passes. I had to add the cronTriggerFactoryBean() method to the configuration as it is omitted in your question.
If you cannot use Spring Boot, you can create the spy within Spring context yourself using configuration:
static class Config {
#Bean
#Primary
Greeting greeting() {
return spy(new Greeting());
}
}
Thanks to that when you inject the bean, it will be possible to act on it with Mockito (remember to include the Config class in the #SpringJUnitConfig annotation).
I created another commit in the GitHub repository - the test passes. You can see the whole code there.

Hystrix and Spring #Async in combination

I'm using Hystrix library for the Spring Boot project (spring-cloud-starter-hystrix). I have a #Service class annotated with #HystrixCommand and it works as expected.
But, when I add the method annotated with #Async in that same service class then the Hystrix doesn't work, and fallback method is never called. What could cause this problem and how to fix it?
This is the Application class:
#EnableCircuitBreaker
#EnableHystrixDashboard
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This is the service class:
#Service
public class TemplateService {
#HystrixCommand(
fallbackMethod = "getGreetingFallback",
commandProperties = {#HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")}
)
public String getGreeting() {
URI uri = URI.create("http://localhost:8090/greeting");
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
if (response.getStatusCode().equals(HttpStatus.OK)) {
return response.getBody();
} else {
return null;
}
}
public String getGreetingFallback(Throwable e) {
return null;
}
#Async
public void async(String message) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
logger.info(MessageFormat.format("Received async message {0}", message));
}
}
#EnableAsync annotation is placed in a different class annotated with #Configuration, where I set some other Thread Executor options from properties file.
Given the code for TemplateService (which doesn't implement interface) and assuming the defaults on #EnableAsync it is safe to concur that CGLIB proxies are created by spring.
Thus the #HystrixCommand annotation on getGreeting() isn't inherited by the service proxy class; which explains the reported behavior.
To get past this error keep the #HystrixCommand and #Async method separated in different service because enabling JDK proxies will also not help and I am not sure about AspectJ mode.
Refer this for further information on Spring proxy mechanism.

Spring Microservices issue with #HystrixCommand

We are facing a problem with Hystrix Command in a Spring Boot / Cloud microservice. We have a Spring Component containing a method annotated with #RabbitListener. When a new message arrives, the method delegates the invocation to NotificationService::processNotification().
The NotificationService is a bean annotated with #Service. The method processNotification() can request third party applications. We want to wrap the invocation of third party applications using #HystrixCommand to provide fault tolerance, but due to some reasons the Hystrix Command annotated method is not working.
If we invoke a Controller and the Controller delegates the invocation to a Service method, which in turns have a Hystrix Command , everything works perfectly. The only problem with Hystrix Command arises when the microservices consume a messages and it seems to be Hystrix Command doesn’t trigger the fallback method.
Here is the non-working code:
#Component
public class MessageProcessor {
#Autowired
private NotificationService notificationService;
#RabbitListener(queues = "abc.xyz-queue")
public void onNewNotification(String payload) {
this.notificationService.processNotification(payload);
}
}
#Service
public class NotificationService {
public void processNotification(String payload) {
...
this.notifyThirdPartyApp(notificationDTO);
...
}
#HystrixCommand(fallbackMethod = "notifyThirdPartyAppFallback")
public void notifyThirdPartyApp(NotificationDTO notificationDTO) {
//Do stuff here that could fail
}
public void notifyThirdPartyAppFallback(NotificationDTO notificationDTO) {
// Fallbacl impl goes here
}
}
#SpringBootApplication
#EnableCaching
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableRabbit
public class NotificationApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationApplication.class, args);
}
}
I'm not sure about your problem without looking at the code.
As another approach you can take: instead of describing this calls with annotations in your service, just extend HystrixCommand and implement api calling logic in it (read more):
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
#Override
protected String run() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}

Testing Mock Bean in Spring with Spock

I'm being hit with the issue that spock doesn't allow Mocks to be created outside of the specification - How to create Spock mocks outside of a specification class?
This seems to be still outstanding so am asking is that giving that i've got a complex and nested DI graph what is the most efficient way to 'inject' a mock representation deep in the graph?
Ideally, I have one bean definition set for normal deployment and another when running unit tests and it is this definition set being the applicable Mocks
e.g.
#Configuration
#Profile("deployment")
public class MyBeansForDeployment {
#Bean
public MyInterface myBean() {
return new MyConcreateImplmentation();
}
}
&&
#Configuration
#Profile("test")
public class MyBeansForUnitTests {
#Bean
public MyInterface myBean() {
return new MyMockImplementation();
}
}
Since Spock 1.1, you can create mocks outside of a specification class (detached mocks). One of the options is DetachedMockFactory. Take a look at the documentation or my answer to the question you linked.
You could try to implement a BeanPostProcessor that will replace the beans that you want with test doubles, such as shown below:
public class TestDoubleInjector implements BeanPostProcessor {
...
private static Map<String, Object[]> testDoubleBeanReplacements = new HashMap<>();
public void replaceBeanWithTestDouble(String beanName, Object testDouble, Class testDoubleType) {
testDoubleBeanReplacements.put(beanName, new Object[]{testDouble, testDoubleType});
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (testDoubleBeanReplacements.containsKey(beanName)) {
return testDoubleBeanReplacements.get(beanName)[TEST_DOUBLE_OBJ];
}
return bean;
}
In your test, setup your mocks like shown below before initializing the application context. Make sure to include the TestDoubleInjector as a bean in your test context.
TestDoubleInjector testDoubleInjector = new TestDoubleInjector()
testDoubleInjector.replaceBeanWithTestDouble('beanToReplace', mock(MyBean.class), MyBean.class)
It could be done using HotSwappableTargetSource
#WebAppConfiguration
#SpringApplicationConfiguration(TestApp)
#IntegrationTest('server.port:0')
class HelloSpec extends Specification {
#Autowired
#Qualifier('swappableHelloService')
HotSwappableTargetSource swappableHelloService
def "test mocked"() {
given: 'hello service is mocked'
def mockedHelloService = Mock(HelloService)
and:
swappableHelloService.swap(mockedHelloService)
when:
//hit endpoint
then:
//asserts
and: 'check interactions'
interaction {
1 * mockedHelloService.hello(postfix) >> { ""Mocked, $postfix"" as String }
}
where:
postfix | _
randomAlphabetic(10) | _
}
}
And this is TestApp (override the bean you want to mock with proxy)
class TestApp extends App {
//override hello service bean
#Bean(name = HelloService.HELLO_SERVICE_BEAN_NAME)
public ProxyFactoryBean helloService(#Qualifier("swappableHelloService") HotSwappableTargetSource targetSource) {
def proxyFactoryBean = new ProxyFactoryBean()
proxyFactoryBean.setTargetSource(targetSource)
proxyFactoryBean
}
#Bean
public HotSwappableTargetSource swappableHelloService() {
new HotSwappableTargetSource(new HelloService());
}
}
Have a look at this example https://github.com/sf-git/spock-spring

AOP Spring Before Advice not working

The method DefaultProduitGeneriqueService.valider is not catched by the method traceWhenReturnedValueDoesntExistOrNotNecessary and I don't understand why?
package fr.generali.nova.atp.service.metier.impl;
public class DefaultProduitGeneriqueService extends DefaultService implements IProduitGeneriqueService, IBacAware {
...
#Override
#Traceable(value = ETraceableMessages.VALIDATION_PRODUIT_GENERIQUE, hasReturnedValue=Traceable.HAS_NOT_RETURNS_VALUE)
public void valider(ElementNiveauUn element) {
...
}
...
}
package fr.generali.nova.atp.logging.advisor;
#Aspect
public class TraceableAdvisor {
#Before(value = "execution(* fr.generali.nova.atp.service.metier.impl.*.*(..)) && #annotation(traceable) && args(element)", argNames = "element,traceable")
public void traceWhenReturnedValueDoesntExistOrNotNecessary(ElementNiveauUn element, Traceable traceable) {
...
}
}
Assuming that the service interfaces are in package fr.generali.nova.atp.service.metier.api:
package fr.generali.nova.atp.service.metier.api;
public interface IProduitGeneriqueService {
void valider(ElementNiveauUn element);
}
And the service implementations are in package fr.generali.nova.atp.service.metier.impl:
package fr.generali.nova.atp.service.metier.impl;
public class DefaultProduitGeneriqueServiceImpl implements IProduitGeneriqueService {
#Override
#Traceable(value = ETraceableMessages.VALIDATION_PRODUIT_GENERIQUE, hasReturnedValue=Traceable.HAS_NOT_RETURNS_VALUE)
public void valider(ElementNiveauUn element) {
// TODO: implement
}
}
Your aspect should look like this:
package fr.generali.nova.atp.logging.advisor;
#Aspect
public class TraceableAdvisor {
#Before(value = "execution(* fr.generali.nova.atp.service.metier.api.*.*(..)) && #annotation(traceable) && args(element)", argNames = "element,traceable")
public void traceWhenReturnedValueDoesntExistOrNotNecessary(ElementNiveauUn element, Traceable traceable) {
// TODO: implement
System.err.println("traced...");
}
}
The default proxy strategy for Spring AOP is JDK interface-based proxies, so Your pointcut expression should match the interface method execution, not the implementation method execution, and Your poincut expression may match either interface mothod execution or implementation method execution.
And remember to include an AspectJAutoProxyCreator in your configuration using for example <aspectj-autoproxy /> tag.
And here is a simple test to prove everyting is working:
public class TraceableAdvisorTest {
#Configuration
public static class TestConfiguration {
#Bean
public IProduitGeneriqueService produitGeneriqueService() {
return new DefaultProduitGeneriqueServiceImpl();
}
#Bean
public TraceableAdvisor traceableAdvisor() {
return new TraceableAdvisor();
}
#Bean
public AnnotationAwareAspectJAutoProxyCreator autoProxyCreator() {
return new AnnotationAwareAspectJAutoProxyCreator();
}
}
private AnnotationConfigApplicationContext testApplicationContext;
#Test
public void testTraceWhenReturnedValueDoesntExistOrNotNecessary() {
this.testApplicationContext = new AnnotationConfigApplicationContext();
this.testApplicationContext.register(TestConfiguration.class);
this.testApplicationContext.refresh();
IProduitGeneriqueService service = BeanFactoryUtils.beanOfType(this.testApplicationContext, IProduitGeneriqueService.class);
System.err.println("BEFORE");
service.valider(null);
System.err.println("AFTER");
}
}
The err output is:
BEFORE
traced...
AFTER
For all combinations:
fr.generali.nova.atp.service.metier.api.*.*(..)
fr.generali.nova.atp.service.metier.impl.*.*(..)
fr.generali.nova.atp.service.metier..*.*(..)
Make sure both beans are properly configured, either through annotations or in your appCtx.
It looks like your Aspect is definitely right, but how about the other class? Is it Spring enabled?
Also, if both classes are indeed configured correctly, are you sure that the instance being passed is a Spring bean and not a "new" instance from a constructor?

Resources