问题描述
我正在阅读 JBoss 其中使用 @RequestScoped
bean 备份 JSF 页面
用于传递用户凭据信息,然后将其保存在 @sessionScoped bean
中.以下是来自 JBoss 文档的示例.
@Named @RequestScoped公共类凭据{私人字符串用户名;私人字符串密码;@NotNull @Length(min=3, max=25)公共字符串 getUsername() { 返回用户名;}public void setUsername(String username) { this.username = username;}@NotNull @Length(min=6, max=20)public String getPassword() { 返回密码;}public void setPassword(String password) { this.password = password;}}
JSF 形式:
<h:panelGrid columns="2" render="#{!login.loggedIn}"><f:validateBean><h:outputLabel for="username">用户名:</h:outputLabel><h:inputText id="username" value="#{credentials.username}"/><h:outputLabel for="password">密码:</h:outputLabel><h:inputSecret id="password" value="#{credentials.password}"/></f:validateBean></h:panelGrid><h:commandButton value="Login" action="#{login.login}" render="#{!login.loggedIn}"/><h:commandButton value="Logout" action="#{login.logout}" render="#{login.loggedIn}"/></h:form>
用户实体:
@Entity公共类用户{private @NotNull @Length(min=3, max=25) @Id String username;private @NotNull @Length(min=6, max=20) 字符串密码;公共字符串 getUsername() { 返回用户名;}public void setUsername(String username) { this.username = username;}public String setPassword(String password) { this.password = password;}}
SessionScoped bean
@SessionScoped @Named公共类登录实现可序列化{@Inject 凭证凭证;@Inject @UserDatabase EntityManager userDatabase;私人用户用户;公共无效登录(){列表结果 = userDatabase.createQuery(从用户 u 中选择 u,其中 u.username = :username 和 u.password = :password").setParameter("用户名",credentials.getUsername()).setParameter("密码",credentials.getPassword()).getResultList();如果 (!results.isEmpty()) {用户 = 结果.get(0);}别的 {//或许在此处添加代码以报告登录失败}}公共无效注销(){用户 = 空;}公共布尔 isLoggedIn() {返回用户 != null;}@Produces @LoggedIn 用户 getCurrentUser() {返回用户;}}
我的问题是
1) @RequestScoped
bean 被注入到 @SessionScoped
bean 中.什么是保证RequestScoped
的一个实例上设置的凭证信息与注入到@SessionScoped
bean 中的相同.为什么不从池中注入不同的 @RequestScoped
甚至一个新实例?
2) 为什么 bean 是 @SessionScoped
而不是 @Stateful
.我猜 @Stateful
会在这里工作.
3) @sessionScoped
bean 的生命周期是如何管理的?那是什么时候被摧毁?.如果我导航到不同的 JSF
页面,在该页面中,如果我提取诸如 currentUser.userName
之类的信息,我将检索在我的第一个 JSF 上设置的相同信息
用于登录的页面.(上面的第 1 步)
4) 如果我不指定 @RequestScoped
,那么 Credentials bean 将获得 @Dependent
范围,这是默认范围.docs 中提到设置 @Dependent
的任何实例变量都会立即丢失.但我不明白为什么?事实上,这让我想到了@Dependent
作用域的用途是什么?
谢谢
编辑感谢 kolossus 提供详细而出色的答案.为了更好地理解,我需要对您的一些观点进行更多说明
- 对于@requestScoped bean,有一个可用的实例池可以移交给客户端.现在,如果我有两个客户端访问由
@RequestScoped
bean 支持的 JSF,则每个客户端都可以处理池中@RequestScoped
bean 的一个实例.事实上,这两个客户端实际上并不在直接实例上工作,而是对作为此处代理的单个实例的间接引用.客户端使用此代理执行所有方法调用或事务.那么代理持有这个间接引用多长时间?也就是说,在我上面的例子中,@RequestScoped
bean (Credentials
) 的实例变量是在 JSF 中设置的.但事实是,实例变量的这种设置通过代理间接发生在@RequestScoped bean 的一个实例上.但是当这个实例被注入到SessionScoped
bean 中时,是不是代理被注入了?由于SessionScoped
的生命周期是针对客户端和应用程序之间建立的会话,因此代理是否也在此生命周期中存在.这是否意味着这个@RequestScoped bean 的单个实例
绑定到SessionScoped
并且@RequestScoped
bean 实例或其代理的生命周期由SessionScoped
bean 的生命周期?
这是合法的,这要归功于 CDI 实际获取对所请求 bean 的引用的方式:客户端代理.来自 CDI 规范
注入的引用,或通过程序化查找获得的引用,通常是上下文引用.对具有正常范围的 bean 的上下文引用[...],不是对bean[...] 的上下文实例.相反,上下文引用是一个客户端代理对象客户端代理实现/扩展 bean 的部分或全部 bean 类型,并将所有方法调用委托给 bean 的当前实例...
这种间接的原因有很多:
- 容器必须保证,当调用任何对正常范围的 bean 的有效注入引用时,该调用始终由注入 bean 的当前实例处理.在某些情况下,例如,如果将请求作用域 bean 注入会话作用域 bean 或 servlet,则此规则需要间接引用
同样来自 这篇 DZone CDI 文章:>
CDI 通过使用代理来处理范围不匹配的 bean 的注入.因此,您可以将请求范围的 bean 注入会话范围的 bean 中,并且该引用对每个请求仍然有效,因为对于每个请求,代理重新连接到请求范围的 bean 的实时实例
这意味着,在每个注入点用一个代理代替真实的东西.代理通过扩展/实现它应该模仿的类型的祖先树来模仿在注入点声明的类型.当您现在实际需要使用该对象时,代理会在当前对话中为请求的 bean 的现有实例执行基于上下文的查找.这是一个请求范围的对象,您可以保证在当前对话/上下文中只有一个实例.
@Stateful
在这里不起作用,就像我在这里所说的那样,它们并不便宜;除非你真的需要,坚持使用 vanillaHttpSession
.更不用说一旦 SFSB 的客户端释放 bean,它就会被销毁,即 SFSB 与当前会话无关,@SessionScoped
是.取决于您指的是哪个
@SessionScoped
:javax.faces.bean.SessionScoped
直接绑定到当前的HttpSession
/浏览器会话,所以只要它死了它就会终止;然而 JBoss 暗示javax.enterprise.context.*
在上下文"消失之前,作用域 bean 实际上不会去任何地方在整个上下文被销毁之前,实际上无法从上下文中删除 bean
将
@Dependent
视为任何方法局部变量:它只有在其父构造存在时才有用.话虽如此,最好的用途不是支持 JSF 视图.最有用的应用程序是覆盖在 bean 上指定的范围,即临时.使用您当前的示例,我可以在我的应用程序中的其他地方使用以下内容:@Inject @New Login aDependentLoginBean;//隐式@Dependent 作用域应用@Inject 登录 aSessionScopedLoginBean;//应用标准登录bean的范围
与 @New 一起,您可以将任何其他 bean 重新用于
@Dependent
相关:
I am reading through this example in JBoss where a @RequestScoped
bean backing up JSF page
is used to pass the user credential information which is then saved in a @sessionScoped bean
.Here is the example take from JBoss docs.
@Named @RequestScoped
public class Credentials {
private String username;
private String password;
@NotNull @Length(min=3, max=25)
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@NotNull @Length(min=6, max=20)
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
JSF form:
<h:form>
<h:panelGrid columns="2" rendered="#{!login.loggedIn}">
<f:validateBean>
<h:outputLabel for="username">Username:</h:outputLabel>
<h:inputText id="username" value="#{credentials.username}"/>
<h:outputLabel for="password">Password:</h:outputLabel>
<h:inputSecret id="password" value="#{credentials.password}"/>
</f:validateBean>
</h:panelGrid>
<h:commandButton value="Login" action="#{login.login}" rendered="#{!login.loggedIn}"/>
<h:commandButton value="Logout" action="#{login.logout}" rendered="#{login.loggedIn}"/>
</h:form>
User Entity:
@Entity
public class User {
private @NotNull @Length(min=3, max=25) @Id String username;
private @NotNull @Length(min=6, max=20) String password;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String setPassword(String password) { this.password = password; }
}
SessionScoped bean
@SessionScoped @Named
public class Login implements Serializable {
@Inject Credentials credentials;
@Inject @UserDatabase EntityManager userDatabase;
private User user;
public void login() {
List<User> results = userDatabase.createQuery(
"select u from User u where u.username = :username and u.password = :password")
.setParameter("username", credentials.getUsername())
.setParameter("password", credentials.getPassword())
.getResultList();
if (!results.isEmpty()) {
user = results.get(0);
}
else {
// perhaps add code here to report a failed login
}
}
public void logout() {
user = null;
}
public boolean isLoggedIn() {
return user != null;
}
@Produces @LoggedIn User getCurrentUser() {
return user;
}
}
My questions are
1) The @RequestScoped
bean gets injected into @SessionScoped
bean. What is the guarantee that the credential information set on one instance of RequestScoped
is the same that is injected into @SessionScoped
bean. why not a different @RequestScoped
from pool gets injected or even a new instance?
2)why is the bean given @SessionScoped
but not @Stateful
. I guess @Stateful
will work here.
3)how is the lifecycle of @sessionScoped
bean managed? That is when does it gets destroyed ?. If I navigate to a different JSF
page in which if I pull the information such as currentUser.userName
, will I retrieve the same information I set on my first JSF
page used to log in. (step 1 above)
4) If I don't specify @RequestScoped
, then the Credentials bean get the @Dependent
scope which is the defualt scope. It is mentioned in the docs that setting any instance variables of a @Dependent
gets lost immediately. But I don't understand why? In fact, this prompts me the question of what use of @Dependent
scope will be ?
Thanks
EDITThanks kolossus for detailed and excellent answer. I need a little more clarifications on some of your points for better understanding
- For a @requestScoped bean, there are is a pool of instances available which gets handed over to clients. Now if I have two clients accessing a JSF which is backed by a
@RequestScoped
bean, each client gets to work on one instance of@RequestScoped
bean from the pool. In fact, both the clients do not actually work on the direct instance, but an indirect reference to the that single instance which is the proxy here. clients do all method calls or transactions using this proxy. so how long does the proxy holds this indirect reference? That is, in my example above, instance variables of@RequestScoped
bean (Credentials
) are set in JSF. but the true fact is that, this setting of instance variables happen to one instance of @RequestScoped bean indirectly through proxy. But when this instance is injected intoSessionScoped
bean, is it the proxy that gets injected? Since the lifecycle ofSessionScoped
is for a session established between client and application, does the proxy also live during this lifetime. Does that mean thissingle instance of @RequestScoped bean
is bound toSessionScoped
and the lifecycle of@RequestScoped
bean instance or its proxy is determined by the lifecycle ofSessionScoped
bean?
This is legal, thanks to the means by which CDI actually obtains references to a requested bean: client proxies. From the CDI spec
There are a number of reasons for this indirection:
- The container must guarantee that when any valid injected reference to a bean of normal scope is invoked, the invocation is always processed by the current instance of the injected bean. In certain scenarios, for example if a request scoped bean is injected into a session scoped bean, or into a servlet, this rule requires an indirect reference
Also from this DZone CDI article:
What this means is that, a proxy is substituted for the real thing at each injection point. The proxy mimics the type declared at the injection point by extending/implementing the ancestor tree of the type it's supposed to be mimicking. At the time you now actually require use of the object, the proxy performs a context-based lookup for an existing instance of the requested bean within the current conversation. This being a request-scoped object, you're guaranteed to have exactly one instance within the current conversation/context.
@Stateful
would not work here, like I stated here, they are not cheap; unless you really need to, stick with vanillaHttpSession
. Not to mention the fact that once the client of the SFSB releases the bean it's destroyed, i.e. the SFSB is not tied to the current session,@SessionScoped
is.Depends on which
@SessionScoped
you're referring to:javax.faces.bean.SessionScoped
is tied directly to the currentHttpSession
/browser session, so it's terminated whenever that dies; JBoss however implies thatjavax.enterprise.context.*
scoped beans don't actually go anywhere until the "context" diesThink of
@Dependent
as you would any method-local variable: it's only useful as long as it's parent construct is around. That being said, it's best use is not for backing a JSF view. It's most useful application is overriding the scope that's specified on a bean, ad-hoc. Using your current example, I can have the following somewhere else in my application:@Inject @New Login aDependentLoginBean; //implicit @Dependent scope applied @Inject Login aSessionScopedLoginBean; //standard Login bean's scope applied
Together with @New, you could repurpose any other bean to be
@Dependent
Related:
这篇关于@RequestScoped bean 实例是如何在运行时提供给 @SessionScoped bean 的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!