I have created an adapterImpl class that will retry a method with an objA but if it throws an exception(hardcoded to throw) it will call recover method - which will again call the method with objB.
My problem is - The #Recover method is not called. I am not sure what I am doing wrong here.
Spring version - 4.3.5.RELEASE
Spring retry - 1.2.1.RELEASE
My Config class -
#Configuration
#EnableRetry
public class ConfigClass {
#Bean
public ClassTest beanA(){
ClassTest obj = new ClassTest();
obj.setProp(5);
return obj;
}
#Bean
public ClassTest beanB(){
ClassTest obj = new ClassTest();
obj.setProp(10);
return obj;
}
#Bean("adapterImpl")
public AdapterInterfaceImpl adapter(){
AdapterInterfaceImpl obj = new AdapterInterfaceImpl();
return obj;
}
}
My AdapterInterfaceImpl class -
public class AdapterInterfaceImpl implements AdapterInterface{
#Autowired
#Qualifier("beanA")
private ClassTest objA;
#Autowired
#Qualifier("beanB")
private ClassTest objB;
public ClassTest getObjA() {
return objA;
}
public void setObjA(ClassTest objA) {
this.objA = objA;
}
public ClassTest getObjB() {
return objB;
}
public void setObjB(ClassTest objB) {
this.objB = objB;
}
#Retryable(maxAttempts = 3, include = Exception.class, backoff = #Backoff(delay = 2000))
public int getValue(int val) throws Exception{
System.out.println("obj A get Value");
return getValue(objA,val);
}
public int getValue(ClassTest obj, int val) throws Exception{
System.out.println("get Value");
if(obj==objA){
throw new Exception("This is msg");
}
return obj.methodA(val);
}
#Recover
public int getValue(Exception e, int val){
System.out.println("Recover get Value");
try{
return getValue(objB,val);
}catch(Exception e1){
return 0;
}
}
My ClassTest class -
public class ClassTest {
private int prop;
public int getProp() {
return prop;
}
public void setProp(int prop) {
this.prop = prop;
}
public int methodA(int x){
return x+prop;
}
}
My class with main method -
public class App
{
public static void main( String[] args )
{
AbstractApplicationContext context = new
AnnotationConfigApplicationContext(ConfigClass.class);
AdapterInterface adapter = (AdapterInterface)
context.getBean("adapterImpl");
try {
System.out.println(adapter.getValue(3));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
My output is not showing any retry nor recovery -
A get Value
get Value
obj A get Value
get Value
obj A get Value
get Value
org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is java.lang.Exception: This is msg
Spring Retry uses AOP, internal calls (from getValue(int) to getValue(ClassTest, int)) won't go through the proxy.
You have to put the #Retryable on the method that is called externally so that the proxy can intercept the call and apply the retry logic.
There is a similar issue reported in https://github.com/spring-projects/spring-retry/issues/75
So #EnableRetry(proxyTargetClass=true) works as it is now able to find the recovery method in the implementation class.
Related
public void stop() {
.....
CountDownLatch latch = new CountDownLatch(this.smartMemberCount);
.....
try {
latch.await(this.timeout, TimeUnit.MILLISECONDS);
.......
}
Stop the specified bean as part of the given set of Lifecycle beans,
making sure that any beans that depends on it are stopped first.
#param lifecycleBeans a Map with bean name as key and Lifecycle instance as value
#param beanName the name of the bean to stop
private void doStop(Map<String, ? extends Lifecycle> lifecycleBeans, final String beanName,
final CountDownLatch latch, final Set<String> countDownBeanNames) {
......
String[] dependentBeans = getBeanFactory().getDependentBeans(beanName);
for (String dependentBean : dependentBeans) {
doStop(lifecycleBeans, dependentBean, latch, countDownBeanNames);
}
......
((SmartLifecycle) bean).stop(() -> {
latch.countDown();
countDownBeanNames.remove(beanName);
......
}
}
This Spring code, when spring find exist dependent Bean, it will run dependent bean before current bean.
I want to konw why spring use countDwonLatch?
I register SmartLifecycle bean exist Dependent Bean like this code.
Because stop2 depends on stop3. When spring close, stop3 run before stop2.If it is a single thread, countDwonLatch is equal to int. In Multi-thread,stop3 run ((SmartLifecycle) bean).stop() before stop2, countDownLatch value is 0 when stop3 not run. So, I think spring will close early,countDownLatch will invalid.
#Component
public class Stop1 implements SmartLifecycle {
.....
#Override
public void stop() {
System.err.println("0");
}
.....
public int getPhase() {
return 1;
}
}
#Component
public class Stop2 implements SmartLifecycle {
....
#Override
public void stop() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("2");
}
......
public int getPhase() {
return 1;
}
}
#DependsOn("stop2")
public class Stop3 implements SmartLifecycle {
......
#Override
public void stop() {
System.err.println("3");
}
.....
public int getPhase() {
return 0;
}
}
I am following this article to implement a database read/write separation feature by calling different methods. However, I got the error:
Missing method call for verify(mock) here: verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY);
when doing the testing.
My test case is trying to verify DatabaseEnvironment.READONLY has been set once when using TransactionReadonlyAspect AOP annotation:
// TransactionReadonlyAspectTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {LoadServiceImpl.class, TransactionReadonlyAspect.class})
public class TransactionReadonlyAspectTest {
#Autowired
private TransactionReadonlyAspect transactionReadonlyAspect;
#MockBean
private LoadServiceImpl loadService;
#Test
public void testReadOnlyTransaction() throws Throwable {
ProceedingJoinPoint mockProceedingJoinPoint = mock(ProceedingJoinPoint.class);
Transactional mockTransactional = mock(Transactional.class);
DatabaseContextHolder spyDatabaseContextHolder = mock(DatabaseContextHolder.class);
when(mockTransactional.readOnly()).thenReturn(true);
when(loadService.findById(16)).thenReturn(null);
when(mockProceedingJoinPoint.proceed()).thenAnswer(invocation -> loadService.findById(16));
transactionReadonlyAspect.proceed(mockProceedingJoinPoint, mockTransactional);
verify(spyDatabaseContextHolder, times(1)).set(DatabaseEnvironment.READONLY); // got the error: Missing method call for verify(mock)
verify(loadService, times(1)).findById(16);
assertEquals(DatabaseContextHolder.getEnvironment(), DatabaseEnvironment.UPDATABLE);
}
}
//TransactionReadonlyAspect.java
#Aspect
#Component
#Order(0)
#Slf4j
public class TransactionReadonlyAspect {
#Around("#annotation(transactional)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,
org.springframework.transaction.annotation.Transactional transactional) throws Throwable {
try {
if (transactional.readOnly()) {
log.info("Inside method " + proceedingJoinPoint.getSignature());
DatabaseContextHolder.set(DatabaseEnvironment.READONLY);
}
return proceedingJoinPoint.proceed();
} finally {
DatabaseContextHolder.reset();
}
}
}
// DatabaseContextHolder.java
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT = new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
DatabaseEnvironment context = CONTEXT.get();
System.out.println("context: " + context);
return CONTEXT.get();
}
public static void reset() {
CONTEXT.set(DatabaseEnvironment.UPDATABLE);
}
}
//DatabaseEnvironment.java
public enum DatabaseEnvironment {
UPDATABLE,READONLY
}
// LoadServiceImpl.java
#Service
public class LoadServiceImpl implements LoadService {
#Override
#Transactional(readOnly = true)
public LoadEntity findById(Integer Id) {
return this.loadDAO.findById(Id);
}
...
}
I just want to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) has been used once then in the TransactionReadonlyAspect finally block it will be reset to DatabaseEnvironment.UPDATABLE which make sense.
However, how to test DatabaseContextHolder.set(DatabaseEnvironment.READONLY) gets called once? Why does this error occur? Is there a better way to test TransactionReadonlyAspect?
I am trying to write and test an application that used spring-cloud with azure functions following this tutorial.
https://github.com/markusgulden/aws-tutorials/tree/master/spring-cloud-function/spring-cloud-function-azure/src/main/java/de/margul/awstutorials/springcloudfunction/azure
I am tryign to write a testcase and override the bean.
Here is the application class having function and handler Bean function.
#SpringBootApplication
#ComponentScan(basePackages = { "com.package" })
public class DataFunctions extends AzureSpringBootRequestHandler<GenericMessage<Optional<String>>, Data> {
#FunctionName("addData")
public HttpResponseMessage addDataRun(
#HttpTrigger(name = "add", methods = {
HttpMethod.POST }, authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) throws JsonParseException, JsonMappingException, IOException {
context.getLogger().info("Java HTTP trigger processed a POST request.");
try {
handleRequest(new GenericMessage<Optional<String>>(request.getBody()), context);
} catch (ServiceException ex) {
ErrorMessage em = new ErrorMessage();
return request.createResponseBuilder(handleException(ex, em)).body(em).build();
}
return request.createResponseBuilder(HttpStatus.CREATED).build();
}
#Autowired
MyService mService;
#Bean
public Consumer<GenericMessage<Optional<String>>> addData() {
ObjectMapper mapper = new ObjectMapper();
return req -> {
SomeModel fp = null;
try {
fp = mapper.readValue(req.getPayload().get(), SomeModel.class);
} catch (Exception e) {
throw new ServiceException(e);
}
mService.addData(fp);
};
}
}
I want to test by overriding the above bean.
Cosmosdb spring configuration
#Configuration
#EnableDocumentDbRepositories
public class CosmosDBConfig extends AbstractDocumentDbConfiguration {
#Value("${cosmosdb.collection.endpoint}")
private String uri;
#Value("${cosmosdb.collection.key}")
private String key;
#Value("${cosmosdb.collection.dbname}")
private String dbName;
#Value("${cosmosdb.connect.directly}")
private Boolean connectDirectly;
#Override
public DocumentDBConfig getConfig() {
ConnectionPolicy cp = ConnectionPolicy.GetDefault();
if (connectDirectly) {
cp.setConnectionMode(ConnectionMode.DirectHttps);
} else {
cp.setConnectionMode(ConnectionMode.Gateway);
}
return DocumentDBConfig.builder(uri, key, dbName).connectionPolicy(cp).build();
}
}
Here is the configuration
#TestConfiguration
#PropertySource(value = "classpath:application.properties", encoding = "UTF-8")
#Profile("test")
#Import({DataFunctions.class})
public class TestConfig {
#Bean(name="addData")
#Primary
public Consumer<GenericMessage<Optional<String>>> addData() {
return req -> {
System.out.println("data mock");
};
}
#Bean
#Primary
public DocumentDBConfig getConfig() {
return Mockito.mock(DocumentDBConfig.class);
}
}
Finally the test class
#RunWith(SpringRunner.class)
//#SpringBootTest //Enabling this gives initialization error.
#ActiveProfiles("test")
public class TempTest {
#InjectMocks
DataFunctions func;
#Mock
MyService mService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
private Optional<String> createRequestString(final String res) throws IOException {
InputStream iStream = TempTest.class.getResourceAsStream(res);
String charset="UTF-8";
try (BufferedReader br = new BufferedReader(new InputStreamReader(iStream, charset))) {
return Optional.of(br.lines().collect(Collectors.joining(System.lineSeparator())));
}
}
#Test
public void testHttpPostTriggerJava() throws Exception {
#SuppressWarnings("unchecked")
final HttpRequestMessage<Optional<String>> req = mock(HttpRequestMessage.class);
final Optional<String> queryBody = createRequestString("/test-data.json");
doNothing().when(mService).addData(Mockito.any(SomeModel.class));
doReturn(queryBody).when(req).getBody();
doAnswer(new Answer<HttpResponseMessage.Builder>() {
#Override
public HttpResponseMessage.Builder answer(InvocationOnMock invocation) {
HttpStatus status = (HttpStatus) invocation.getArguments()[0];
return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status);
}
}).when(req).createResponseBuilder(any(HttpStatus.class));
final ExecutionContext context = mock(ExecutionContext.class);
doReturn(Logger.getGlobal()).when(context).getLogger();
doReturn("addData").when(context).getFunctionName();
// Invoke
final HttpResponseMessage ret = func.addDataRun(req, context);
// Verify
assertEquals(ret.getStatus(), HttpStatus.CREATED);
}
}
For this case instead of test configuration addData the actual bean is called from DataFunctions class. Also the database connection is also created when it should use the mocked bean from my test configuration. Can somebody please point out what is wrong in my test configuration?
I was able to resolve the first part of cosmos db config loading by marking it with
#Configuration
#EnableDocumentDbRepositories
#Profile("!test")
public class CosmosDBConfig extends AbstractDocumentDbConfiguration {
...
}
Also had to mark the repository bean as optional in the service.
public class MyService {
#Autowired(required = false)
private MyRepository myRepo;
}
Didn't use any spring boot configuration other than this.
#ActiveProfiles("test")
public class FunctionTest {
...
}
For the second part of providing mock version of Mock handlers, I simply made the test config file as spring application as below.
#SpringBootApplication
#ComponentScan(basePackages = { "com.boeing.da.helix.utm.traffic" })
#Profile("test")
public class TestConfiguration {
public static void main(final String[] args) {
SpringApplication.run(TestConfiguration.class, args);
}
#Bean(name="addData")
#Primary
public Consumer<GenericMessage<Optional<String>>> addData() {
return req -> {
System.out.println("data mock");
};
}
}
and made use of this constructor from azure functions library in spring cloud in my constructor
public class AppFunctions
extends AzureSpringBootRequestHandler<GenericMessage<Optional<String>>, List<Data>> {
public AppFunctions(Class<?> configurationClass) {
super(configurationClass);
}
}
public AzureSpringBootRequestHandler(Class<?> configurationClass) {
super(configurationClass);
}
Hope it helps someone.
I am trying to leverage both the retry and circuit breaker mechanism of spring-retry.
I tried to use both annotations(#Retryable and #CircuitBreaker) in a particular function(like below), but Circuit Breaker was not working.
#Service
public class CommandAndRetry {
private static final Logger LOGGER = LoggerFactory.getLogger(SampleRetryService.class);
#CircuitBreaker(maxAttempts = 1, openTimeout = 10000)
#Retryable(
value = {TypeOneException.class},
maxAttempts = 3, backoff = #Backoff(2000))
public void retryWhenException() throws TypeOneException {
LOGGER.info("Retrying");
throw new TypeOneException();
}
#Recover
public void recover(Throwable t) throws Throwable {
LOGGER.info("SampleRetryService.recover");
throw t;
}
}
Then I tried dividing the functionality into two different functions, both having #Retryable and #CircuitBreaker respectively. In this case, retry mechanism was not working. Please find below code snippet.
PS: exec method(Circuit Breaker method) is invoked from a controller.
#Service
public class CommandAndRetry {
private static final Logger LOGGER = LoggerFactory.getLogger(SampleRetryService.class);
#CircuitBreaker(maxAttempts = 1, openTimeout = 10000)
public void exec() throws TypeOneException {
retryWhenException();
}
#Retryable(
value = {TypeOneException.class},
maxAttempts = 3, backoff = #Backoff(2000))
public void retryWhenException() throws TypeOneException {
LOGGER.info("Retrying");
throw new TypeOneException();
}
#Recover
public void recover(Throwable t) throws Throwable {
LOGGER.info("SampleRetryService.recover");
throw t;
}
}
Can anyone please tell why it's behaving like this.
Also please advise if there exists a better way to implement both retry and circuit-breaker.
PS: I neither want to use resilience4j nor retryTemplate.
If you want retry within circuit breaker, they must be in different beans. If you call one #Retryable directly from another, in the same bean, you will bypass the interceptor.
This works fine for me...
#SpringBootApplication
#EnableRetry
public class So52193237Application {
public static void main(String[] args) {
SpringApplication.run(So52193237Application.class, args);
}
#Bean
public ApplicationRunner runner(Foo foo) {
return args -> {
try {
foo.exec();
}
catch (Exception e) {
try {
foo.exec();
}
catch (Exception ee) {
Thread.sleep(11000);
try {
foo.exec();
}
catch (Exception eee) {
}
}
}
};
}
#Component
public static class Foo {
private static final Logger LOGGER = LoggerFactory.getLogger(Foo.class);
private final Bar bar;
public Foo(Bar bar) {
this.bar = bar;
}
#CircuitBreaker(maxAttempts = 1, openTimeout = 10000, resetTimeout=10000)
public void exec() throws TypeOneException {
LOGGER.info("Foo.circuit");
this.bar.retryWhenException();
}
#Recover
public void recover(Throwable t) throws Throwable {
LOGGER.info("Foo.recover");
throw t;
}
}
#Component
public static class Bar {
private static final Logger LOGGER = LoggerFactory.getLogger(Bar.class);
#Retryable(value = { TypeOneException.class }, maxAttempts = 3, backoff = #Backoff(2000))
public void retryWhenException() throws TypeOneException {
LOGGER.info("Retrying");
throw new TypeOneException();
}
#Recover
public void recover(Throwable t) throws Throwable {
LOGGER.info("Bar.recover");
throw t;
}
}
}
How do I write a unit test to verify async behavior using Spring 4 and annotations?
Since i'm used to Spring's (old) xml style), it took me some time to figure this out. So I thought I answer my own question to help others.
First the service that exposes an async download method:
#Service
public class DownloadService {
// note: placing this async method in its own dedicated bean was necessary
// to circumvent inner bean calls
#Async
public Future<String> startDownloading(final URL url) throws IOException {
return new AsyncResult<String>(getContentAsString(url));
}
private String getContentAsString(URL url) throws IOException {
try {
Thread.sleep(1000); // To demonstrate the effect of async
InputStream input = url.openStream();
return IOUtils.toString(input, StandardCharsets.UTF_8);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
Next the test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class DownloadServiceTest {
#Configuration
#EnableAsync
static class Config {
#Bean
public DownloadService downloadService() {
return new DownloadService();
}
}
#Autowired
private DownloadService service;
#Test
public void testIndex() throws Exception {
final URL url = new URL("http://spring.io/blog/2013/01/16/next-stop-spring-framework-4-0");
Future<String> content = service.startDownloading(url);
assertThat(false, equalTo(content.isDone()));
final String str = content.get();
assertThat(true, equalTo(content.isDone()));
assertThat(str, JUnitMatchers.containsString("<html"));
}
}
If you are using the same example in Java 8 you could also use the CompletableFuture class as follows:
#Service
public class DownloadService {
#Async
public CompletableFuture<String> startDownloading(final URL url) throws IOException {
CompletableFuture<Boolean> future = new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
getContentAsString(url);
future.complete(true);
return null;
});
return future;
}
private String getContentAsString(URL url) throws IOException {
try {
Thread.sleep(1000); // To demonstrate the effect of async
InputStream input = url.openStream();
return IOUtils.toString(input, StandardCharsets.UTF_8);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
Now the test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class DownloadServiceTest {
#Configuration
#EnableAsync
static class Config {
#Bean
public DownloadService downloadService() {
return new DownloadService();
}
}
#Autowired
private DownloadService service;
#Test
public void testIndex() throws Exception {
final URL url = new URL("http://spring.io/blog/2013/01/16/next-stop-spring-framework-4-0");
CompletableFuture<Boolean> content = service.startDownloading(url);
content.thenRun(() -> {
assertThat(true, equalTo(content.isDone()));
assertThat(str, JUnitMatchers.containsString("<html"));
});
// wait for completion
content.get(10, TimeUnit.SECONDS);
}
}
Please that when the time-out is not specified, and anything goes wrong the test will go on "forever" until the CI or you shut it down.