我目前正在将Ed Burns的JSF 2.0教科书中的Virtual Trainer示例应用程序从JSF Managed Beans转换为CDI。到目前为止我遇到的大多数问题都与范围和忘记正确注入有关,但现在我正在努力克服最近与从RequestMap中提取CDI Bean(实际上是实体类)有关的障碍。从目前为止我能够确定的情况来看,似乎可以通过使用Map实现提供的样板.get(String managedbeanname)方法非常简单地提取请求范围的Managed Bean。但是,使用CDI,Bean在CreationalContextImp实例中被Weld包装,我无法提取我真正追求的对象,即使它已经确认它存在于RequestMap中。我可以简单地从RequestMap访问一个代理对象,但是在调用.get(“user”)之后它会恢复为null,我怀疑它会做多少好事,因为代理中的字段都是无效的。
我发现BalusC的一篇帖子讨论了使用过滤器类来访问SessionScope中保存的CDI bean(How do I get a SessionScoped CDI bean from inside a Filter?)这似乎有点涉及 - 是否有更简单的解决方案?我也非常清楚我可能正在捣乱管理Bean与CDI策略的范围/混合,所以请随意让我直截了当...我也有点不确定直接以这种方式使用实体Bean而不是使用正面。这会导致我/可能会在以后引起我的问题吗?
环境:JEE7,Glassfish 4,Netbeans 7.4,Maven EE Web原型 有关使用Managed Beans的原始代码已被注释掉。
抽象支持bean类:
@RequestScoped
public abstract class AbstractBacking implements Serializable {
//@ManagedProperty(value="#{facesContext}")
private FacesContext facesContext;
//@ManagedProperty(value="#{requestScope}")
private Map<String, Object> requestMap;
//@ManagedProperty(value="#{sessionScope}")
private Map<String, Object> sessionMap;
@PostConstruct
public void init() {
this.facesContext = FacesContext.getCurrentInstance();
this.sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
this.requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
}
注册页面支持bean:
@Named
@RequestScoped
public class RegisterBacking extends AbstractBacking implements Serializable {
private Object password1;
@Inject
private User newUser;
public String registerUser() {
String result = null;
User newUser = (User) getRequestMap().get("user");
// set the password into the user, because we know the validator was
// successful if we reached here.
newUser.setPassword((String) getRequestMap().get("password1"));
try {
UserRegistry.getCurrentInstance().addUser(newUser);
// Put the current user in the session
setCurrentUser(newUser);
// redirect to the main page
result = "/user/allEvents?faces-redirect=true";
} catch (EntityAccessorException ex) {
getFacesContext().addMessage(null,
new FacesMessage("Error when adding user"
+ ((null != newUser) ? " " + newUser.toString() : "") + "."));
}
return result;
}
用户实体bean:
@Entity
@Named
@Table(name = "Users")
@RequestScoped
@NamedQueries({
@NamedQuery(name = "user.getAll", query = "select u from User as u"), // @NamedQuery(name = "user.getTrainers", query = "select u from User as u where u.trainer = TRUE"),
// @NamedQuery(name = "user.getUsersForTrainerId", query = "select u from User as u where u.personalTrainerId = :theId")
})
public class User extends AbstractEntity implements Serializable {
protected String firstName;
protected String lastName;
@Temporal(TemporalType.DATE)
protected Date dob;
protected String sex;
protected String email;
private String serviceLevel = "medium";
@Column(name = "userid", nullable = false)
private String userid;
private String password;
private boolean trainer;
private List<Long> subscribedEventIds;
private Long personalTrainerId;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<TrainingSession> sessions;
private boolean sessionsInitialized = false;
public User() {
this.init();
}
public User(String firstName, String lastName,
String sex, Date dob, String email, String serviceLevel,
String userid, String password, boolean isTrainer) {
this.init();
this.setFirstName(firstName);
this.setLastName(lastName);
this.setSex(sex);
this.setDob(dob);
this.setEmail(email);
this.setServiceLevel(serviceLevel);
this.setUserid(userid);
this.setPassword(password);
this.setTrainer(isTrainer);
}
.....
Getters/setters/etc
.....
注册页面:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<body>
<ui:composition template="template.xhtml">
<ui:define name="content">
<h:form prependId="false">
<h:panelGrid columns="3">
<h:outputLabel for="fname" value="First Name:" />
<h:inputText label="First Name"
id="fname" value="#{user.firstName}"
required="true"/>
<h:message for="fname" />
<h:outputLabel for="lname" value="Last Name:" />
<h:inputText label="Last Name"
id="lname" value="#{user.lastName}"
required="true"/>
<h:message for="lname" />
<h:outputLabel for="sex" value="Sex:" />
<h:selectOneRadio label="Sex"
id="sex" value="#{user.sex}" required="true">
<f:selectItem itemLabel="Male" itemValue="male" />
<f:selectItem itemLabel="Female" itemValue="female" />
</h:selectOneRadio>
<h:message for="sex" />
<h:outputLabel for="dob" value="Date of Birth:" />
<h:panelGroup>
<h:inputText label="Date of Birth"
id="dob" value="#{user.dob}" required="true">
<f:convertDateTime pattern="MM-dd-yy" />
</h:inputText> (mm-dd-yy)
</h:panelGroup>
<h:message for="dob" />
<h:outputLabel for="email" value="Email Address:" />
<h:inputText label="Email Address"
id="email" value="#{user.email}" required="true" />
<h:message for="email" />
<h:outputLabel for="slevel" value="Service Level:" />
<h:selectOneMenu label="Service Level" id="slevel"
value="#{user.serviceLevel}">
<f:selectItem itemLabel="Medium" itemValue="medium" />
<f:selectItem itemLabel="Basic" itemValue="basic" />
<f:selectItem itemLabel="Premium" itemValue="premium" />
</h:selectOneMenu>
<h:message for="slevel" />
<h:outputLabel for="userid" value="Userid:" />
<h:inputText required="true" id="userid" value="#{user.userid}" />
<h:message for="userid" />
<h:outputLabel for="password" value="Password:" />
<h:inputSecret required="true" id="password"
validator="#{registerBacking.validatePassword1}"
value="#{requestScope.password1}" />
<h:message for="password" />
<h:outputLabel for="password2" value="Retype Password:" />
<h:inputSecret required="true" id="password2" value="#{requestScope.password2}"
validator="#{registerBacking.validatePassword2}" />
<h:message for="password2" />
</h:panelGrid>
<p><h:commandButton value="Register"
action="#{registerBacking.registerUser}" /></p>
</h:form>
</ui:define>
</ui:composition>
</body>
</html>
答案 0 :(得分:4)
事实上,你似乎混合了CDI Beans和JSF Beans,说实话,你翻译的例子看起来很奇怪,我只是把它全部扔出窗外。
因为,JSF也有它自己的依赖注入,除非你有一个非常具体的用例(比如说ServletFilter),否则你不会自己从作用域地图中提取东西。
澄清CDI使用http://en.wikipedia.org/wiki/Inversion_of_control原则并自行“提取”内容是一种反模式。
此外CDI bean由CDI容器创建和管理(几乎在所有情况下都是Weld或OWB),你根本无法从externalContext检索CDI bean,因为它是JSF的上下文,而且它是一个完全独立的东西。
因此,JSF创建的实例不能在CDI Bean中注入,反之亦然。无论如何,正如我先前所说的那样自己提取东西是不好的做请参阅此列表中的#10:http://zeroturnaround.com/rebellabs/watch-out-for-these-10-common-pitfalls-of-experienced-java-developers-architects/
所以无论你想要什么,你只需使用@Inject
(可能使用限定符)。
解决方法对于Inject不起作用的特殊情况:
第一个场景会在您拥有ThreadLocal时发生,例如QuartzJob。 EJB容器可能提供获取上下文线程的方法,但如果您使用普通的servlet容器(tomcat),或者生成的线程没有CDI上下文,那么您必须自己附加此信息。为此,请使用Deltaspike CDI Control。
http://deltaspike.apache.org/container-control.html
该示例未更新,今天您应该使用DependentProvider来获取ContextControl。
我的博客示例:http://www.kildeen.com/blog/post/2013-10-11/Batch%20Jobs%20in%20EE/
第二种情况是ServletContainer创建实例并且您在普通容器(例如Tomcat)上时,JSF创建了实例,无论如何。
然后,注射不起作用。解决方法是使用Deltaspike的BeanProvider。 http://deltaspike.apache.org/core.html它可以在当前实例中强制注入,更有用的是它可以为你提取bean。如果你这样做并且仍然遇到第一个场景,你将得到一个例外,可能是ContextNotActiveException
。
要正确使用JSF和CDI,我建议使用TomEE。这是一个apache项目,因此它是开源的。邮件列表和irc频道非常活跃,而且它正在崛起,即使现在也非常棒。
当然这是我的观点,其他人更喜欢其他解决方案,例如WildFly,使用Tomcat / Jetty,Glassfish等自行构建。
现在,JSF的所有常规规则都适用(必须使用getter / setter等约定)。要将bean公开给EL表达式(JSF使用),您必须将其标记为@Named
。如果类名为myBean
,则默认名称为MyBean
。
现在是范围。始终对应用程序中的每个bean使用@RequestScoped。如果您遇到麻烦,因为该范围很短,您应该考虑它应该是多长时间,如果您想要保留的数据对其他bean感兴趣,并且您希望它在所有浏览器选项卡中都可用。
如果是用户信息,那么很多豆子很有可能。因此,我们创建了一个名为WebUser
的新类。用户可能希望他的信息在整个会话期间保留(您可能也需要使用该对象跟踪他)。因此,我们使用正确包中的@SessionScoped
,必须导入javax.enterprise.context.SessionScoped
。但突然之间,一些逻辑不应该在标签之间共享,因此您需要更精细的范围。 CDI附带@ViewScoped
(CDI 1.1,JSF 2.2)和@ConversationScoped
,但您很可能迟早需要来自Myfaces CODI的示波器。现在,大多数CODI已经在deltaspike但不是范围。然而,它们已被拆分,如何导入它们在这里解释:http://os890.blogspot.fr/2013/07/add-on-codi-scopes-for-deltaspike.html你迟早会在Deltaspike中看到它们。
所以现在你可以使用很多不同的范围,你可以明智地选择它。只应从数据库中读取一次的内容可以与@ApplicationScoped
一起存储。
例如,您可能希望从数据库中读取系统设置并将它们存储在SettingManager中,这是一个用@ApplicationScoped
注释的CDI bean。
WelcomeBean
是@RequestScoped
和@Model
,负责登录和帐户创建。要确定是否可以创建新帐户,可能如下所示:
@ViewScoped
@Named
public class WelcomeBean {
@Inject
private SettingManager settingManager;
private boolean allowCreateAccount;
public boolean isAllowCreateAccount() {
return allowCreateAccount;
}
// login and create account here
@PostConstruct
private void init() {
allowCreateAccount = settingManager.getBooleanSetting("registrationOpen");
}
}
Facelet创建帐户按钮如下所示:
<h:commandButton action="#{welcomeBean.createAccount}" value="login" disabled="#{welcomeBean.allowCreateAccount}"/>
现在,当用户执行登录时,您可能希望将此信号指示为事件。阅读CDI活动。真正最先进的例子与这个简单的例子有很大不同。