是时候让Facelets开始使用XSD了

发布者    |       讨论 JSF

我已经有这个想法有一段时间了,关于在Facelets模板中使用XSD。我认为我们应该停止假装Facelets模板是XHTML文档,而是开始将其视为无限制的XML。这将使我们能够通过XML Schema扩展XML方言,充分利用XML Schema提供的类型强制、语法识别和工具支持。

我在JSFOne上尝试向JSF EG传达这个想法,但我担心我没有很好地表达出来,因为这个想法从未被采纳。我想在这里详细解释这个方法,看看是否有人认为它有价值。在这篇文章中,我概述了一种更好地利用Facelets当前优势的方法,并提议在JSF 2.1中进行更改以支持这种用法。现在,我只是在公开提出这个想法。

Facelets简介

熟悉JSF的每个人 肯定 到现在都应该听说过Facelets了。 Facelets 是JSF的替代视图处理器和视图定义语言,比JSP更优越。但Facelets不仅仅是替代品。我真诚地认为,Facelets挽救了JSF,如果不是有像Facelets这样优雅的东西出现,让开发者有机会尝试JSF,他们早就放弃了这个框架。Facelets确实意义重大。为了认可其影响,Facelets将成为JSF 2.0的标准的 页面声明语言 --所以如果你一直躲在岩下,你很快就要出来了。

Facelets成功的原因

Facelets有两个特点,使其比JSP更适合UI开发

  1. XML标记被直接转换为JSF UI组件
  2. XML标记必须是有效的

第一个要点非常重要,因为它意味着在视图模板到组件树构建过程中,中间商已被驱逐。JSF最初的设计是为了能够利用JSP页面及其标签处理器。每个JSF组件都一对一地映射到JSP标签处理器。这意味着JSF不需要处理和解析XML(或者说是经典JSP语法中的伪XML)。相反,JSP会将UI组件提供给JSF组件树构建过程。

从重用的角度来看,这听起来不错,但结果却让开发者陷入了可怕的噩梦。JSP标签处理器为每个组件复制了组合UIComponent和组件渲染类属性。这意味着开发者需要编写和维持更多的类。更不用说,还要同步标签库描述符(TLD)元数据,以使JSP解析器满意。为了减少工作量,许多开发者转向代码生成,这在某种程度上表明设计存在严重缺陷。

接下来是Facelets。Facelets提供了自己的XML编译器和标签处理器。在这个情况下,标签处理器是通用的,配置简单。Facelets可以反射UIComponent和组件渲染类,以确定如何将XML文档中的信息映射到JSF组件树中的对象,这两个对象都由Facelets构建器创建。Facelets有效地切断了中间商。更重要的是,通过控制编译过程,Facelets能够引入自己的高级模板机制(称为组合),允许一个Facelets模板成为另一个模板的模板,几乎类似于LISP方言。

虽然直接构建是Facelets成功的重要原因,但上面列出的第二个要点与本文最为相关。Facelets通过强制执行有效的XML语法,终于摒弃了经典JSP所支持的荒谬的伪XML语法。甚至JSP标签库指令也被移除,转而使用XML命名空间声明来导入组件集。

Facelets == XML

如果你的模板中有一个未声明的实体,Facelets将终止处理并显示一个漂亮的(是的,真的!)错误页面。当Facelets报告错误时,它可以非常精确地(例如,行号和列号)报告,因为它使用SAX编译器在构建组件树之前解析模板。

现在,我将说服你为什么使用有效的XML是件好事。

利用XML生态系统

XML无处不在。因此,可用的XML编辑器可能和电子邮件客户端一样多。由于Facelets强制执行有效的XML语法,这意味着我不必等待供应商创建一个支持专有语法的编辑器,就像JSP那样。相反,Facelets打开了使用任何可用的XML工具来创建/修改/翻译Facelets视图模板的大门。这是一个非常重要的好处。

但有一个问题...实际上只是一个小插曲。为了支持一种实验性的设计时技术,Facelets偏离了正确的道路,没有充分利用XML提供的工具优势。让我解释一下Facelets的创建者想要达到的目标,然后向您展示如何利用他错过的好处。

通过直接解析模板,Facelets能够克服早期JSF-JSP集成中最糟糕的怪物之一:<f:verbatim>标签。您看,JSF响应中的每个字符都必须由JSF组件产生。这意味着所有静态HTML标记都必须以某种方式附加到组件上。在JSP中实现这一点的唯一方法是通过引入一个包装标签,<f:verbatim>然而,这个标签不仅让模板看起来又丑又啰嗦,而且还使得创建有效的XML变得几乎不可能,因为它忽略了HTML结构。

又一次,Facelets救了场。每当Facelets遇到静态标记时,它会自动创建一个UIComponent来包装标记,并在编码时输出。这也意味着Facelets模板可以匹配JSP页面的配方:静态HTML标记与动态部分混合。

但Facelets在假设(在一般情况)文档本身是一个包含JSF组件的HTML模板上走得太远了。在页面顶部(至少是顶级模板)包含一个XHTML DOCTYPE变得很常见,Facelets然后在编码组件树时将其传递给响应。Facelets甚至采取措施通过允许使用专有的jsfc属性(JSF组件标签的名称定义为属性的值)在标准XHTML标签上定义JSF组件来隐藏这些组件。这里的理论是,当模板在HTML查看器中打开时,它就像任何其他静态HTML文档一样渲染,从而允许设计师预览页面。理论上。

<ul jsfc="ui:repeat" var="_name" value="#{bean.names}">
    <li>#{_name}</li>
</ul>

但现实中,这个理论根本无法扩展。除非你只是开发一个主要由静态网站组成,动态部分散布在少数页面上,否则最终你会到达这样一个点,即这种《假装》的HTML文档会同时成为页面设计师和开发者的障碍。特别是这对开发者来说很痛苦,因为它限制了他/她重用模板逻辑或将它们封装成自定义组件的能力。我做了很多Web开发,唯一能让这种关系工作的是使用像JBoss Tools视觉设计器这样的工具,该工具可以在设计时提供页面运行时视图。否则,设计师应该简单地在自己的原型上工作,并在准备好集成到应用程序中时将其交付给开发者。

摆脱XHTML的枷锁

虽然Facelets模板是有效的XML,但大多数开发者将它们视为XHTML文档。问题是,XHTML词汇表是不可扩展的,尤其是在文档与DOCTYPE相关联时。这意味着文档中的所有JSF组件标签都被视为外来物体,导致XHTML解析器大声抱怨。再一次,你必须等待供应商创建一个支持这种专有语法的编辑器。那么这种支持看起来是什么样子呢?嗯,很可能编辑器将不得不解析TLD文件,并基于该元数据提供某种代码辅助(即,标签自动完成)。专有,专有,专有。真恶心。

但等等,我们刚才不是刚刚强调Facelets模板是有效的XML吗?当然!那么,有一个非常好的XML词汇表扩展系统,称为XML Schema。XML Schema将一个XML方言与一个XML命名空间关联起来。与DTD不同,这个合同允许将多个词汇表导入到单个文档中,从而使其能够无限扩展。此外,XML Schema的特定性远远超过了DTD允许的范围。事实上,XML Schema非常详细,有了它的支持,你可以说XML是一种《类型安全》语言。这种类型系统转化为代码辅助和验证,大大减少了使用XML的痛苦。

而且你知道吗?你今天使用的许多Java框架的配置文件都依赖于XML Schema文档(XSD)来描述、扩展、模块化和记录允许的元素和属性。这里有一些例子

  • Seam的组件描述符(components.xml, .component.xml)
  • Seam的页面描述符(pages.xml, .page.xml)
  • Faces配置资源(faces-config.xml)
  • Web应用程序描述符(web.xml)
  • Spring配置文件

为什么不给这个列表添加 Facelets 呢?没有任何阻止我们这样做!我们唯一需要做的就是为每个我们使用的 JSF 组件库(例如 Facelets UI、JSF 核心、HTML 基础等)编写一个 XSD,并通过将其与组件库的 XML 命名空间相关联来导入 XSD 中定义的类型。

为每个组件库创建 XSD 确实是一项繁琐的任务。您需要为每个组件标签定义一个元素,然后列出所有组件特定和渲染器特定的属性。但是,我们还不应该丢弃那些 TLD 文件!正如 Mark Ziesemer 在 他的博客 中指出,TLD 文件也是 XML。因此,您可以通过编写一个 XSLT 文档来将 TLD 转换为 XSD,从而更聪明地工作,而不是更辛苦。幸运的是,马克已经为我们做了这件事!我使用了 他的 XSLT 脚本 来为标准 JSF 组件集创建 XSD。一旦创建,我将它们与下面的 xsi:schemaLocation 属性中的命名空间匹配起来。请注意,要使用此属性,有必要声明 XML Schema 的命名空间。

<?xml version="1.0" encoding="UTF-8"?>
<f:view 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:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/jsf/facelets facelets-ui-2.0.xsd
        http://java.sun.com/jsf/core jsf-core-2.0.xsd
        http://java.sun.com/jsf/html html-basic-2.0.xsd">
    <html>
        <body>
            <h2>My name is Duke. What's yours?</h2>
            <h:form id="helloForm" prependId="false">
                <h:graphicImage url="/wave.png"/>
                <h:inputText id="name" value="#{HelloBean.name}"/>
                <h:commandButton id="submit" action="success" value="Submit"/>
            </h:form>
            <h2>Folks who stopped by to say "Hi!"</h2>
            <ul>
                <ui:repeat var="_name" value="#{GuestBook.names}">
                    <li>#{_name}</li>
                </ui:repeat>
            </ul>
        </body>
    </html>
</f:view>

如果您在能够处理 XSD 并提供代码辅助的任何 XML 编辑器中打开此文档(几乎所有 XML 编辑器都可以),现在除了标准 HTML 属性外,您还可以获得标准 JSF 组件元素和属性的标签完成!您可以下载 示例代码[1] 尝试一下。

唯一的缺点是根标签不是<html>,因此您无法使用标准 HTML 查看器获得即点即用的预览。假设这对您很重要,使根标签成为问题的是,HTML 无法在文档中容纳其他词汇。HTML 架构缺乏<html><xs:any>声明,这将允许存在外来的标签。不幸的是,HTML 架构几乎是固定的。有一个模块化 XHTML的尝试,但它尚未得到广泛采用。目前,将 HTML 标签交织到<f:view>文档根中比反过来更容易。现在,您可能认为创建 XSD 是创建 JSF 组件库过程中的又一步。我要说的是,从 TLD 到 XSD 的转换应该是一种从 TLD 迁移到 XSD 的迁移。XML Schema 在定义 XML 词汇方面非常全面,TLD 所能描述的任何东西,XML Schema 都能描述。与 TLD 不同,XSD 甚至能够声明组件支持哪些方面名称。 扔掉 TLD

那么,你的类型是什么,Doc?

在这个设置中有一个缺点。我们必须从文档的顶部移除 DOCTYPE 定义,这样 XML 编辑器就不会抱怨 JSF 组件标签(这些标签显然没有在 XHTML DOCTYPE 中声明)。XML Schema 是 DOCTYPE 的替代品,因此这两个是互斥的。但是,如果您这么想,DOCTYPE 不是用来验证模板的语法。它实际上是输出的一部分,旨在指导浏览器如何渲染页面。因此,DOCTYPE 应该由组件标签渲染,就像模板中的任何其他标记生成区域一样。

不幸的是,标准 HTML 组件库不包括一个输出 DOCTYPE 的标签。但是,您可以使用 JSF 2.0 组合组件(或您可以走得更远并创建一个真正的 UIComponent)快速定义这个标签的原型。以下是从文件 resources/meta/doctype.xhtml 中的片段定义了

<meta:doctype>标签作为一个组合组件然后您只需将此标签直接添加到

<comp:interface name="doctype" 
    displayName="XHTML DOCTYPE"
    shortDescription="Produces an XHTML DOCTYPE declaration">
    <comp:attribute name="type" required="true"/>
</comp:interface>
<comp:implementation>
    <h:outputText value='&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 #{
        fn:toUpperCase(fn:substring(compositeComponent.attrs.type, 0, 1))}#{fn:substring(compositeComponent.attrs.type, 1, -1)
        }//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-#{compositeComponent.attrs.type}.dtd"&gt;' escape="false"/>
</comp:implementation>

文档根中比反过来更容易。.

<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:meta="http://java.sun.com/jsf/composition/meta"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/jsf/facelets facelets-ui-2.0.xsd
        http://java.sun.com/jsf/core jsf-core-2.0.xsd
        http://java.sun.com/jsf/html html-basic-2.0.xsd">
    <meta:doctype type="strict"/>
    <html>
        ...
    </html>
</f:view>

很清楚,标准 HTML 组件库应该包含一个能够生成所有有效 HTML 和 XHTML DOCTYPE 的组件标签。没有理由让开发人员承担这样的基本负担。

引脚

告诉我您对这个想法的看法。您认为它会帮助开发吗?您认为它会带来更好的工具吗?如果这个想法值得追求,我认为应该从标准 faces-config.xml 中的元数据以及组件类/渲染器的反射中自动构建 XSDs,这样您,作为开发者,就不必担心创建它们并在文件系统中提供它们。当然,工具应该帮助您将 XSDs 添加到模板中。


返回顶部