0
votes

I have one spring component bean which contains a method methodA defined by @HystrixCommand with fallbackMethod. The bean has another method methodB invokes methodA by CompletableFuture.supplyAsync(...). I expect Hystrix javanica will weave the aspect on methodA, but when I debug it, I didn't see hystrix aspect is weaved.

Here are some of the main sudo code,

TestApplication:

package com.my.own.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.my.own.test")
public class TestApplication {
    public static void main(final String[] args) throws Exception {
        SpringApplication.run(TestApplication.class, args);
    }
}

ApplicationConfiguration:

package com.my.own.test;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableConfigurationProperties
@EnableCircuitBreaker
public class ApplicationConfig {

}

AsyncConfig:

package com.my.own.test;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfig {

   @Value("${spring.threadpool.executor.core:10}")
   private int corePoolSize;

   @Value("${spring.threadpool.executor.max:20}")
   private int maxPoolSize;

   @Value("${spring.threadpool.executor.queue:1000}")
   private int queueCapacity;

   @Value("${spring.threadpool.executor.timeout:true}")
   private boolean coreThreadTimeOut;

   @Value("${spring.threadpool.executor.keepalive:30000}")
   private int keepAlive;

   @Value("${spring.threadpool.executor.prefix:ThreadPoolTaskExecutor}")
   private String threadNamePrefix;

   @Bean("taskExecutor")
   public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
       final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(corePoolSize);
       executor.setMaxPoolSize(maxPoolSize);
       executor.setQueueCapacity(queueCapacity);
       executor.setAllowCoreThreadTimeOut(coreThreadTimeOut);
       executor.setKeepAliveSeconds(keepAlive);
       executor.setThreadNamePrefix(threadNamePrefix + "-");
       executor.initialize();

       return executor;
   }
}

TestController:

package com.my.own.test.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.my.own.test.core.TestProcessor;

@RestController
public class TestController {

    private static Logger logger = LoggerFactory.getLogger(TestController.class);

    @Autowired
    TestProcessor tester;

    @RequestMapping(value = "/test", method = { RequestMethod.POST })
    public void test() {
        tester.methodB();
    }
}

TestProcessor:

package com.my.own.test.core;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@Component
public class TestProcessor {

    @Autowired
    private ThreadPoolTaskExecutor executor;

    public void methodB() {
        final List<CompletableFuture<String>> a = new ArrayList<>();
        a.add(CompletableFuture.supplyAsync(() -> methodA(), executor));
        CompletableFuture.allOf(a.toArray(new CompletableFuture[a.size()])).join();
    }

    @HystrixCommand(fallbackMethod = "deferPushdown")
    public String methodA() {
        if (true) {
            throw new RuntimeException();
        } else {
            return "methodA";
        }
    }

    public String deferMethodA() {
        return "deferMethodA";
    }
}

Run output

Jan 03, 2018 2:55:55 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.util.concurrent.CompletionException: java.lang.RuntimeException] with root cause
java.lang.RuntimeException
    at com.my.own.test.core.TestProcessor.methodA(TestProcessor.java:40)
    at com.my.own.test.core.TestProcessor.lambda$0(TestProcessor.java:33)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

P.S. From the output, methodA is not invoked from hystrix aspect, but by lambda directly. Is this an issue on Hystrix or javanica? Please share if you know a solution. I appreciate it.

1
Are you using aspectj weaving or regular interface/cglib aspects? - kewne
I use spring aop, so I am assuming it's aspectj. I have other aspects weaved on other methods, and they are working fine. on ApplicationConfiguration, I have defined @EnableAspectJAutoProxy(exposeProxy = true). - davy_wei
What I mean is have you configured aspectj specific stuff like weaving? If not it defaults to the standard spring interface/cglib weaving which I think explains the behavior. - kewne

1 Answers

1
votes

Unless you’re using aspectj weaving (which requires special handling of compile steps, I think), spring defaults to using interface/cglib weaving, which only applies to the first method called from outside the class, as described in https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-understanding-aop-proxies.

In summary, if you call methodB, no aspect applies and the call from methodB to methodA is not eligible for aspect interception. To activate the aspect you have to call methodB directly from outside the TestProcessor class.