是否可以创建一个工厂或代理来决定线程是在(Web)请求还是后台进程(即调度程序)中运行,然后根据该信息创建会话bean或原型bean?
示例(伪Spring配置:)
<bean id="userInfoSession" scope="session" />
<bean id="userInfoStatic" scope="prototype" />
<bean id="currentUserInfoFactory" />
<bean id="someService" class="...">
<property name="userInfo" ref="currentUserInfoFactory.getCurrentUserInfo()" />
</bean>
我希望这会让我的问题更容易理解......
我的解决方案
更新自己的问题永远不会迟到;)。我用两个不同的客户端会话实例,一个SessionScoped客户端会话和一个SingletonScoped会话解决了它。两者都是正常豆类。
<bean id="sessionScopedClientSession" class="com.company.product.session.SessionScopedClientSession" scope="session">
<aop:scoped-proxy />
</bean>
<bean id="singletonScopedClientSession" class="com.company.product.session.SingletonScopedClientSession" />
<bean id="clientSession" class="com.company.product.session.ClientSession">
<property name="sessionScopedClientSessionBeanName" value="sessionScopedClientSession" />
<property name="singletonScopedClientSessionBeanName" value="singletonScopedClientSession" />
</bean>
然后,ClientSession将决定单例或会话范围:
private IClientSession getSessionAwareClientData() {
String beanName = (isInSessionContext() ? sessionScopedClientSessionBeanName : singletonScopedClientSessionBeanName);
return (IClientSession) ApplicationContextProvider.getApplicationContext().getBean(beanName);
}
可以通过以下方式收集会话类型:
private boolean isInSessionContext() {
return RequestContextHolder.getRequestAttributes() != null;
}
所有类都实现了一个名为IClientSession的接口。 singletonScoped和sessionScoped bean都是从发现实现的BaseClientSession扩展而来。
然后,每个服务都可以使用客户端会话,即:
@Resource
private ClientSession clientSession;
...
public void doSomething() {
Long orgId = clientSession.getSomethingFromSession();
}
现在,如果我们更进一步,我们可以为会话编写类似于模拟器的东西。这可以通过初始化单个会话的clientSession(在请求的上下文中)来完成。现在所有服务都可以使用相同的clientSession,我们仍然可以“模拟”用户,即:
clientSessionEmulator.startEmulateUser( testUser );
try {
service.doSomething();
} finally {
clientSessionEmulator.stopEmulation();
}
还有一条建议:在SingletonScoped clientSession实例中注意线程化!哇,我以为我能用更少的线条做到这一点;)如果您想了解更多有关此方法的信息,请随时与我联系。
答案 0 :(得分:3)
我创建了一个小的通用解决方法来注入bean取决于上下文。
猜猜我们有两个豆子:
<bean class="xyz.UserInfo" id="userInfo" scope="session" />
<bean class="xyz.UserInfo" id="userInfoSessionLess" />
我们希望将“userInfo”bean用于Web用户操作,将“userInfoSessionLess”bean用于后台服务。 Wa也想编写代码而不想考虑上下文,例如:
@Autowired
//You will get "java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request?" for session less services.
//We can fix it and autowire "userInfo" or "userInfoSessionLess" depends on context...
private UserInfo userInfo;
public save(Document superSecureDocument) {
...
superSecureDocument.lastModifier = userInfo.getUser();
...
}
现在我们需要创建自定义会话范围以使其工作:
public class MYSessionScope extends SessionScope implements ApplicationContextAware {
private static final String SESSION_LESS_POSTFIX = "SessionLess";
private ApplicationContext applicationContext;
public Object get(String name, ObjectFactory objectFactory) {
if (isInSessionContext()) {
log.debug("Return session Bean... name = " + name);
return super.get(name, objectFactory);
} else {
log.debug("Trying to access session Bean outside of Request Context... name = " + name + " return bean with name = " + name + SESSION_LESS_POSTFIX);
return applicationContext.getBean(name.replace("scopedTarget.", "") + SESSION_LESS_POSTFIX);
}
}
private boolean isInSessionContext() {
return RequestContextHolder.getRequestAttributes() != null;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
注册新范围:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="mySession">
<bean class="com.galantis.gbf.web.MYSessionScope" />
</entry>
</map>
</property>
</bean>
现在我们需要修改这样的bean定义:
<bean class="xyz.UserInfo" id="userInfo" scope="mySession" autowire-candidate="true"/>
<bean class="xyz.UserInfo" id="userInfoSessionLess" autowire-candidate="false"/>
这就是全部。如果我们在实际的Web请求线程之外使用bean,那么名为“SessionLess”的Bean将用于所有“mySession”作用域bean。
答案 1 :(得分:2)
你的改述确实相当简单:)
您的currentUserInfoFactory
可以使用RequestContextHolder.getRequestAttributes()
。如果会话存在并与调用线程关联,那么这将返回一个非null对象,然后您可以安全地从上下文中检索会话范围的bean。如果它返回null,那么你应该获取原型范围的bean。
它不是很整洁,但它很简单,而且应该有用。
答案 2 :(得分:1)
创建两个自定义上下文加载器,将相同的范围定义绑定到不同的实现:
public final class SessionScopeContextLoader extends GenericXmlContextLoader {
protected void customizeContext(final GenericApplicationContext context) {
final SessionScope testSessionScope = new SessionScope();
context.getBeanFactory().registerScope("superscope", testSessionScope);
}
...
}
然后你为单身人士制作一个相应的单身人士(用静态制作自己的范围)
然后,您只需在xml启动中为两个上下文中的每一个指定适当的上下文加载器。