Java 如何使用JSF2.2、JPA2.0和依赖项注入功能设置Google AppEngine Web应用程序?

Java 如何使用JSF2.2、JPA2.0和依赖项注入功能设置Google AppEngine Web应用程序?,java,google-app-engine,jpa,jsf-2,guice,Java,Google App Engine,Jpa,Jsf 2,Guice,几周前,我被要求创建一个在Google云平台上运行的web应用程序(在输入本文时,SDK v1.9.48)。在配置AppEngine设置(创建帐户、项目、云数据库、源代码存储库等)之后,我准备使用GAE Eclipse插件开发我的第一个webapp 当我发现GAE只默认支持JSP和servlet时,我非常失望 然后我说:“愿上帝保佑我!再次回到J2EE的石器时代?我已经习惯了JSF和(C)DI的UI。我如何将这3个J2EE标准集成到GAE webapp中,并使其平稳运行(如果可能的话)?” j

几周前,我被要求创建一个在Google云平台上运行的web应用程序(在输入本文时,SDK v1.9.48)。在配置AppEngine设置(创建帐户、项目、云数据库、源代码存储库等)之后,我准备使用GAE Eclipse插件开发我的第一个webapp

当我发现GAE只默认支持JSP和servlet时,我非常失望

然后我说:“愿上帝保佑我!再次回到J2EE的石器时代?我已经习惯了JSF和(C)DI的UI。我如何将这3个J2EE标准集成到GAE webapp中,并使其平稳运行(如果可能的话)?”

  • jsf2.x
  • (C) DI 1.x
  • JPA2.x

只要继续读这篇文章,你就会知道怎么做

好吧,我决定不那么轻易放弃,而去解决这个问题。经过几周的艰苦研究和试错编码,我找到了解决这一混乱局面的办法

在开始写这篇文章之前,我将为您提供一些很好的资源,帮助您将其整合起来:

  • 框架:

  • Datanucleus 3.1.1(JPA 2.0)
  • Oracle Mojarra 2.2.4(JSF 2.2)
  • Google Guice 4.0(DI 1.0)
  • 我就是这样让它工作的:

    最重要的配置在web.xml中。JSF初始化必须首先运行:我发现
    com.sun.faces.config.ConfigureListener
    负责该步骤,它总是查找
    FacesServlet
    声明。由于为了启用DI,JSF请求必须由Guice使用
    FacesHttpServlet
    包装器(稍后我将发布)提供,因此:

    我声明了
    FacesServlet
    ,但没有使用
    s(我通过尝试错误编码找到了这一步)。 它只声明初始化
    FacesContextFactory
    。这是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>BrochureForce</display-name>
    
                    <description>Purchase orders generator configured to run on the Google AppEngine.</description>
    
                    <context-param>
                        <description>Project stage (Development or Production)</description>
                        <param-name>javax.faces.PROJECT_STAGE</param-name>
                        <param-value>Development</param-value>
                    </context-param>
    
                    <context-param>
                        <description>
                                    Designate client-side state saving, since GAE doesn't handle 
                                    server side (JSF default) state management.
                        </description>
                        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
                        <param-value>client</param-value>
                    </context-param>
    
                    <context-param>
                        <description>Sets the default suffix for JSF pages to .xhtml</description>
                        <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
                        <param-value>.xhtml</param-value>
                    </context-param>
    
                    <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 single-threaded
                                 environments such as the Google AppEngine).
                                 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>
    
                    <context-param>
                        <description>Allows dependency-injection into ManagedBeans</description>
                        <param-name>com.sun.faces.injectionProvider</param-name>
                        <param-value>mypackage.jsf.JsfInjectionProvider</param-value>
                    </context-param>
    
                    <context-param>
                        <description>Specify JBoss Expression Language Over Default</description>
                        <param-name>com.sun.faces.expressionFactory</param-name>
                        <param-value>org.jboss.el.ExpressionFactoryImpl</param-value>
                    </context-param>
    
                    <!-- JSF INITIALIZATION GOES FIRST!! -->
                    <servlet>
                        <description>
                                JSF 2 Servlet. There's NO servlet-mapping defined for this servlet because
                                it's declared here in order to enforce the FacesFactory to load properly
                                so that an instance of this servlet can be injected in the FacesHttpServlet
                                used by Guice to serve JSF requests and as injection provider at the same time.
                                Furthermore, the "load-on-startup" property is set to "0" to tell Jetty
                                that this servlet MUST be loaded first.
                        </description>
                        <servlet-name>JSF Servlet</servlet-name>
                        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
                        <load-on-startup>0</load-on-startup>
                    </servlet>
                    <listener>
                        <description>JSF Initialization.</description>
                        <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
                    </listener>
                    <!-- JSF INITIALIZATION GOES FIRST!! -->
    
                    <listener>
                        <description>PERSISTENCE ENGINE INITIALIZATION AND SHUTDOWN.</description>
                        <listener-class>mypackage.listener.PersistenceManagerSetupListener</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>
                    <!-- **************************************************** -->
                    <!-- DI API initialization (Google Guice Implementation). -->
                    <!-- **************************************************** -->
                    <filter>
                        <description>Google Guice filter which enables DI.</description>
                        <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>
                        <description>
                                    This listener initializes the Guice injector and wraps the JSF Servlet
                                    into a HttpServlet in order to serve JSF requests via Guice Filter.
                            </description>
                        <listener-class>mypackage.listener.GuiceListener</listener-class>
                    </listener>
                    <!-- **************************************************** -->
                </web-app>
    
  • 我定义了一个业务逻辑接口及其实现,并用
    @BusinessLogic
    注释对其进行了注释(这是一个注册对页面的访问的示例。字段包括:访问编号、源IP和时间戳):

    businessLogicBindings()
    方法将业务逻辑接口与实现实例相关联。另一方面,您可以在这一行看到:
    service(urlPattern).with(FacesHttpServlet.class),Guice将使用注入的
    FacesServlet
    实例将JSF请求重新路由到HttpServlet包装器:

                import java.io.IOException;
                import javax.faces.webapp.FacesServlet;
                import javax.inject.Inject;
                import javax.inject.Singleton;
                import javax.servlet.Servlet;
                import javax.servlet.ServletConfig;
                import javax.servlet.ServletException;
                import javax.servlet.ServletRequest;
                import javax.servlet.ServletResponse;
                import javax.servlet.http.HttpServlet;
                import javax.servlet.http.HttpServletRequest;
                import javax.servlet.http.HttpServletResponse;
    
                @Singleton
                public class FacesHttpServlet extends HttpServlet {
    
                    private static final long serialVersionUID = 1L;
    
                    private final Servlet facesServlet;
    
                    @Inject
                    public FacesHttpServlet(FacesServlet facesServlet) {
                        this.facesServlet = facesServlet;
                    }
    
                    @Override
                    public void init(ServletConfig config) throws ServletException {
                        this.facesServlet.init(config);
                    }
    
                    @Override
                    public ServletConfig getServletConfig() {
                        return this.facesServlet.getServletConfig();
                    }
    
                    @Override
                    public String getServletInfo() {
                        return this.facesServlet.getServletInfo();
                    }
    
                    @Override
                    public void destroy() {
                        super.destroy();
                        this.facesServlet.destroy();
                    }
    
                    @Override
                    public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
                        HttpServletRequest httpReq = (HttpServletRequest) req;
                        String reqUrl = httpReq.getRequestURL().toString();
                        // A hack to redirect the index page. It's been throwing an error if the
                        // "/index.[xhtml|jsf]" is not explicitly specified in the request URL.
                        if(reqUrl.toLowerCase().endsWith("index.xhtml")) {
                            ((HttpServletResponse) resp).sendRedirect(reqUrl.replace("index.xhtml", "index.jsf"));
                        } else {
                            this.facesServlet.service(req, resp);
                        }
                    }
                }
    
    现在,初始化喷油器的侦听器:

                import java.util.HashMap;
                import mypackage.cdi.JSFModule;
                import mypackage.cdi.JsfInjectionProvider;
                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;
                    private static HashMap<String, Object> instancesMap;
    
                    public GuiceListener() {
                        // Bean instance list to ensure that we inject a unique bean instance.
                        instancesMap = new HashMap<>();
    
                        // Create the injector.
                        injector = Guice.createInjector(new JSFModule());
                    }
    
                    @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 and make sure to
                     * only inject one.
                     */
                    public static void injectMembers(Object instance) {
                        Object obj = null;
                        if (JsfInjectionProvider.isBusinessLogicObject(obj)) {
                            String instanceClassName = instance.getClass().getName();
                            Object mappedInstance = instancesMap.get(instanceClassName);
                            if (mappedInstance == null) {
                                // It's a new bean instance. It's stored in the bean map
                                // to be able to reuse it.
                                instancesMap.put(instanceClassName, instance);
                                obj = instance;
                            } else {
                                // There's already a bean instance. Let's reuse it!.
                                obj = mappedInstance;
                            }
                        } else { // it should be a managed bean.
                            obj = instance;
                        }
                        injector.injectMembers(obj);
                    }
                }
    
    所有这些都起到了作用(但与JPA部分不相关):
    ExampleBean

                import java.io.Serializable;
                import java.util.List;
    
                import javax.annotation.PostConstruct;
                import javax.faces.bean.ManagedBean;
                import javax.inject.Inject;
    
                import mypackage.jsf.logic.VisitsHandler;
                import mypackage.dataaccess.entity.Visit;
    
                @ManagedBean(name="jsfbExample")
                public class ExampleBean implements Serializable {
    
                    private static final long serialVersionUID = 1L;
    
                    @Inject
                    private VisitsHandler visitsHandler;
    
                    @PostConstruct
                    public void init() {
                        System.out.println("ExampleBean - Injection works! visitsHandler = " + visitsHandler); // It works.
                    }
    
                    /**
                     * Method to test EL engine processing with parameters. 
                     * @param param
                     * @return
                     */
                    public void insertVisit() {
                        this.visitsHandler.insertVisit();
                    }
    
                    public List<Visit> getPageVisits() {
                        return this.visitsHandler.getPageVisits();
                    }
    
    
                    /**
                     * @return the currentVisit
                     */
                    public String getCurrentVisit() {
                        return this.visitsHandler.getCurrentVisit();
                    }
    
                    /**
                     * @param currentVisit
                     *            the currentVisit to set
                     */
                    public void setCurrentVisit(String currentVisit) {
                        this.visitsHandler.setCurrentVisit(currentVisit);
                    }
                }
    
    所有持久性init属性都在
    app engine.xml
    中定义。其基本结构:

                <appengine-web-app ...>
                    <application>cloud-project-id</application>
                    <version>1</version>
                    <threadsafe>true</threadsafe>
                    <system-properties>
                        <!-- Cloud platform properties (their name starts with "cloud") -->
                        <property name="cloud.db.url"
                            value="jdbc:google:mysql://(cloud-connection-name)/(db-name)" />
                        <property name="cloud.db.driver"
                            value="com.google.appengine.api.rdbms.AppEngineDriver" />
                            <!--  ...  -->
                        <!-- Dev platform properties (their name starts with "dev") -->
                        <property name="dev.db.url" value="jdbc:mysql://(db-server):(db-port)/(db-name)" />
                        <property name="dev.db.driver" value="com.mysql.jdbc.Driver" />
                            <!--  ...  -->
                        <!-- Datanucleus properties --> 
                        <!-- *********************************************** -->
                        <!-- THESE 2 ARE A MUST-HAVE!!! Others are optional -->
                        <!-- *********************************************** -->
                        <property name="persistencemanager.storeManagerType" value="rdbms" />
    
                        <!-- This means that all DB identifiers MUST be defined in lowercase. -->
                        <property name="persistencemanager.identifier.case" value="LowerCase" /> 
                        <!-- *********************************************** -->
                            <!--  ...  -->
                    </system-properties>
                    <sessions-enabled>true</sessions-enabled>
                    <async-session-persistence enabled="false" />
                    <static-files>
                        <exclude path="/**.xhtml" />
                    </static-files>
                </appengine-web-app>
    
    “Visit”类只是一个实体类,它映射3个字段(访问次数、源IP和时间戳),并在“persistence.xml”文件中注册

    我写这篇文章是作为一个教程,一步一步地展示了我是如何在GAE上运行这些技术的(当我输入这些行时,SDK1.9.48)。我花了数周的时间研究和试错编码,我希望通过本指南可以帮助其他Java程序员避免像我一样经历这种混乱


    希望本指南能帮助其他人在GAE中创建出色的J2EE应用程序。

    好吧,我决定不那么轻易放弃并陷入这个问题。经过几周的艰苦研究和试错编码,我找到了解决这一混乱局面的办法

    在开始写这篇文章之前,我将为您提供一些很好的资源,帮助您将其整合起来:

  • 框架:

  • Datanucleus 3.1.1(JPA 2.0)
  • Oracle Mojarra 2.2.4(JSF 2.2)
  • Google Guice 4.0(DI 1.0)
  • 我就是这样让它工作的:

    最重要的配置在web.xml中。JSF初始化必须首先运行:我发现
    com.sun.faces.config.ConfigureListener
    负责该步骤,它总是查找
    FacesServlet
    声明。由于为了启用DI,JSF请求必须由Guice使用
    FacesHttpServlet
    包装器(稍后我将发布)提供,因此:

    我声明了
    FacesServlet
    ,但没有使用
    s(我通过尝试错误编码找到了这一步)。 它只声明初始化
    FacesContextFactory
    。这是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>BrochureForce</display-name>
    
                    <description>Purchase orders generator configured to run on the Google AppEngine.</description>
    
                    <context-param>
                        <description>Project stage (Development or Production)</description>
                        <param-name>javax.faces.PROJECT_STAGE</param-name>
                        <param-value>Development</param-value>
                    </context-param>
    
                    <context-param>
                        <description>
                                    Designate client-side state saving, since GAE doesn't handle 
                                    server side (JSF default) state management.
                        </description>
                        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
                        <param-value>client</param-value>
                    </context-param>
    
                    <context-param>
                        <description>Sets the default suffix for JSF pages to .xhtml</description>
                        <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
                        <param-value>.xhtml</param-value>
                    </context-param>
    
                    <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 single-threaded
                                 environments such as the Google AppEngine).
                                 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>
    
                    <context-param>
                        <description>Allows dependency-injection into ManagedBeans</description>
                        <param-name>com.sun.faces.injectionProvider</param-name>
                        <param-value>mypackage.jsf.JsfInjectionProvider</param-value>
                    </context-param>
    
                    <context-param>
                        <description>Specify JBoss Expression Language Over Default</description>
                        <param-name>com.sun.faces.expressionFactory</param-name>
                        <param-value>org.jboss.el.ExpressionFactoryImpl</param-value>
                    </context-param>
    
                    <!-- JSF INITIALIZATION GOES FIRST!! -->
                    <servlet>
                        <description>
                                JSF 2 Servlet. There's NO servlet-mapping defined for this servlet because
                                it's declared here in order to enforce the FacesFactory to load properly
                                so that an instance of this servlet can be injected in the FacesHttpServlet
                                used by Guice to serve JSF requests and as injection provider at the same time.
                                Furthermore, the "load-on-startup" property is set to "0" to tell Jetty
                                that this servlet MUST be loaded first.
                        </description>
                        <servlet-name>JSF Servlet</servlet-name>
                        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
                        <load-on-startup>0</load-on-startup>
                    </servlet>
                    <listener>
                        <description>JSF Initialization.</description>
                        <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
                    </listener>
                    <!-- JSF INITIALIZATION GOES FIRST!! -->
    
                    <listener>
                        <description>PERSISTENCE ENGINE INITIALIZATION AND SHUTDOWN.</description>
                        <listener-class>mypackage.listener.PersistenceManagerSetupListener</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>
                    <!-- **************************************************** -->
                    <!-- DI API initialization (Google Guice Implementation). -->
                    <!-- **************************************************** -->
                    <filter>
                        <description>Google Guice filter which enables DI.</description>
                        <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>
                        <description>
                                    This listener initializes the Guice injector and wraps the JSF Servlet
                                    into a HttpServlet in order to serve JSF requests via Guice Filter.
                            </description>
                        <listener-class>mypackage.listener.GuiceListener</listener-class>
                    </listener>
                    <!-- **************************************************** -->
                </web-app>
    
  • 我定义了一个业务逻辑接口及其实现,并用
    @BusinessLogic
    注释对其进行了注释(这是一个注册对页面的访问的示例。字段包括:访问编号、源IP和时间戳):

    businessLogicBindings()
    方法将业务逻辑接口与实现实例相关联。另一方面,您可以在这一行看到:
    service(urlPattern).with(FacesHttpServlet.class),Guice将使用注入的
    FacesServlet
    实例将JSF请求重新路由到HttpServlet包装器:

                import java.io.IOException;
                import javax.faces.webapp.FacesServlet;
                import javax.inject.Inject;
                import javax.inject.Singleton;
                import javax.servlet.Servlet;
                import javax.servlet.ServletConfig;
                import javax.servlet.ServletException;
                import javax.servlet.ServletRequest;
                import javax.servlet.ServletResponse;
                import javax.servlet.http.HttpServlet;
                import javax.servlet.http.HttpServletRequest;
                import javax.servlet.http.HttpServletResponse;
    
                @Singleton
                public class FacesHttpServlet extends HttpServlet {
    
                    private static final long serialVersionUID = 1L;
    
                    private final Servlet facesServlet;
    
                    @Inject
                    public FacesHttpServlet(FacesServlet facesServlet) {
                        this.facesServlet = facesServlet;
                    }
    
                    @Override
                    public void init(ServletConfig config) throws ServletException {
                        this.facesServlet.init(config);
                    }
    
                    @Override
                    public ServletConfig getServletConfig() {
                        return this.facesServlet.getServletConfig();
                    }
    
                    @Override
                    public String getServletInfo() {
                        return this.facesServlet.getServletInfo();
                    }
    
                    @Override
                    public void destroy() {
                        super.destroy();
                        this.facesServlet.destroy();
                    }
    
                    @Override
                    public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
                        HttpServletRequest httpReq = (HttpServletRequest) req;
                        String reqUrl = httpReq.getRequestURL().toString();
                        // A hack to redirect the index page. It's been throwing an error if the
                        // "/index.[xhtml|jsf]" is not explicitly specified in the request URL.
                        if(reqUrl.toLowerCase().endsWith("index.xhtml")) {
                            ((HttpServletResponse) resp).sendRedirect(reqUrl.replace("index.xhtml", "index.jsf"));
                        } else {
                            this.facesServlet.service(req, resp);
                        }
                    }
                }
    
    现在,初始化喷油器的侦听器:

                import java.util.HashMap;
                import mypackage.cdi.JSFModule;
                import mypackage.cdi.JsfInjectionProvider;
                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;
                    private static HashMap<String, Object> instancesMap;
    
                    public GuiceListener() {
                        // Bean instance list to ensure that we inject a unique bean instance.
                        instancesMap = new HashMap<>();
    
                        // Create the injector.
                        injector = Guice.createInjector(new JSFModule());
                    }
    
                    @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 and make sure to
                     * only inject one.
                     */
                    public static void injectMembers(Object instance) {
                        Object obj = null;
                        if (JsfInjectionProvider.isBusinessLogicObject(obj)) {
                            String instanceClassName = instance.getClass().getName();
                            Object mappedInstance = instancesMap.get(instanceClassName);
                            if (mappedInstance == null) {
                                // It's a new bean instance. It's stored in the bean map
                                // to be able to reuse it.
                                instancesMap.put(instanceClassName, instance);
                                obj = instance;
                            } else {
                                // There's already a bean instance. Let's reuse it!.
                                obj = mappedInstance;
                            }
                        } else { // it should be a managed bean.
                            obj = instance;
                        }
                        injector.injectMembers(obj);
                    }
                }
    
    所有这些都起到了作用(但与JPA部分不相关):
    ExampleBean

                import java.io.Serializable;
                import java.util.List;
    
                import javax.annotation.PostConstruct;
                import javax.faces.bean.ManagedBean;
                import javax.inject.Inject;
    
                import mypackage.jsf.logic.VisitsHandler;
                import mypackage.dataaccess.entity.Visit;
    
                @ManagedBean(name="jsfbExample")
                public class ExampleBean implements Serializable {
    
                    private static final long serialVersionUID = 1L;
    
                    @Inject
                    private VisitsHandler visitsHandler;
    
                    @PostConstruct
                    public void init() {
                        System.out.println("ExampleBean - Injection works! visitsHandler = " + visitsHandler); // It works.
                    }
    
                    /**
                     * Method to test EL engine processing with parameters. 
                     * @param param
                     * @return
                     */
                    public void insertVisit() {
                        this.visitsHandler.insertVisit();
                    }
    
                    public List<Visit> getPageVisits() {
                        return this.visitsHandler.getPageVisits();
                    }
    
    
                    /**
                     * @return the currentVisit
                     */
                    public String getCurrentVisit() {
                        return this.visitsHandler.getCurrentVisit();
                    }
    
                    /**
                     * @param currentVisit
                     *            the currentVisit to set
                     */
                    public void setCurrentVisit(String currentVisit) {
                        this.visitsHandler.setCurrentVisit(currentVisit);
                    }
                }
    
    所有持久性init属性都在
    app engine.xml
    中定义。其基本结构:

                <appengine-web-app ...>
                    <application>cloud-project-id</application>
                    <version>1</version>
                    <threadsafe>true</threadsafe>
                    <system-properties>
                        <!-- Cloud platform properties (their name starts with "cloud") -->
                        <property name="cloud.db.url"
                            value="jdbc:google:mysql://(cloud-connection-name)/(db-name)" />
                        <property name="cloud.db.driver"
                            value="com.google.appengine.api.rdbms.AppEngineDriver" />
                            <!--  ...  -->
                        <!-- Dev platform properties (their name starts with "dev") -->
                        <property name="dev.db.url" value="jdbc:mysql://(db-server):(db-port)/(db-name)" />
                        <property name="dev.db.driver" value="com.mysql.jdbc.Driver" />
                            <!--  ...  -->
                        <!-- Datanucleus properties --> 
                        <!-- *********************************************** -->
                        <!-- THESE 2 ARE A MUST-HAVE!!! Others are optional -->
                        <!-- *********************************************** -->
                        <property name="persistencemanager.storeManagerType" value="rdbms" />
    
                        <!-- This means that all DB identifiers MUST be defined in lowercase. -->
                        <property name="persistencemanager.identifier.case" value="LowerCase" /> 
                        <!-- *********************************************** -->
                            <!--  ...  -->
                    </system-properties>
                    <sessions-enabled>true</sessions-enabled>
                    <async-session-persistence enabled="false" />
                    <static-files>
                        <exclude path="/**.xhtml" />
                    </static-files>
                </appengine-web-app>
    
    “Visit”类只是一个实体类,它映射3个字段(访问次数、源IP和时间戳),并在“persistence.xml”文件中注册

    我写这篇文章是作为一个教程,一步一步地展示了我是如何在GAE上运行这些技术的(当我输入这些行时,SDK1.9.48)。这花了我一周的时间
                <!DOCTYPE html 
                         PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
                <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
                    xmlns:f="http://java.sun.com/jsf/core"
                    xmlns:h="http://java.sun.com/jsf/html"
                    xmlns:ui="http://java.sun.com/jsf/facelets">
                <h:head id="head">
                    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
                    <title>Welcome to JSF 2.1 on the Google AppEngine!</title>
                </h:head>
                <h:body>
                        <h:form>
                            <h:outputText id="lastVisit" value="#{jsfbExample.currentVisit}" /><br/>
                            <h:commandButton value="New visit!"
                                actionListener="#{jsfbExample.insertVisit()}">
                                <f:ajax execute="@this" render="pageVisitsList" />
                            </h:commandButton>
                            <h:commandButton value="Last inserted visit!">
                                <f:ajax execute="@this" render="lastVisit" />
                            </h:commandButton>
                            <h:panelGrid id="pageVisitsList">
                                <c:forEach var="visit" items="#{jsfbExample.pageVisits}">
                                    <h:outputText value="#{visit.toString()}" />
                                </c:forEach>
                            </h:panelGrid>
                        </h:form>
                </h:body>
                </html>
    
                package mypackage.listener;
    
                import javax.servlet.ServletContextEvent;
                import javax.servlet.ServletContextListener;
                import mypackage.dataaccess.PersistenceManager;
                import mypackage.utils.StringMap;
    
                public class PersistenceManagerSetupListener implements ServletContextListener {
    
                    @Override
                    public void contextInitialized(ServletContextEvent servletContextInitEvt) {
    
                      // This is only a wrapper over HashMap<String, String>
                        StringMap initProperties = new StringMap();
    
                        // Check the System properties to determine if we are running on cloud
                        // or not, and set up the JDBC driver accordingly.
                        String platform = System.getProperty("com.google.appengine.runtime.version").toLowerCase()
                                .contains("google app engine") ? "cloud" : "dev";
                        initProperties.put("datanucleus.ConnectionURL", System.getProperty(platform + ".db.url"));
                        initProperties.put("datanucleus.ConnectionDriverName", System.getProperty(platform + ".db.driver"));
                        initProperties.put("datanucleus.ConnectionUserName", System.getProperty(platform + ".db.user"));
                        initProperties.put("datanucleus.ConnectionPassword", System.getProperty(platform + ".db.password"));
                        // I implemented password encryption. See Datanucleus' "ConnectionEncryptionProvider" interface documentation.
                      initProperties.put("datanucleus.ConnectionPasswordDecrypter",
                                System.getProperty(platform + ".db.encryptionProvider"));
    
                        // ***********************************************************************************************************
                        // THESE 2 ARE A MUST-HAVE!!!
                        // ***********************************************************************************************************
                        initProperties.put("datanucleus.identifier.case", System.getProperty("persistencemanager.identifier.case"));
                        initProperties.put("datanucleus.storeManagerType", System.getProperty("persistencemanager.storeManagerType"));
                        // ***********************************************************************************************************
    
                        initProperties.put("datanucleus.NontransactionalRead",
                                System.getProperty("persistencemanager.NontransactionalRead"));
                        initProperties.put("datanucleus.NontransactionalRead",
                                System.getProperty("persistencemanager.NontransactionalRead"));
                        initProperties.put("datanucleus.NontransactionalWrite",
                                System.getProperty("persistencemanager.NontransactionalWrite"));
                        initProperties.put("datanucleus.singletonEMFForName",
                                System.getProperty("persistencemanager.singletonEMFForName"));
                        initProperties.put("javax.persistence.query.timeout", System.getProperty("persistencemanager.query.timeout"));
                        initProperties.put("datanucleus.datastoreWriteTimeout",
                                System.getProperty("persistencemanager.datastoreWriteTimeout"));
    
    
                        // Initialize persistence engine.
                        PersistenceManager.initialize(initProperties);
                    }
    
                    @Override
                    public void contextDestroyed(ServletContextEvent servletContextDestroyedEvt) {
                        PersistenceManager.shutdown();
                    }
                }
    
                <appengine-web-app ...>
                    <application>cloud-project-id</application>
                    <version>1</version>
                    <threadsafe>true</threadsafe>
                    <system-properties>
                        <!-- Cloud platform properties (their name starts with "cloud") -->
                        <property name="cloud.db.url"
                            value="jdbc:google:mysql://(cloud-connection-name)/(db-name)" />
                        <property name="cloud.db.driver"
                            value="com.google.appengine.api.rdbms.AppEngineDriver" />
                            <!--  ...  -->
                        <!-- Dev platform properties (their name starts with "dev") -->
                        <property name="dev.db.url" value="jdbc:mysql://(db-server):(db-port)/(db-name)" />
                        <property name="dev.db.driver" value="com.mysql.jdbc.Driver" />
                            <!--  ...  -->
                        <!-- Datanucleus properties --> 
                        <!-- *********************************************** -->
                        <!-- THESE 2 ARE A MUST-HAVE!!! Others are optional -->
                        <!-- *********************************************** -->
                        <property name="persistencemanager.storeManagerType" value="rdbms" />
    
                        <!-- This means that all DB identifiers MUST be defined in lowercase. -->
                        <property name="persistencemanager.identifier.case" value="LowerCase" /> 
                        <!-- *********************************************** -->
                            <!--  ...  -->
                    </system-properties>
                    <sessions-enabled>true</sessions-enabled>
                    <async-session-persistence enabled="false" />
                    <static-files>
                        <exclude path="/**.xhtml" />
                    </static-files>
                </appengine-web-app>
    
                <?xml version="1.0" encoding="UTF-8" ?>
                <persistence xmlns="http://java.sun.com/xml/ns/persistence"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                                http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
                    version="1.0">
    
                    <persistence-unit name="MyPersistenceUnit">
                        <!-- DATANUCLEUS' JPA 2.0 PERSISTENCE PROVIDER CLASS -->
                        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
    
                        <!-- ENTITY CLASSES -->
                        <class>mypackage.dataaccess.entity.Visit</class>
    
                        <!-- DON'T PROCESS UNLISTED CLASSES AS ENTITY CLASSES. -->
                        <exclude-unlisted-classes>true</exclude-unlisted-classes>
                    </persistence-unit>
                </persistence>
    
                    public static void initialize(Map properties) {
                        if (!isInitialized) {
                            if (properties == null) {
                                emfInstance = Persistence.createEntityManagerFactory("MyPersistenceUnit");
                            } else {
                                emfInstance = Persistence.createEntityManagerFactory("MyPersistenceUnit", properties);
                            }
                            emInstance = emfInstance.createEntityManager();
                            isInitialized = true;
                        }
                    }
    
                    public static void shutdown() {
                        try {
                            emInstance.close();
                        } catch (Exception e) {}
                        try {
                            emfInstance.close();
                        } catch (Exception e) {}
                    }