在 amqp rpc 客户端中创建一个 spring 会话

Create a spring session in amqp rpc client

我开发了 spring 远程 amqp rpc 应用程序。

这对于不将 bean 与 Scope SESSION 一起使用的方法非常有效。

对于其他方法,客户端无法使用spring会话,我得到这个异常

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.userSession': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is 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.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:362) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at io.kzreactive.akwtype.akwtypeback.common.service.UserSession$$EnhancerBySpringCGLIB$d53e95.setUser(<generated>) ~[classes/:na]
    at io.kzreactive.akwtype.akwtypeback.engine.service.AppService.login(AppService.kt:30) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.remoting.support.RemoteInvocation.invoke(RemoteInvocation.java:215) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.remoting.support.DefaultRemoteInvocationExecutor.invoke(DefaultRemoteInvocationExecutor.java:39) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at io.kzreactive.akwtype.akwtypeback.gateway.rabbitmq.SessionDefaultRemoteInvocationExecutor.invoke(RabbitMQSession.kt:48) ~[classes/:na]
    at org.springframework.remoting.support.RemoteInvocationBasedExporter.invoke(RemoteInvocationBasedExporter.java:78) [spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.remoting.support.RemoteInvocationBasedExporter.invokeAndCreateResult(RemoteInvocationBasedExporter.java:114) [spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter.onMessage(AmqpInvokerServiceExporter.java:80) [spring-amqp-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1457) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1348) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1324) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1303) [spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:785) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:769) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access0(SimpleMessageListenerContainer.java:77) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1010) ~[spring-rabbit-2.0.3.RELEASE.jar:2.0.3.RELEASE]
    at java.base/java.lang.Thread.run(Thread.java:844) ~[na:na]
Caused by: 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) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.context.request.SessionScope.get(SessionScope.java:55) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:350) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    ... 24 common frames omitted

所以我会在 rabbit mq

上创建和使用 spring 会话

首先,我设法在 RPC 调用中传递了 sessionId

在服务器上我添加了一个属性

class SessionDefaultRemoteInvocationFactory : DefaultRemoteInvocationFactory() {

    override fun createRemoteInvocation(methodInvocation: MethodInvocation?): RemoteInvocation {
        return super.createRemoteInvocation(methodInvocation)
                .apply { addAttribute("sessionId", RequestContextHolder.currentRequestAttributes().sessionId) }
    }
}

在客户端我可以阅读

class SessionDefaultRemoteInvocationExecutor : DefaultRemoteInvocationExecutor() {

    override fun invoke(invocation: RemoteInvocation?, targetObject: Any?): Any {

        if (invocation is RemoteInvocation) {
            invocation.getAttribute("sessionId")?.let {

                val sessionId = it.toString()

                SecurityContextHolder.getContext().authentication = AnonymousAuthenticationToken(sessionId,
                        "anonymousUser", listOf(SimpleGrantedAuthority("ROLE_ANONYMOUS")))

                val attr = RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes // <= ERROR here
                attr.request.getSession(true)
            }
        }

        return super.invoke(invocation, targetObject)
    }
}

但我无法使用它来创建 spring 会话

如何在此非 http 上下文中创建 spring 会话

我尝试创建一个请求上下文侦听器

@Configuration
@WebListener
class MyRequestContextListener : RequestContextListener()

但同样的错误

目前我绕过 spring 会话并注入了一个具有相同行为的 bean

@Component
@Scope("singleton")
class EngineThread(var engineSession: ThreadLocal<EngineSession> = ThreadLocal()) : IEngineSession by engineSession.getOrSet( { EngineSession() })

@Component
@Profile("engine & !test")
class SessionDefaultRemoteInvocationExecutor(val engineThread: EngineThread) : DefaultRemoteInvocationExecutor() {

    val engineSessionStore : MutableMap<String, EngineSession> = mutableMapOf()

    override fun invoke(invocation: RemoteInvocation?, targetObject: Any?): Any {

        if (invocation is RemoteInvocation) {
            invocation.getAttribute(SESSION_ID)?.let {

                val sessionId = it.toString()

                SecurityContextHolder.getContext().authentication = AnonymousAuthenticationToken(sessionId,
                        "anonymousUser", listOf(SimpleGrantedAuthority("ROLE_ANONYMOUS")))

                if (! engineSessionStore.contains(sessionId)) {
                    engineSessionStore.put(sessionId, EngineSession())
                }
                engineThread.engineSession.set(engineSessionStore.getValue(sessionId))

            }
        }

        return super.invoke(invocation, targetObject)
    }
}

工作已完成,但我对这个解决方案不是很满意 (我将通过 softreference 添加 clean ......但所有这些工作都是重新发明会话而不是使用 spring 会话)