简体   繁体   中英

Accessing SessionScoped bean from main thread within the CompletableFuture thread is not working

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:

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: In this case, 
use RequestContextListener or RequestContextFilter to expose the current request.

Why it happened, all beans with request, session scopes are stored in ThreadLocal classes. There are few bean storages, which are located in LocaleContextHolder and RequestContextHolder , and these contexts are related with DispatcherServlet and that servlet will execute each request for initContextHolders and resetContextHolders .

When request come to server, server provides thread from pool and DispatcherServlet starts 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 as DispatcherServlet .

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 @Async proxy for executing our method, because thread directly connected with ThreadLocal class and as result there is not session scoped bean in new thread.

We can setup inheritability for bean storages via setting property setThreadContextInheritable in true, in following way:

@Configuration
public class WebConfig {

    @Bean
    @SessionScope
    public HelloBean helloBean() {
        return new HelloBean();
    }

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setThreadContextInheritable(true);
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

}

And change little bit HelloService :

@Service
public class HelloService {

    @Autowired
    private HelloBean helloBean;

    @Async
    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(e);
        }
        return CompletableFuture.completedFuture(null);
    }

}

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:

    /**
     * Set whether to expose the LocaleContext and RequestAttributes as inheritable
     * for child threads (using an {@link java.lang.InheritableThreadLocal}).
     * <p>Default is "false", to avoid side effects on spawned background threads.
     * Switch this to "true" to enable inheritance for custom child threads which
     * are spawned during request processing and only used for this request
     * (that is, ending after their initial task, without reuse of the thread).
     * <p><b>WARNING:</b> Do not use inheritance for child threads if you are
     * accessing a thread pool which is configured to potentially add new threads
     * on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}),
     * since this will expose the inherited context to such a pooled thread.
     */
    public void setThreadContextInheritable(boolean threadContextInheritable) {
        this.threadContextInheritable = threadContextInheritable;
    }

PS You can change code snippet:

    @Resource(name = "helloBean")
    private HelloBean helloBean;

on

    @Autowired
    private HelloBean helloBean;

Spring supports both ways for injecting beans, from my point of view the second code snippet is more spring styled.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM