I am new to Spring Boot develpment. I need to run few tasks in parallel using CompletableFuture also need to access SessionScoped bean from main thread within the CompletableFuture thread. Based on the blow code when it tries to call helloBean.getHelloMessage() from HelloService.completableFuture1() it stops processing further. Any help would be appreciated.
SessionScopeWithCfApplication.java
@EnableAsync
@SpringBootApplication
public class SessionScopeWithCfApplication {
public static void main(String[] args) {
SpringApplication.run(SessionScopeWithCfApplication.class, args);
}
}
=====
HelloBean.java
public class HelloBean {
private String helloMessage;
public String getHelloMessage() {
return helloMessage;
}
public void setHelloMessage(String helloMessage) {
this.helloMessage = helloMessage;
}
}
=====
HelloBeanScopeConfig.java
@Configuration
public class HelloBeanScopeConfig {
@Bean
//@SessionScope
//@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloBean helloBean() {
return new HelloBean();
}
}
=====
HelloController.java
@Controller
public class HelloController {
@Resource(name = "helloBean")
HelloBean helloBean;
@RequestMapping(value = {"/"}, method = RequestMethod.GET)
public String home(Model model, HttpServletRequest request) {
System.out.println("HelloController.home() - helloBean.getHelloMessage() = " + helloBean.getHelloMessage());
helloBean.setHelloMessage("Welcome");
System.out.println("HelloController.home() - helloBean.getHelloMessage() = " + helloBean.getHelloMessage());
return "index";
}
}
=====
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Login</title>
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/3.3.6/css/bootstrap.min.css}" />
<script type='text/javascript' th:src="@{/webjars/jquery/1.9.1/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/webjars/bootstrap/3.3.6/js/bootstrap.min.js}"></script>
<script type='text/javascript'>
function getHelloMessage() {
return $.ajax({
url: '/gethellomessage',
method: 'get',
contentType: "application/json; charset=utf-8",
});
};
$(document).ready(function() {
$('#btn').on('click', function () {
getHelloMessage();
});
});
</script>
</head>
<body>
<div>
<button id="btn" type="submit" class="btn btn-primary">Click Me</button>
</div>
</body>
</html>
=====
HelloRestController.java
@RestController
public class HelloRestController {
@Autowired
HelloService helloService;
@Resource(name = "helloBean")
HelloBean helloBean;
@RequestMapping(value = "/gethellomessage", method = RequestMethod.GET)
public ResponseEntity getHelloMessage() {
try {
System.out.println("HelloRestController.getHelloMessage() - helloBean.getHelloMessage() = " + helloBean.getHelloMessage());
helloService.completableFuture1();
//CompletableFuture.allOf(helloService.completableFuture1()).get();
return new ResponseEntity(HttpStatus.OK);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
}
=====
HelloService.java
@Service
public class HelloService {
@Resource(name = "helloBean")
HelloBean helloBean;
@Async
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CompletableFuture<Void> completableFuture1() {
System.out.println("executing completableFuture1 by - "+Thread.currentThread().getName());
try {
System.out.println("completableFuture1 - helloBean.getHelloMessage() = " + helloBean.getHelloMessage());
Thread.sleep(5000);
System.out.println("Done completableFuture1");
} catch (Exception e) {
throw new RuntimeException((new Exception().getStackTrace()[0].getMethodName()) + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
return CompletableFuture.completedFuture(null);
}
}
Output:
HelloController.home() - helloBean.getHelloMessage() = null
HelloController.home() - helloBean.getHelloMessage() = Welcome
HelloRestController.getHelloMessage() - helloBean.getHelloMessage() = Welcome
executing completableFuture1 by - task-1
It is not printing value from HelloService.completableFuture1() for the below command and stops processing at this stage:
System.out.println("completableFuture1 - helloBean.getHelloMessage() = " + helloBean.getHelloMessage());
The issue is in incorrect bean storage scope, because I got following exception with message:
Why it happened, all beans with request, session scopes are stored in
ThreadLocalclasses. There are few bean storages, which are located inLocaleContextHolderandRequestContextHolder, and these contexts are related withDispatcherServletand that servlet will execute each request forinitContextHoldersandresetContextHolders.When request come to server, server provides thread from pool and
DispatcherServletstarts executing request in that thread, which receives session scoped beans from http session and creates new instances of request scoped beans, and all these beans are available in any spring component(Controller, Service) if these components are executing in the same thread asDispatcherServlet.In our case we are starting new thread from thread(main) in which is executing
DispatcherServlet, it means, bean storages have been changed for new thread, which was started from@Asyncproxy for executing our method, because thread directly connected withThreadLocalclass and as result there is not session scoped bean in new thread.We can setup inheritability for bean storages via setting property
setThreadContextInheritablein true, in following way:And change little bit
HelloService:After setting that property all new thread will inherit bean storages from parent thread, as result your session scoped bean will be available. WARNING: that property you cannot use with thread pool, see java doc:
P.S. You can change code snippet:
on
Spring supports both ways for injecting beans, from my point of view the second code snippet is more spring styled.