何时使用 < ui: include > 、标记文件、复合组件和/或自定义组件?

我最近开始使用带有 Faclets 的 JSF 2.0,并且在了解了现有的 <ui:include>和 Facelets 1.x 提供的其他模板技术之后,对新的复合组件感到困惑。

这些方法之间有什么区别?在功能上,它们似乎提供了大致相同的功能: <ui:param> vs <cc:attribute><ui:insert> + <ui:define> vs 标记文件,重用现有的模板。在复合组件的情况下,除了语法和明确的接口规范之外,还有其他的东西吗?表现会有所不同吗?

57878 次浏览

What is the difference between those approaches?

Facelet templates

Use Facelet templates (as in <ui:composition>, <ui:include> and <ui:decorate>) if you want to split main page layout fragments into reuseable templates. E.g. header, menu, content, footer, etc.

Examples:

Facelet tag files

Use Facelet tag files if you want to have a reuseable group of components in order to prevent/minimize code duplication. E.g. a group of label+input+message components. The major difference with composite components is that the output of a Facelet tag file does not represent a single UIComponent and may in some circumstances be the only solution when a composite component doesn't suffice. Generally, having an <ui:include> with one or more <ui:param> which passes a managed bean property (and thus not a hardcoded value) is a signal that the include file can better be a tag file.

Examples:

Composite components

Use composite components if you want to create a single and reuseable custom UIComponent with a single responsibility using pure XML. Such a composite component usually consists of a bunch of existing components and/or HTML and get physically rendered as single component and is supposed to be bound to a single bean property. E.g. a component which represents a single java.time.LocalDate property by 3 dependent <h:selectOneMenu> components representing day, month and year, or a component which combines <p:fileUpload> and <p:imageCropper> into a single <my:uploadAndCropImage> referring a single custom com.example.Image entity as property.

Examples:

Custom components

Use a custom component whenever the functionality cannot be achieved with Facelet tag files or composite components, because of the lack of support in the standard/available set of components. Generally when you want a high degree of control and/or customization of the decoding and/or encoding, and also to offer the endusers the possibility to relatively easily extend/override the decoding and/or encoding. Examples can be found over all place in source code of open source component libraries such as PrimeFaces and OmniFaces.

Tag handlers

When you want to control the building of the JSF component tree instead of rendering of the HTML output, then you should use a tag handler instead of a component.

Examples:

Example projects

Here are some example projects which utilize all of above mentioned techniques.


Could performance differ?

Technically, the performance concern is negligible. The choice should be made based on the concrete functional requirements and the final degree of abstraction, reusability and maintainability of the implementation. Each approach has its own well definied purpose and limitations.

Composite components do however have a significant overhead during building/restoring of the view (specifically: during saving/restoring the view state). And, in older versions of Mojarra, composite components had performance issues with assigning default values, this is already fixed since 2.1.13. Also, Mojarra had a memory leak when a <cc:attribute method-signature> is used for method expressions, basically the entire component tree is re-referenced in HTTP session, this is fixed since 2.1.29 / 2.2.8. The memory leak can be bypassed in older 2.1 versions as below:

<context-param>
<param-name>com.sun.faces.serializeServerState</param-name>
<param-value>true</param-value>
</context-param>

Or in older 2.2 versions as below:

<context-param>
<param-name>javax.faces.SERIALIZE_SERVER_STATE</param-name>
<param-value>true</param-value>
</context-param>

Still, when you have relatively "a lot of" composite components, and you have javax.faces.STATE_SAVING_METHOD set to client, then the performance will be a pain. Do not abuse composite components if you merely want the basic functionality which is already possible with a simple include file or tag file. Do not use the ease of configuration (read: no *.taglib.xml file needed) as an excuse to prefer composite components over tag files.

When using Mojarra 2.2.10 or older, do not forget to disable the relatively short Facelets refresh period for production mode:

<context-param>
<param-name>javax.faces.FACELETS_REFRESH_PERIOD</param-name>
<param-value>-1</param-value>
</context-param>

Do not use this setting for development, otherwise you've to restart the whole server to get changes in Facelets files to be reflected! Mojarra 2.2.11 and newer, and MyFaces already defaults to -1 when javax.faces.PROJECT_STAGE is not set to Development.