Spring @Async Not Working

后端 未结 11 690
北荒
北荒 2020-12-08 06:30

An @Async method in a @Service-annotated class is not being called asynchronously - it\'s blocking the thread.

I\'ve got

相关标签:
11条回答
  • 2020-12-08 06:59

    @Async can not be used in conjunction with lifecycle callbacks such as @PostConstruct. To asynchonously initialize Spring beans you currently have to use a separate initializing Spring bean that invokes the @Async annotated method on the target then.

    public class SampleBeanImpl implements SampleBean {
    
      @Async
      void doSomething() { … }
    }
    
    
    public class SampleBeanInititalizer {
    
      private final SampleBean bean;
    
      public SampleBeanInitializer(SampleBean bean) {
        this.bean = bean;
      }
    
      @PostConstruct
      public void initialize() {
        bean.doSomething();
      }
    }
    

    source

    0 讨论(0)
  • 2020-12-08 07:02

    I realized following the tutorial async-method tutorial code that my issue source was: the bean with the annotated @Async method was not being created wrapped in a proxy. I started digging and realized that there was a message saying

    Bean 'NameOfTheBean' is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

    You can see here responses about this issue and its basically that BeanPostProcessors are required by every Bean, so every bean injected here and its dependencies will be excluded to be processed later by other BeanPostProcessors, because it corrupted the life cycle of beans. So identify which is the BeanPostProcessor that is causing this and dont use or create beans inside of it.

    In my case i had this configuration

    @EnableWs
    @Configuration
    public class WebServiceConfig extends WsConfigurerAdapter {
    
        @Autowired
        private Wss4jSecurityInterceptor securityInterceptor;
    
        @Autowired
        private DefaultPayloadLoggingInterceptor payloadLoggingInterceptor;
    
        @Override
        public void addInterceptors(List<EndpointInterceptor> interceptors) {
            interceptors.add(securityInterceptor);
            interceptors.add(payloadLoggingInterceptor);
        }
    }
    

    WsConfigurerAdapter is actually a BeanPostProcessor and you realize it because there is always a pattern: @Configuration that extends classes and override some of it functions to install or tweak beans involved in some non functional features, like web service or security.

    In the aforementioned example you have to override the addInterceptors and added interceptors beans, so if you are using some annotation like @Async inside DefaultPayloadLoggingInterceptor it wont work. What is the solution? Get ride of WsConfigurerAdapter to start. After digging a bit i realized a class named PayloadRootAnnotationMethodEndpointMapping at the end was which had all valid interceptors, so i did it manually insted of overriding a function.

    @EnableWs
    @Configuration
    public class WebServiceConfig {
    
        @Autowired
        private Wss4jSecurityInterceptor securityInterceptor;
    
        @Autowired
        private DefaultPayloadLoggingInterceptor payloadLoggingInterceptor;
    
        @Autowired
        public void setupInterceptors(PayloadRootAnnotationMethodEndpointMapping endpointMapping) {
            EndpointInterceptor[] interceptors = {
                    securityInterceptor,
                    payloadLoggingInterceptor
            };
    
            endpointMapping.setInterceptors(interceptors);
        }
    }
    

    So this will be run after all BeanPostProcessor have done their job. The setupInterceptors function will run when that party is over and install the interceptors beans. This use case may be extrapolated to cases like Security.

    Conclusions:

    • If you are using a @Configuration extending from some class that runs some given functions automatically and you override them, you are probably inside of a BeanPostProcessor, so dont inject beans there and try to use AOP behaviour, because it wont work, and you will see Spring tells it to you with the beforementioned message in the console. In those cases dont use beans but objects (using the new clause).
    • If you need to use beans digg about which class is carrying the beans you want to setup at the end, @Autowired it and add those beans like i did before.

    I hope this may save some time for you.

    0 讨论(0)
  • 2020-12-08 07:06

    With the help of this excellent answer by Ryan Stewart, I was able to figure this out (at least for my specific problem).

    In short, the context loaded by the ContextLoaderListener (generally from applicationContext.xml) is the parent of the context loaded by the DispatcherServlet (generally from *-servlet.xml). If you have the bean with the @Async method declared/component-scanned in both contexts, the version from the child context (DispatcherServlet) will override the one in the parent context (ContextLoaderListener). I verified this by excluding that component from component scanning in the *-servlet.xml -- it now works as expected.

    0 讨论(0)
  • 2020-12-08 07:06

    In my case the @Async method was defined in same class as the sync method that used it, and that apparently caused all jobs to hang on current thread.

    Bad

    @Component
    @EnableAsync
    public class TranslationGapiReader {
        @Async
        public CompletableFuture<GapiFile> readFile(String fileId) {
            try { Thread.sleep(2000); } catch (Exception exc) { throw new RuntimeException("ololo", exc); }
            return CompletableFuture.completedFuture(null);
        }
    
        public Stream<GapiFile> readFiles(Iterable<String> fileIds) {
            List<CompletableFuture<GapiFile>> futures = new ArrayList<>();
            for (String fileId: fileIds) {
                futures.add(readFile(fileId));
            }
            return Stream.empty();
        }
    }
    

    Good

    @Component
    @EnableAsync
    public class AsyncGapiFileReader {
        @Async
        public CompletableFuture<TranslationGapiReader.GapiFile> readFile(String fileId) {
            try { Thread.sleep(2000); } catch (Exception exc) { throw new RuntimeException("ololo", exc); }
            return CompletableFuture.completedFuture(null);
        }
    }
    
    @Component
    @EnableAsync
    public class TranslationGapiReader {
        @Autowired
        AsyncGapiFileReader asyncGapiFileReader;
    
        public Stream<GapiFile> readFiles(Iterable<String> fileIds) {
            List<CompletableFuture<GapiFile>> futures = new ArrayList<>();
            for (String fileId: fileIds) {
                futures.add(asyncGapiFileReader.readFile(fileId));
            }
            return Stream.empty();
        }
    }
    

    I'm not Spring guru enough to understand why does it only work when the @Async method is in a different class, but that's what fixes the issue from my observations.

    0 讨论(0)
  • 2020-12-08 07:11

    Try below: 1. In config create bean for ThreadPoolTaskExecutor

    @Bean(name = "threadPoolTaskExecutor")
        public Executor threadPoolTaskExecutor() {
            return new ThreadPoolTaskExecutor();
        }
    

    2. In service method where @Async is used add

    @Async("threadPoolTaskExecutor")
        public void asyncMethod(){
        //do something
        }
    

    This should get @Async working.

    0 讨论(0)
  • 2020-12-08 07:12
    1. Try adding proxy-target-class="true" to all <*:annotation-driven/> elements that support this attribute.
    2. Check if your method annotated with @Async is public.
    0 讨论(0)
提交回复
热议问题