Google App Engine中的CDI重新实施注入的托管bean,从而丢失了JSF bean的当前数据

时间:2017-01-17 22:18:36

标签: google-app-engine jsf-2 cdi jpa-2.0

我设法用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根本无法“交互”。

这是我的实施:

  1. 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>
    
  2. 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>
    
  3. 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;
                }
            }
    
  4. 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);
                }
            }
    
  5. 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);
                }
            }
    
  6. 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);
                }
            }
    
  7. 最后,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:

    1. 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;
                  }
              }
      
    2. 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...");
                  }
              }
      
    3. @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根本无法“交互”时,这就是我的意思。

      这里有人能指出我正确的方向吗?我做错了什么?

0 个答案:

没有答案