[翻译] JSF for Nonbelievers: Clearing the FUD about JSF

JSF for Nonbelievers: Clearing the FUD about JSF -- Richard Hightower
来自:http://www-128.ibm.com/developerworks/library/j-jsf1/
翻译:zhouy
--------------------------------------------------------------------------------
恐惧、不确定和怀疑 JSF 的观念环绕着 JSF 技术已经有一段时间了,我觉得是阻止这种观念继续曼延的时候了——或者至少在两者这间取得平衡点。关于 JSF ,首当其充的误解,就是离不开拖拽式所见即所得工具进行 JSF 开发。其二, JSF 不支持类似 Struts 的 MVC Model 2 框架。而最后一点,也是困扰 JSF 开发最大的一点,就是关于其开发困难度的说法。

在这个四小节的讨论中,我将尽我所能,通过教你如何利用JSF进行开发这种最实际的方法,来消除上述所有三种误解。事实上,如果你觉得JSF开发是件困难的事,那么很有可能是因为你没有正确的使用它——幸运的是要修正它并不难。我会让你对JSF有一个大体的观念,并且利用一个可工作的示例展示MVC和JSF的基本原理。在我们开始所有介绍之前,先花一分钟时间将事实不符的FUD区分清楚。

不要相信FUD

如前面提到的,关于JSF有三个大的误解,其一便是JSF要求所见即所得工具进行开发工作。这是错误的,就像许多Swing开发人员不使用所见即所得工具构建Swing应用程序一样,你不必需要所见即所得编辑器来构建JSF应用。事实上,不使用所见即所得工具来开发 JSF 会比传统的Model 2框架如Struts和WebWork开发来得简单。在稍后的章节中我会有详细的解释,但在这里请记住:JSF开发比Struts来得简单,即使不使用所见即所得工具!

关于JSF的另一个误解是,它不支持Model 2模型。现在,这种说法只是部分正确。事实是Model 2是一种建立在Servlets基础上的MVC瀑布模型版本。与Model 2面向无状态协议(HTTP)所不同的是,JSF支持富MVC模型——一种与传统的GUI应用更加相似的模型。虽然MVC的基础概念使得JSF框架的实现比其它框架困难,但在上层结构上许多实现JSF的工作已经为你做好,得到的结果是,付出减少,却能够获得更多的回报。 

流传最广的关于JSF的误解是在于JSF的开发难度。我经常从人们口中听到这样的说法,他们阅读了大量的技术文档,但是并没有实际动手去尝试这项技术,所以我认为要消除这个虑虑很简单。如果你是通过 JSF 浩瀚无边的规格说明来建立你对JSF的理解,那么这些东西肯定会使你受惊吓。要明确的是,所有的这些规格本质上是给实现工具使用的,而不是应用程序开发者。如前所述,JSF是为方便应用程序开发者而设计的。 

虽然从某种意义上说,JSF的组件基础和事件驱动的GUI式的开发模型对于Java世界来说是一种新鲜的事物,但是它其实早已在某些领域中存在了。Apple的WebObjects和ASP.net与JSF就十分的相似。Tapestry是一项开源的Web组件框架,它与JSF所采用的方法不同,但也是一种基于Web GUI组件框型的技术。 

无论如何,关于FUD的讨论或许可以终止。解决关于JSF的编见最快捷的方法便是正确地研究这门技术,这正是我们稍后要做的事情。考虑到这或许是你第一次了解JSF,我将为你介绍它的总体概念。 

给JSF初学者

类似于Swing和AWT,JSF提供了一套标准和可重用的GUI组件。JSF被用来构建Web应用表层,它提供如下开发优势: 
  • 动作逻辑与表现逻辑的明显区分
  • 有状态事务的组件级别控制
  • 事件与服务端代码的轻松绑定
  • UI组件及Web层观念
  • 提供多样的、标准的开发商实现规则
一个典型的JSF应用包括以下几个部分:
  • 供管理应用状态和动作的JavaBeans组件
  • 事件驱动开发(通过与传统GUI开发类似的监听器来实现)
  • 展示MVC样式的页面,通过JSF组件树引用视图
虽然你将需要克服使用JSF过程中的观念上的困难,但是这些努力都将是值得的。JSF的组件状态管理,方便的用户输入校验,小粒度,组件基础的事件处理及方便的可扩展框架将使你的Web开发便得简单。我将在接下来的几章中用最详细的解释说明这些最重要的特征。 

组件基础的框架模式

JSF为标准HTML中每个可用的输入域提供了组件标签,你也可以为你应用中的特殊目的定义自己的组件,或者也可以将多项HTML组件组合起来成为一个新的组合——例如一个数据采集组件可以包含三个下拉菜单。JSF组件是有状态的,组件的状态由JSF框架来提供,JSF用组件来处理HTML响应。

JSF的组件集包含事件发布模型、一个轻量级的IoC容量、拥有其它普遍的GUI特征的组件,包括可插入的渲染、服务器端校验、数据转换、页面导航控制等等。作为一种基于组件的框架,JSF极具可配置性和可护展性。大部分的JSF功能,比如导航和受管bean查询,可以被可插入组件所替换。这样的插入程度给开发人员构建Web应用GUI时提供了极大的弹性,允许开发人员将其它基于组件的技术应用于JSF的开发过程中来。比如说,你可以将JSF的内嵌IoC框架替换为更加全能的Spring的IoC/AOP以供受管bean查询。 

JSF和JSP技术

一个JSF应用的用户表层被包含在JSP(JavaServer Pages)页面中。每个JSP页面包含有JSF组件,这些组件描绘了GUI功能。你可以在JSP页面里使用 JSF 定制标签库来渲染UI组件,注册事件句柄,实现组件与校验器的交互及组件与数据转换器的交互等等。 

那就是说,JSF不是固定与JSP技术相关联。事实上,JSF标签仅仅引用组件以实现显示的目的。你会发现当你第一次修改一个JSP页面中某一JSF组件的属性,在重新载入页面的时候并没有发生应有的变化。这是因为标签在它自己的当前状态中进行查询,如果组件已经存在,标签就不会改变它的状态。组件模型允许控制代码改变一个组件的状态(例如将一个输入域置为不可用),当视图被展现的时候,组件树的当前状态也随着一览无余。 

一个典型的JSF应用在UI层无需任何的Java代码,只需要很少的一部份JSTL EL( JSP 标准标签库,表达式语言)。前面已经提及,有非常多IDE工具可以帮助我们构建JSF应用,并且有越来越多的第三方JSF GUI组件市场。不使用所见即所得工具来编写JSF也是可行的。 

JSF和MVC

JSF技术是在多年Java平台上的Web开发技术的总结的产物。这种趋势起源于JSP。JSP是一种很好的表现层,但同时它容易将Java代码与HTML页面混淆起来。另一个不好的原因与Model 1架构有关,它使得开发人员通过使用 <jsp:useBean> 标签将许多原本应该在后端处理的代码引入到Web页面中来。这种方法在简单的Web应用中可以运行得很好,但许多Java开发者不喜欢这种类似C++的静态引入组合方式,所以Model 2架构随之被引进。 

本质上,Model 2架构是MVC Web应用的一种瀑布模型版本。在Model 2架构中控制器通过Servlets来表现,显示层则通过JSP页面来展现。Struts是一种最简单的Model 2实现,它使用Actions取代了Servlets。在Struts中应用的控制逻辑与数据层(通过ActionForms来展现)相分离。反对Struts的主要的不满在于,Struts更多偏向程序化,而非面向对象。WebWork和Spring MVC是Model 2的另外两种框架,它们比起Struts更大的改进在于尽量减少程序化,但是两者都没有Struts普及。另外两者也不像JSF般提供组件模型。 

大多数Model 2框架真正的因素在于它们的事件模型显得过于单薄,留下了太多的工作需要开发人员自己来处理。一个富事件模型使多数用户所希望的交互变得简单。许多Model 2技术就像JPS一样,很容易将HTML布局及格式与GUI标签相混合,在表现上不像真正的组件。而一些Model 2的架构(如Struts)错误地将表现与状态分离,便得许多Java开发人员感觉他们像是在编写COBOL程序。 

富MVC环境

JSF提供一种组件模型及比其它Model 2实现更为丰富的MVC环境。本质上说,JSF比Model 2架构更接近真正的MVC开发环境,虽然它仍然属于无状态协议。JSF能够构建比起其它Model 2框架更精彩的事件驱动 GUI 。JSF提供一系列事件选项如menu选项的selected事件,button的clicked事件等等,这与在其它Model 2框架中更多地依赖简单的“request received”不同。 

JSF的易于翻转的事件模型允许应用程序更少地依赖HTTP实现细节,简化你的开发量。JSF改善了传统Model 2架构,它更容易将表现层和业务层移出控制器,并将业务逻辑从JSP页面中移出。事实上,简单的控制器类根本无需与JSF相关联,方便了对控制器类的测试。与真正的MVC架构不同,JSF不会在多于一个视点上发出多条事件,我们仍然是在处理一个无状态的协议,这使得这一点变得无关紧要。系统事件对一个视图的变动或更新通常来自于用户的请求。 

JSF的MVC实现细节

在JSF的MVC实现中,作为映射的backing bean在视图和模型间扮演着协调的作用。正因如此,在backing bean里限制业务逻辑和持久层逻辑就显得犹为重要。一种普遍的做法是将业务逻辑置入应用模型中。这样的话backing bean仍会映射模型对象以供视图显示。另一种选择是将业务逻辑放入业务代理——一种与模型相作用的表层。 

与JSP技术不同,JSF的视图实现是一种有状态组件模型。JSF的视图由两部分组成:根视图和JSP页面。根视图是UI组件的集合,它负责维护UI的状态。就如像Swing和AWT,JSF组件使用组合式设计模式来管理组件树,用按钮来管理事件句柄及动作方法。 

Figure 1 通过MVC角度来透析的示例 

 

 

一个 JSF 例子

在文章剩余的章节里,我将专注于构建一个 JSF 应用的过程。这个例子是 JSF 技术的一个非常简单的展现,主要表现在以下几个方面:

  • 如何设置 JSF 应用程序的格局
  • 如何为 JSF 配置 web.xml 文件
  • 如何为程序配置 faces-config.xml 文件
  • 编写模型(即 backing bean )
  • 利用 JSP 技术构建视图
  • 利用传统标签库在根视图框建组件树
  • Form 的默认校验规则

例子是一个简单的计算器程序,使目标用户能够输入两个数并计算。因此页面上有两个文本域,两个标签,两处错误提示和一个提交按钮。文本域用来输入数字,标签用来标示输入的文本域,错误提示用来显示文本域中的输入在校验或数据转换时出现的错误。总共有三个 JSP 页面: index.jsp 用来重定向到 calculator.jsp 页面; calculator.jsp 用来显示上面提及的 GUI ;result.jsp 用来显示最后结果。一个受管 bean : CalculatorController 作为 calculator.jsp 和 result.jsp 的 backing bean 。

  Figure 2 示例程序的第二张 MVC 视图

 

构建应用 
  

为了构建计算器程序,你需要如下步骤:

  1.   收集 web.xml 和 faces-config.xml 文件,可以在下面的示例源代码中找到。
  2. 在 web.xml 中声明 Faces Servlet 和 Faces Servlet 映射。
  3. 在 web.xml 中指定 faces-config.xml 。
  4. 在 faces-config.xml 中声明受管于 JSF 的 bean 。
  5. 在 faces-config.xml 中声明导航规则。
  6. 构建模型对象 Calculator 。
  7. 用 CalculatorController 与 Calculator 交互。
  8. 创建 index.jsp 页面。  
  9. 创建 calculator.jsp 页面。
  10. 创建 results.jsp 页面。

声明 Faces Servlet 和 Servlet 映射 
  

为了使用 Faces ,你需要在 web.xml 中装载 Faces Servlet 如下: 

<!-- 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>

这和大多数web.xml描述文件类似,所不同的是将request的掌握权转向JSF Servlet,而非自定义的Servlet。所有请求引用了f:view的JSP文件的request都将通过此Servlet。因此需要增加相应的映射,并且将允许使用JSF的JSP技术通过映射装载进来。

<!-- Faces Servlet Mapping -->
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/calc/*</url-pattern>
</servlet-mapping>

以上配置告知Faces Servlet容器将所有符合/calc/的请求都转给Faces Servlet处理。这使得JSF能够初始化JSF内容和根视图。

定义faces-config.xml文件

如果你将你的faces配置文件命名为faces-config.xml并将其放置在WEB-INF目录下,Faces Servlet会查找到它并自动使用它。或者你也可以通过web.xml 中的一个初始化参数装载一个或多个应用配置文件——javax.faces.application.CONFIG_FILES——通过逗号将作为参数的文件列表分隔开来。或许你会使用第二种方法为所有的JSF应用程序作配置。

声明bean管理

接着是声明哪个bean被JSF GUI组件所使用。在示例中只有一个受管bean,在faces-config.xml中配置如下:
<faces-config>
    ...
  <managed-bean>
    <description>
      The "backing file" bean that backs up the calculator webapp
    </description>
    <managed-bean-name>CalcBean</managed-bean-name>
<managed-bean-class>
    com.arcmind.jsfquickstart.controller.CalculatorController
</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
</faces-config>

上面的配置告知JSF要往JSF环境中增加一个叫做CalcBean的bean,你也可以把受管bean取任何名字。Bean已经声明了,然后你要做的就是为应用定义导航规则。

定义导航规则

这样一个简单的应用,你只需使导航规则从calculator.jsp到result.jsp页面即可。如下:

<navigation-rule>
  <from-view-id>/calculator.jsp</from-view-id>
  <navigation-case>
    <from-outcome>success</from-outcome>
    <to-view-id>/results.jsp</to-view-id>
  </navigation-case>
</navigation-rule>

上面的代码表明,如果一个动作从/calculator.jsp页面返回逻辑输出为“success”,那么它将用户转向/result.jsp页面。

 视计模型对象

 由于我们的目标是介绍如何开始JSF,所以我将模型对象设计得非常简单。这个模型包含在一个对象里,如Listing 1所示。

 Listing 1. The Calculator app's model object

package com.arcmind.jsfquickstart.model;

/**
 * Calculator
 *
 * @author Rick Hightower
 * @version 0.1
 */
public class Calculator {
    //~Methods------------------------------------------------
    /**
     * add numbers.
     *
     * @param a first number
     * @param b second number
     *
     * @return result
     */
    public int add(int a, int b) {
        return a + b;
    }

    /**
     * multiply numbers.
     *
     * @param a first number
     * @param b second number
     *
     * @return result
     */
    public int multiply(int a, int b) {
        return a * b;
    }
}

这样,业务逻辑就全部建立了。下一步便是将其显示在Web表现层上。

 结合模型和视图

 控制器的目的就是将模型与视图结合在一起。控制器对象的一个作用就是保持模型与视图的不可知论。(?)如同你在下面所看到的,控制器定义了三个JavaBeans属性,通过这些属些控制输入和输出结果。这些属性是:results(作为输出)、firstNumber(作为输入)、secondNumber(作为输入)。Listing 2是CalculatorController的代码。

 Listing 2. The CalculatorController

package com.arcmind.jsfquickstart.controller;

import com.arcmind.jsfquickstart.model.Calculator;

/**
 * Calculator Controller
 *
 * @author $author$
 * @version $Revision$
 */
public class CalculatorController {
    //~ Instance fields --------------------------------------------------------

    /**
     * Represent the model object.
     */
    private Calculator calculator = new Calculator();

    /** First number used in operation. */
    private int firstNumber = 0;

    /** Result of operation on first number and second number. */
    private int result = 0;

    /** Second number used in operation. */
    private int secondNumber = 0;

    //~ Constructors -----------------------------------------------------------

    /**
     * Creates a new CalculatorController object.
     */
    public CalculatorController() {
        super();
    }

    //~ Methods ----------------------------------------------------------------

    /**
     * Calculator, this class represent the model.
     *
     * @param aCalculator The calculator to set.
     */
    public void setCalculator(Calculator aCalculator) {
        this.calculator = aCalculator;
    }

    /**
     * First Number property
     *
     * @param aFirstNumber first number
     */
    public void setFirstNumber(int aFirstNumber) {
        this.firstNumber = aFirstNumber;
    }

    /**
     * First number property
     *
     * @return First number.
     */
    public int getFirstNumber() {
        return firstNumber;
    }

    /**
     * Result of the operation on the first two numbers.
     *
     * @return Second Number.
     */
    public int getResult() {
        return result;
    }

    /**
     * Second number property
     *
     * @param aSecondNumber Second number.
     */
    public void setSecondNumber(int aSecondNumber) {
        this.secondNumber = aSecondNumber;
    }

    /**
     * Get second number.
     *
     * @return Second number.
     */
    public int getSecondNumber() {
        return secondNumber;
    }

    /**
     * Adds the first number and second number together.
     *
     * @return next logical outcome.
     */
    public String add() {
        
        result = calculator.add(firstNumber, secondNumber);

        return "success";
    }

    /**
     * Multiplies the first number and second number together.
     *
     * @return next logical outcome.
     */
    public String multiply() {

        result = calculator.multiply(firstNumber, secondNumber);
        
        return "success";
    }
}

请注意,在Listing 2中乘法和加法都返回“success”。“success”表明一个逻辑输出,它不是保留字,它是与faces-config.xml的导航规则相结合的,通过增加或定义操作执行,页面将转向result.jsp。

 这样,你已经完成了后台代码工作。接下来我们将定义JSP页面及组件树以展示应用视图。

 创建index.jsp页面

 Index.jsp的目的是保证/calculator.jsp页面被正确的裁入JSF内容中,使得页面可以找到正确的根视图。

<jsp:forward page="/calc/calculator.jsp" />

 这个页面全部所做的工作便是将用户重定向到/calc/calculator.jsp页面。这样便将calculator.jsp页面导向JSF内容中,使得JSF可以找到根视图。

 创建calculator.jsp页面

 Calculator.jsp是整个计算器应用程序的中心视图。这个页面获取用户输入的两个数字,如Figure 3所示。

Figure 3. The Calculator page

 

这一页较为复杂,我将一步一步进行讲解。首先你需要声明供JSF使用的标签库:

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

上述代码告诉JSP引擎你将使用两个JSF标签库:html和core。Html标签库包含所有与forms和其它HTML规范有关的标签。Core标签库包含所有逻辑、校验、控制等JSF标签。

 一旦将页面展示在普通的HTML中,你会告诉JSF系统你将使用JSF来管理你的组件。通过使用<f:view>标签来实现这点,它会告知容器你将使用JSF来管理所有包含在它里面的组件。

 少了<f:view>,JSF将无法构建组件树,稍后也无法在已经创建起来的组件树中进行查询。通过以下代码使用<f:view>:

<f:view>
  <h:form id="calcForm">
     ...    
  </h:form>
</f:view>

上面的第一行是<f:view>的声明,告知容器它受管于JSF。下一行是<h:form>标签,它告知JSF你需要在此建一个HTML FORM。在位于FORM组件内的组件容器的语法渲染期间,所有的组件将会被问询自动渲染,这样它们将会生成标准的HTML代码。

接下来,你告知JSF在FORM里所需的其它组件。在<h:form>中定义了一个panelGrid。panelGrid是一个组合组件——也就是一个组件里包含有其它的组件。panelGrid定义其它组件的布局,Listing 3显示如何定义panelGrid的代码:

Listing 3. Declaring the panelGrid

<h:panelGrid columns="3"> 
  <h:outputLabel value="First Number" for="firstNumber" />
  <h:inputText id="firstNumber" value="#{CalcBean.firstNumber}" required="true" />
  <h:message for="firstNumber" />    
  <h:outputLabel value="Second Number" for="secondNumber" />
  <h:inputText id="secondNumber" value="#{CalcBean.secondNumber}" required="true" />
  <h:message for="secondNumber" />
</h:panelGrid>

属性columns被定义为3表明所有组件将被布置在拥有3列空间的格局中。我们在panelGrid中加入了6个组件,共占2行。每一行包含一个outputLabel,一个inputText和一条message。Label和message都和inputText组件关联,因此当一个校验错误或或误信息关联到textField时,信息将会显示在message组件上。两个文本输入域都要求有,如果在提交的时候检测到无值,错误信息将会被创建,控制也将会转到这个视图来。

注意到两个inputFields都使用一条JSF表达式来做数值绑定。乍一看这很像一条JSTL表达式。但是,JSF表达式确实与绑定着后台代码相应字段的输入域相关联。这种关联是反向的,如果firstNumber是100,那么在form显示的时候100也会被显示。同样,如果用户提交一个有效的值,例如200,那么200也将作为firstNumber的新值。

一个更加实用的目的是,后台代码通过绑定模型对象的属性的值到输入域中,从而将模型对象展现出来。你将在此节稍后看到关于此目的的例子。

除了输入域,calForm通过panelGroup中的两个commandButton与两个动作关联:

<h:panelGroup>
    <h:commandButton id="submitAdd" action="#{CalcBean.add}"  value="Add" />
    <h:commandButton id="submitMultiply" action="#{CalcBean.multiply}" value="Multiply" />
</h:panelGroup>

panelGroup的概念与panelGrid很相似,除了它们显示方式的不同。命令按钮利用action=”#{CalcBean.add}”将按钮与后台动作绑定在一起。因此当这个form通过这个按钮提交的时候,相关联的方法便开始执行。

创建results.jsp页面

Results.jsp页面是用来显示最后计算结果。如Listing 4所示:

Listing 4. The results.jsp page

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
...
<f:view>
  First Number: <h:outputText id="firstNumber" value="#{CalcBean.firstNumber}"/> 
  <br />
  Second Number: <h:outputText id="secondNumber" value="#{CalcBean.secondNumber}"/>
  <br />
  Result: <h:outputText id="result" value="#{CalcBean.result}"/> 
  <br />
</f:view>

Results.jsp是一个相对简单的页面,将加法运算的结果显示给用户。通过<h:outputText>标签来完成这一功能。<h:outputText>标签带有id和value属性,value属性输出值,它在渲染的时候将被当作string看待。Value属性通过JSF将要输出的值与后台代码的属性绑定在一起。

 运行程序!

运行war文件所映射的程序。Index.jsp页面将调用calculator.jsp,如果你在firstNumber域或secondNumber域中输入非法文本(如“abc”)提交,你将返回到/calculator.jsp视图并且相应的错误信息将会显示在页面上。如果你将firstNumber或secondNumber放空提交你也将会获得相应的错误结果。由此可以看出在JSF许多校验几乎是自动定义,只要你将输入域定义为required并且绑定相应的int属性的字段即可。

Figure 4显示应用程序如何处理校验和数据转换数据。

Figure 4. Validation and data conversion errors


 

总结

在这篇关于JSF的介绍中是否使你有些头晕?不用担心,你已经跨过了最坏的一道坎。了解JSF的框架概念是这场战役的一半,有过之而无不及,而且你将会很快意识到它的价值。

如果在阅读的过程中你想象使用Struts实现上述代码会更加简单,我估计它会耗费至少两倍精力。用Struts构建同样的应用,你需要为两个按钮创建两个action类,各自需要自己的对应的动作映射。而且需要一个动作映射来装载首页(假设你遵守Model 2的建议)。另外,模仿JSF默认的错误获取和校验机制,你需要为Struts配置校验框架或者实现在ActionForm中实现等值的validate方法。你还必须在Struts配置中定义DynaValidatorForm或者建立一个ActionForm重写validate方法,或者使用ValidatorForm的子类。最终,你或许(必须)需要为所有的action定义forward或者全局forward。

不止双倍的代码工作,Struts对于初学者来说意味着需要花费更多的精力。我之所以知道这点,是因为我写过关于Struts和JSF课程的教材并且为我的学员们上过课。开发人员通常非常容易掌握JSF,但在学习Struts的过程中却倍受折磨。我相信更多有远见的人选择JSF,而非Struts。直觉上说,JSF更加合理。Struts已经被搁置,JSF被列入技术清单。在我的书中,JSF开发过程是简便的,并且比Struts更具有生产率。

这是JSF系列的第一篇文章的总结。在下一篇文章里我会继续这篇文章的话题,内容覆盖JSF的request处理生命周期,指明生命周期中同一应用程序的不同部分。我还将介绍immediate event handling的概念,并且让你对JSF的组件事件模型有更全面了解,包括关于许多内嵌组件的讨论。我还将谈谈有关JSF与JavaScript相结合的话题,请关注下个月的文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值