我有一个完全注释驱动的Spring Boot 1.3.5应用程序,它有这个需要自动装配另一个服务bean的异步服务(并且将来它需要自动装配一个存储库bean,但我不是还有)为了执行一些业务逻辑:
@Service
public class AsyncService {
@Autowired
public HelpingService helpingService;
@Async
public Future<String> doFoo(String someArgument)
throws InterruptedException {
Thread.sleep(3000);
System.out.println("about to do Foo "+someArgument);
String result = "";
try {
result = helpingService.getSomeStuff(someArgument);
}
catch (Exception e) {
e.printStackTrace();
}
return new AsyncResult<String>(hello);
}
}
上面的方法是从@Controller bean调用的,它有其他端点(非异步),也可以按预期工作
@Controller
public class MyController extends BaseController {
@Autowired
HelpingService helpingService;
@Autowired
AsyncService asyncService;
@RequestMapping(method=RequestMethod.GET, value={"/rest/threads/getIp/{jobId}"}, produces={"application/json"})
public ResponseEntity<?> getLog(@PathVariable("jobId") String jobId) throws InterruptedException {
asyncService.doFoo(jobId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
这里是helpingService
的实现(它是一个界面),当我没有从@Async方法执行时,调用任何方法都可以正常工作以上:
@Service
@Validated
public class HelpingServiceImpl implements HelpingService {
@Autowired
HttpSession httpSession;
@Value(value="${projName}")
private String projName;
public String getServerAddress(){
AuthRegion region = (AuthRegion) httpSession.getAttribute("region");
if (region != null)
return region.getServerAddress();
else
return null;
}
@Override
public String getSomeStuff(String jobId) {
String responseString = "";
String projName = this.projName;
String serverAddress = getServerAddress(); // Code stops here with an exception
// Some code here that works fine outside this thread
return responseString;
}
}
这是被抓住的例外:
about to do Foo (267)
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.support.WebApplicationContextUtils.currentRequestAttributes(WebApplicationContextUtils.java:309)
at org.springframework.web.context.support.WebApplicationContextUtils.access$400(WebApplicationContextUtils.java:64)
at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:366)
at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:361)
at org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler.invoke(AutowireUtils.java:307)
at com.sun.proxy.$Proxy96.getAttribute(Unknown Source)
at corp.fernandopcg.myapp.service.ThreadServiceImpl.getRundeckServerPort(ThreadServiceImpl.java:45)
at corp.fernandopcg.myapp.service.ThreadServiceImpl.getJobExecutionOutput(ThreadServiceImpl.java:65)
at corp.fernandopcg.myapp.service.AsyncService.doFoo(AsyncService.java:40)
at corp.fernandopcg.myapp.service.AsyncService$$FastClassBySpringCGLIB$$7e164220.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
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)
我添加了(由于我无法在AsyncConfigurer
的同时延长SpringBootServletInitializer
而进行了一些更改,我必须抓住一个未提及的例外情况)taskExecutor
部分到我的应用程序主类如下,在this tutorial的指导下,看起来与我需要的相似,在我看来
@SpringBootApplication
@EnableAsync
@EnableJpaRepositories(repositoryFactoryBeanClass = DataTablesRepositoryFactoryBean.class)
public class MyApplication extends SpringBootServletInitializer implements AsyncConfigurer{
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("SomeRandomLookup-");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// TODO Auto-generated method stub
return null;
}
}
我可以告诉我的@Async
服务能够使用该应用程序的其他服务吗?因为如果不可能,我真的不会看到这些线程机制的使用。
答案 0 :(得分:5)
这很好地说明了请求范围注入可能存在问题的原因。您的HelpingServiceImpl
隐藏了特定于请求的HttpSession
,它看起来像一个字段,但实际上是Spring在每次调用时解析的代理,始终引用“当前”请求(使用线程局部变量。)
问题在于,通过调用@Async
,您将HelpingServiceImpl
调用与触发它的请求分开,并且不再存在与同一线程上的隐式连接允许它从全球背景中提取信息。
最直接的解决方法是让您的依赖项显式化 - 而不是让HelpingServiceImpl
直接从HttpSession
抓取区域,将区域作为方法参数传递给它。