Метод с аннотацией @Async с вызовом шаблона Rest в приложении Springboot

#java #spring #spring-boot #asynchronous #resttemplate

Вопрос:

У меня проблема с вызовом шаблона @Async и Rest; вот мой основной класс приложения с компонентом исполнителя задач и аннотацией EnableAsync

 @SpringBootApplication
@ComponentScan({"org.***"})
@EnableAspectJAutoProxy
@EnableAsync
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class})
@EnableFeignClients(basePackages = {"org.service.feign"})
public class MainApplication extends SpringBootServletInitializer {

    /**
     * <p>main.</p>
     *
     * @param args an array of {@link java.lang.String} objects.
     */
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }

    @Bean(name = "threadPoolTaskExecutor")
      public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix(“CustomLookup-");
        executor.initialize();
        return executor;
      }
    /**
     * Configure.
     *
     * @param application the application
     * @return the spring application builder
     */
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(MainApplication.class);
    }
    
    /**
     * <p>requestContextListener.</p>
     *
     * @return a {@link org.springframework.web.context.request.RequestContextListener} object.
     */
    @Bean
    public RequestContextListener requestContextListener() {
        return new RequestContextListener();
   }
}
 

это моя тестовая служба с асинхронной аннотацией, которую я вызываю в контроллере Rest:

 @Service
public class TestService {

   

    @Async("threadPoolTaskExecutor")
    public ResponseEntity<Object> test()
            throws Exception{
        PagedRequest<SearchRequest> request = new PagedRequest<SearchRequest>();

        SearchRequest filters = new Request();

        filters.setCod(“abcdeg");

        request.setFilters(filters);
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(Collections.singletonList(new RestInterceptor()));  // here I set a custom headers
        final HttpHeaders theJsonHeader = new HttpHeaders();
        theJsonHeader.setContentType(MediaType.APPLICATION_JSON);
        final MultiValueMap<String, Object> theMultipartRequest = new LinkedMultiValueMap<>();
        ObjectMapper objectMapper = new ObjectMapper();
        String someJsonString = objectMapper.writeValueAsString(request);
        theMultipartRequest.add("request", new HttpEntity<>(someJsonString, theJsonHeader));
        ResponseEntity<Object> response = null;
        final HttpEntity<PagedRequest<SearchRequest>> theHttpEntity = new HttpEntity<>(request, theJsonHeader);

        String path = “http://...”; //url removed for privacy

        response = restTemplate.postForEntity(path, theHttpEntity, Object.class);
        return response;
        
    }

}
 

Эта служба возвращает нулевой указатель в шаблоне rest; это трассировка стека

 java.lang.NullPointerException: null
    at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.java:61) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:773) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:743) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:677) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:452) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.REL
 

Вот код моей концепции RestIntercept, который я добавляю в свою табличку RestTemplate

 public class RestInterceptor implements ClientHttpRequestInterceptor {

   
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null)
            return null;
        
        HttpServletRequest req = requestAttributes.getRequest();
        if (req == null || req.getHeader(PrevConstants.USER_KEY)==null || req.getHeader(PrevConstants.JWT_HEADER_NAME)==null)
            return null;


        String userKey = req.getHeader(PrevConstants.USER_KEY);

        String jwt = req.getHeader(PrevConstants.JWT_HEADER_NAME);
        
        if (jwt == null) {
            jwt = "custom jwt";
        }
       
        else if ( !jwt.startsWith("Bearer")) {                     jwt = "Bearer "   jwt;                 }

        request.getHeaders().set(PrevConstants.USER_KEY, userKey);
        request.getHeaders().set(PrevConstants.JWT_HEADER_NAME, jwt);         
        
        ClientHttpResponse response = execution.execute(request, body);
        return response;
    }
}
 

но если я удалю @EnableAsync и @Async, простой шаблон Rest будет работать идеально.

Когда я передаю запрос HttpServlet, отладка Eclipse показывает это:

введите описание изображения здесь В чем проблема? Я не знаю. Спасибо вам за ответы

Комментарии:

1. НПЭ возникает из-за того, что реакция такова null . Этого не должно было случиться. Можете ли вы поделиться кодом для RestInterceptor ? Это может быть связано с этим.

2. @AndyWilkinson я отредактировал свой ответ с помощью кода RestInterceptor

Ответ №1:

RequestContextHolder содержит контекст для запроса, обрабатываемого текущим потоком. Когда вы используете @Async свой перехватчик, он вызывается в потоке, отличном от того, который обрабатывает запрос. В результате RequestContextHolder.getRequestAttributes() возвращается null , и ваш перехватчик затем возвращает null ответ. Чтобы соответствовать контракту ClientHttpRequestInterceptor#intercept , он должен возвращать ненулевое значение, поэтому этот null ответ вызывает сбой.

Если вы хотите использовать @Async , вам нужно будет получить RequestAttributes данные в вашем контроллере REST, а затем передать их в свой TestService в качестве параметра test метода. Затем вы могли бы создать свой RestInterceptor с атрибутами, а не использовать RequestContextHolder его для доступа к ним:

 @Async("threadPoolTaskExecutor")
public ResponseEntity<Object> test(RequestAttributes requestAttributes) throws Exception {
    // …
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setInterceptors(Collections.singletonList(new RestInterceptor(requestAttributes)));
    // …
}