我设法用Oracle的Mojarra设置了JSF 2.2,用Google Guice 3.0设置了CDI 1.0,用DataNucleus 3.1.3设置了JPA 2.0(我省略了我在这里发布的代码中的JPA部分)以使其运行谷歌应用引擎(SDK 1.9.48),除了一个很大的缺点:当我尝试@Inject
一个@javax.faces.bean.ManagedBean
时,一个bean实例被正确注入,但它在执行代码结束时被销毁,全部丢失先前在注入的实例中修改的数据。这使得CDI实现不完整,因为JSF bean根本无法“交互”。
这是我的实施:
Web.xml中
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>MyApp</display-name>
<description>CDI, JSF 2.2 and JPA example</description>
<!-- ***** Designate client-side state saving, since GAE doesn't handle
server side (JSF default) state management. ***** -->
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<!-- Set the default suffix for JSF pages to .xhtml -->
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<!-- Disable use of threading for single-threaded environments such as the
Google AppEngine. -->
<context-param>
<description>
When enabled, the runtime initialization and default ResourceHandler
implementation will use threads to perform their functions. Set this
value to false if threads aren't desired (as in the case of running
within the Google Application Engine).
Note that when this option is disabled, the ResourceHandler will not
pick up new versions of resources when ProjectStage is development.
</description>
<param-name>com.sun.faces.enableThreading</param-name>
<param-value>false</param-value>
</context-param>
<!-- allow dependency-injection into ManagedBeans -->
<context-param>
<param-name>com.sun.faces.injectionProvider</param-name>
<param-value>cdi.InjectionProvider</param-value>
</context-param>
<!-- ***** Specify JBoss Expression Language Over Default -->
<context-param>
<param-name>com.sun.faces.expressionFactory</param-name>
<param-value>org.jboss.el.ExpressionFactoryImpl</param-value>
</context-param>
<!-- ***** Load the JavaServer Faces Servlet ***** -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<!-- **************************************************** -->
<!-- CDI API initialization (Google Guice Implementation). -->
<!-- **************************************************** -->
<filter>
<filter-name>GuiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GuiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>cdi.listener.GuiceListener</listener-class>
</listener>
<!-- **************************************************** -->
<!-- ***** Specify session timeout of thirty (30) minutes. ***** -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsf</welcome-file>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
faces-config.xml中
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
version="2.2">
<!-- a hack to enfore session serialization -->
<lifecycle>
<phase-listener>listener.SessionPhaseListener</phase-listener>
</lifecycle>
</faces-config>
SessionPhaseListener.java
package listener;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
public class SessionPhaseListener implements PhaseListener {
/** Used in object serialization */
private static final long serialVersionUID = 1L;
@Override
public void afterPhase(PhaseEvent event) {
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("CURRENT_TIME",
System.currentTimeMillis());
}
@Override
public void beforePhase(PhaseEvent event) {
}
@Override
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
}
InjectionProvider.java
package cdi;
import javax.faces.bean.ManagedBean;
import com.cloud.brochureforce.client.cdi.listener.GuiceListener;
import com.sun.faces.spi.InjectionProviderException;
import com.sun.faces.vendor.WebContainerInjectionProvider;
public class InjectionProvider extends WebContainerInjectionProvider {
@Override
public void inject(Object managedBean) throws InjectionProviderException {
if (isToBeInjectedByGuice(managedBean))
GuiceListener.injectMembers(managedBean);
}
/**
* As an arbitrary choice, the choice here is to inject only into
* {@code @ManagedBean} instances, so that other classes - not written by us
* - wouldn't be injected too. This choice could be altered.
*
* @param managedBean A JSF bean instance (annotated with @ManagedBean).
* @return
*/
private boolean isToBeInjectedByGuice(Object managedBean) {
return managedBean.getClass().getAnnotation(ManagedBean.class) != null;
}
@Override
public void invokePostConstruct(Object managedBean) throws InjectionProviderException {
// @PostConstruct is already handled in classes we injected
if (!isToBeInjectedByGuice(managedBean))
super.invokePostConstruct(managedBean);
}
@Override
public void invokePreDestroy(Object managedBean) throws InjectionProviderException {
super.invokePreDestroy(managedBean);
}
}
GuiceListener.java
package cdi.listener;
import cdi.JSFModule;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
public class GuiceListener extends GuiceServletContextListener {
protected AbstractModule module;
protected static Injector injector;
public GuiceListener() {
module = new JSFModule();
if (injector == null) {
injector = Guice.createInjector(module);
}
}
@Override
public Injector getInjector() {
return injector;
}
/**
* given a class, generates an injected instance. Useful when an API call is
* needed internally.
*/
public static <T> T getInstance(Class<T> type) {
return injector.getInstance(type);
}
/**
* given an injectable instance, injects its dependencies
*/
public static void injectMembers(Object instance) {
injector.injectMembers(instance);
}
}
PostConstructTypeListener.java(处理@PostConstruct带注释代码的侦听器)
package cdi.listener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.PostConstruct;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
/**
* Listener to detect @PostConstruct annotated code.
*/
public class PostConstructTypeListener implements TypeListener {
private final String packagePrefix;
public PostConstructTypeListener(String packagePrefix) {
this.packagePrefix = packagePrefix;
}
@Override
public <I> void hear(final TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
Class<? super I> clz = typeLiteral.getRawType();
if (packagePrefix != null && !clz.getName().startsWith(packagePrefix))
return;
final Method method = getPostConstructMethod(clz);
if (method != null) {
typeEncounter.register(new InjectionListener<I>() {
@Override
public void afterInjection(Object i) {
try {
// call the @PostConstruct annotated method after all
// dependencies have been injected
method.invoke(i);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
});
}
}
/**
* checks whether the provided class, or one of its super-classes, has a
* method with the {@code PostConstruct} annotation. the method may be
* public, protected, package-private or private. if it's inaccessible by
* Java rules, this method will also make it accessible before returning it.
*
* @return the method that meets all requirements, or null if none found
*/
private Method getPostConstructMethod(Class<?> clz) {
for (Method method : clz.getDeclaredMethods()) {
if (method.getAnnotation(PostConstruct.class) != null && isPostConstructEligible(method)) {
method.setAccessible(true);
return method;
}
}
Class<?> superClz = clz.getSuperclass();
return (superClz == Object.class || superClz == null) ? null : getPostConstructMethod(superClz);
}
/**
* apply restrictions as defined in the <a href=
* "http://docs.oracle.com/javaee/5/api/javax/annotation/PostConstruct.html">JavaEE
* specifications</a>
*/
private boolean isPostConstructEligible(final Method method) {
return (method.getReturnType() == void.class) && (method.getParameterTypes().length == 0)
&& (method.getExceptionTypes().length == 0);
}
}
最后,Guice要求创建注入器的ServletModule
:
package cdi;
import com.cloud.brochureforce.client.cdi.listener.PostConstructTypeListener;
import com.google.inject.matcher.Matchers;
import com.google.inject.servlet.ServletModule;
public class JSFModule extends ServletModule {
@Override
protected void configureServlets() {
// add support for the @PostConstruct annotation for Guice-injected
// objects
// if you choose to remove it, also modify
// GuiceJsfInjector.invokePostConstruct()
bindListener(Matchers.any(), new PostConstructTypeListener(null));
}
protected String getServingUrl() {
return "*.jsf";
}
}
现在,我通过这个简单的测试尝试了CDI实现:
两个@javax.faces.bean.SessionScoped
托管bean:
CDIExampleBean.java
package jsf.bean;
import java.io.Serializable;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.inject.Inject;
@ManagedBean(name = "jsfbCdi", eager = true)
@SessionScoped
public class CDIExampleBean implements Serializable {
private static final long serialVersionUID = 1L;
private String injectedProperty;
@PostConstruct
private void init() {
System.out.println("CDIExampleBean - Injection works!"); // It works.
}
/**
* @return the injected property
*/
public String setInjectedProperty() {
return injectedProperty;
}
/**
* @param injectedProperty
* the new injectedProperty
*/
public void setInjectedProperty(String injectedProperty) {
this.injectedProperty = injectedProperty;
}
}
ExampleBean.java
package jsf.bean;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.inject.Inject;
import com.cloud.brochureforce.dataaccess.PersistenceManager;
import com.cloud.brochureforce.dataaccess.Queries;
import com.cloud.brochureforce.dataaccess.entity.Visit;
@ManagedBean(name="jsfbExample", eager=true)
@SessionScoped
public class ExampleBean implements Serializable {
private static final long serialVersionUID = 1L;
@Inject
private CDIExampleBean jsfbCdi;
@PostConstruct
private void init() {
System.out.println("ExampleBean - Injection works!"); // It works.
}
/**
* Method to test EL engine processing with parameters.
* @param param
* @return
*/
public void injectValue() {
this.jsfbCdi.setInjectedProperty("Injecting...");
}
}
@Inject private CDIExampleBean jsfbCdi;
被注入,但是当方法injectValue()
结束时它被销毁。
我怎么知道?如果我使用这样的AJAX在index.xhtml中调用此属性:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jsp/jstl/core"
template="WEB-INF/template/main.xhtml">
<ui:define name="header">
<p style="text-align: center">HI ALL!</p>
<br />
<p style="text-align: center">JSF 2.2.4 is up and running on Google
App. Engine.</p>
</ui:define>
<ui:define name="content">
<h:outputLabel for="companyId" value="Company ID" />
<h:inputText id="companyId" value="" required=""
requiredMessage="This value is mandatory" />
</ui:define>
<ui:define name="footer">
<h:form>
<h:commandButton value="Inject!"
actionListener="#{jsfbExample.injectValue()}">
<f:ajax execute="@this" render="emptyText" />
</h:commandButton>
<h:commandButton value="Show injected!"
actionListener="#{jsfbCdi.getInjectedProperty()}">
<f:ajax execute="@this" render="injectedValue" />
</h:commandButton>
<h:outputText id="injectedValue" /><br/>
<h:outputText id="emptyText" value="" /><br/>
</h:form>
</ui:define>
</ui:composition>
并在getInjectedProperty()
方法中设置断点,相关字段private String injectedProperty;
返回null
。当我说JSF bean根本无法“交互”时,这就是我的意思。
这里有人能指出我正确的方向吗?我做错了什么?