繁体   English   中英

从 CompletableFuture 线程中的主线程访问 SessionScoped bean 不起作用

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

我是 Spring 引导开发的新手。 我需要使用 CompletableFuture 并行运行几个任务,还需要从 CompletableFuture 线程中的主线程访问 SessionScoped bean。 根据尝试从 HelloService.completableFuture1() 调用 helloBean.getHelloMessage() 时的打击代码,它会停止进一步处理。 任何帮助,将不胜感激。

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());

问题出在不正确的 bean 存储 scope 中,因为我收到以下异常消息:

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.

为什么会发生,所有带有请求的 bean,session 范围都存储在ThreadLocal类中。 很少有 bean 存储,位于LocaleContextHolderRequestContextHolder中,这些上下文与DispatcherServlet相关,并且该 servlet 将执行对resetContextHolders initContextHolders每个请求。

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,服务),如果这些组件在与DispatcherServlet相同的线程中执行。

在我们的例子中,我们从正在执行DispatcherServlet的 thread(main) 启动新线程,这意味着新线程的 bean 存储已更改,新线程从@Async代理启动以执行我们的方法,因为线程直接与ThreadLocal class 连接因此,新线程中没有 session 范围 bean。

我们可以通过将属性setThreadContextInheritable设置为 true 来设置 bean 存储的可继承性,方法如下:

@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;
    }

}

并改变一点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);
    }

}

设置该属性后,所有新线程将从父线程继承 bean 存储,因此您的 session 范围 bean 将可用。 警告:该属性不能用于线程池,请参阅 java 文档:

    /**
     * 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您可以更改代码段:

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

    @Autowired
    private HelloBean helloBean;

Spring 支持注入 bean 的两种方式,从我的角度来看,第二个代码片段更像 spring 样式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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