SEAM IN ACTION 第5章


5、The Seam component descriptor

This chapter covers
■ Creating component definitions in XML
■ Defining component XML namespaces
■ Configuring a component’s properties
■ Using the Seam resource bundle

部件描述
在XML中建立部件描述定义
定义部件XML名字空间
配置部件属性
使用SEAM源簇


Seam embraces annotations to keep you out of the XML weeds. You are a Java (or
Groovy) developer, darn it, and that’s the language in which you should be allowed
to program your application! Despite this pragmatic statement, it would be misleading
to say that Seam eliminates XML entirely. It doesn’t. If you’re one of those XML
enthusiasts, you’ll be glad to know that you don’t have to give up your angled brackets
when you move to Seam. In fact, there are some areas of Seam where XML configuration
is the best choice—or even the only choice. One example is Seam’s page
descriptor, covered in chapter 3, which administers Seam’s page-oriented functionality.
1 Since views are defined in XML, it’s only natural for page controls to be defined
that way as well. The use of XML also ensures a quick turnaround by avoiding the compilation
step, which is important given that views often require a lot of tinkering.

SEAM喜annotations厌XML,但果你是XML的粉丝,你会发现SEAM还是支持XML的。并且一些地方XML还是最佳选择或唯一选择。
一个例子就是页描述符,


Component definitions are another example where XML proves useful. Annotations
such as @Name are easy enough to add to classes under your control. However, if
the class you intend to declare as a Seam component is sealed in a third-party JAR file
or maintained by another team, annotations don’t do you much good. There may also
be situations where you need to alter an existing component definition—either one
from your application or one of Seam’s built-in components—or assign property values
to that component. In these cases, you have to resort to external configuration,
which this chapter covers.

部件定义是XML的另一个用处。如果项目中有非SEAM的成员,则@Name就要慎用。

In the previous chapter, you gained an appreciation for the simplicity that annotations
bring to the development of Seam components. If you are content working with
annotations, I encourage you to skip ahead to the next chapter to learn about another
set of annotations that are used to wire components together and to initialize context
variables. On the other hand, if you are interested in learning how to define and configure
components in XML, this chapter shows how this task is accomplished using the
Seam component descriptor. The component descriptor contains XML-based metadata
that offers a way to keep the component definition separate from the class. You can also
use this file to assign initial property values to component instances, wire components
together, override the settings of existing components, and control built-in Seam functionality.
The XML in Seam isn’t all old-fashioned, though. Thanks to XML namespaces,
you may almost mistake the XML for a real language. In addition to XML, you’ll learn
that Java properties files can be used to accomplish certain types of configuration in
Seam. We’ll look at Seam’s internationalization (i18n) support as an example of configuring
a built-in Seam component and how keys in a message bundle can in turn be
used to supply locale-specific values to the properties of a Seam component. By the end
of the chapter, you’ll appreciate that both XML and Java properties serve as a valuable
supplement to Seam’s primarily annotation-based approach.

如果你很喜欢用注释方法,那直接跳到下一章好了,ok...

5.1 Defining components using XML
The last thing the world needs is another XML configuration file, right? After the
debacle that was EJB 2, a major theme of the EJB 3 rework was to do away with
required XML descriptors. That theme carries into Seam. As promised in the previous
chapter, Seam components can be authored strictly using annotations. So, while Seam
has an XML-based component descriptor, its use is entirely optional. Seam happily
bootstraps in its absence.

Let’s talk about those cases, though, when annotations are not well suited and XML
is warranted. The component descriptor supplements annotations in the following
cases:
■ Declare a class, which you can’t modify and that lacks a @Name annotation, as a
Seam component (admittedly you could extend the class as an alternative)
■ Install a class that’s not installed by default (the class has an @Install annotation
indicating that the component shouldn’t be installed)
■ Override a component definition setting, such as the scope or autocreate value
(the value from the descriptor always takes precedence over the annotation)
■ Configure bean properties of components that get applied to the component
instance (perhaps to externalize deployment-specific information)

There’s also the possibility that you simply prefer XML over annotations. In that case,
you can use the component descriptor to define all of your Seam components. Seam
affords you that flexibility, though I don’t recommend that approach. Either way,
Seam has a wealth of built-in functionality that is only an XML element away. Likewise,
you can use the component descriptor to dress up your own components once they
are set in stone (i.e., compiled).

I begin by providing an overview of the component descriptor; what it is, where it lives,
and the syntax it uses. I then explain how to use it to define and configure components.


5.1.1 Choosing your descriptor strategy
Seam’s XML-based component configuration can be partitioned across many files.
The component descriptor is a general term for the combined sum of the configurations
in all of the component descriptors on the classpath. Note that descriptor is a
fancy term for XML file.
Seam supports both general descriptors and fine-grained descriptors. The general
descriptor can hold an arbitrary number of component definitions, whereas the finegrained
descriptor is designed to govern the components for a single class. The name
of the general descriptor is components.xml, whereas fine-grained descriptors are
named using the file extension .component.xml.
The general descriptor is often placed in the WEB-INF directory of the web application,
which is where seam-gen stashes it. However, this file need not be confined to the
WEB-INF directory. Instead, it can be distributed across the classpath, allowing you to
organize your configuration the way that’s most suitable for you, rather than having to
jam every last component definition into a single file. One recommendation is to partition
your component descriptors by module so that each descriptor is centered on
the classes within that artifact. You can narrow it even further by putting a general
descriptor in each Java package. The most extreme solution to avoiding monolithic
component descriptors is to define every component in a fine-grained descriptor adjacent
to the subject class. The choice is up to you.
The rules regarding where the component descriptors can be placed are fairly loose.
The locations that the Seam component scanner visits are summarized in table 5.1,

listed in the order that they’re addressed by the scanner. Regardless of how you decide
to divide up your XML-based component configuration, Seam collects them, combines
them with the settings defined in annotations, and assembles a unified set of component
definitions in memory. From that unified set is where instances are born, as you learned
in the previous chapter.
TIP Although seam-gen places the components.xml file in the WEB-INF directory,
consider storing it in the META-INF directory instead, where it’s
accessible to unit and integration test environments that don’t recognize
the WEB-INF directory as part of the classpath.
That sums up where the component descriptor can be placed. Let’s open up the file,
have a look at its structure, and learn how to use it to create component definitions.


5.1.2 The structure of the component descriptor
The Seam component descriptor consists of one or more component definitions,
declared using the element and nested within the root
element. (In a fine-grained component descriptor, can be the root element.)
In addition to generic elements, Seam supports extension elements
through the use of XML namespaces to accommodate “type-safe” XML
component declarations. The component descriptor also accommodates a handful of
noncomponent elements, such as and , that are covered later in
this chapter and the next.
NOTE If you’ve worked with the Spring configuration file, you should feel right
at home using the Seam component descriptor. The main difference is
that instead of having a root element and child elements,
the Seam component descriptor has a root element
and child elements. Both Seam and Spring support
extension elements using XML namespaces.
Listing 5.1 shows a simple component descriptor with two components defined. For
the components shown, it is assumed that the @Name and @Scope annotations are
absent from the class definitions. Instead, these classes are declared to be components
using XML. If the classes had @Name annotations equivalent to these definitions, an
exception would result for reasons that are described in section 5.4 (which covers
component definitions overrides).


If Spring hasn’t made you tired of XML yet, then these definitions don’t look so bad.
The element defines a new Seam component for the class specified in
the class attribute. The name attribute is equivalent to the @Name annotation and the
scope attribute is equivalent to the @Scope annotation. The mappings between component
definition annotations and the XML element attributes are
shown in table 5.2.

You may notice that the XML equivalent of the @Role annotation is missing from this
list. Actually, it’s not. It’s the element itself. The component descriptor
supports an arbitrary number of component definitions for a single class. The only
requirement is that you assign a different component name to each definition using
the name attribute. In effect, the name attribute is the role name. You may find the
declaration to be more suitable for defining roles than the @Role annotation,
as I have.
Here, the role assigned to the Golfer entity in the previous chapter using the
@Role annotation is defined using the element instead:
class="org.open18.model.Golfer" scope="event"/>
From the standpoint of the application, there’s no difference in how the component
is constructed when it’s defined using annotations versus XML. Seam builds the same
internal representation of a component definition in both cases and uses it to dish out
component instances. But the XML version allows you to assign initial values to bean
properties, which is covered in section 5.3. Right now, the XML stanza appears basic
because all it’s doing is declaring the component.

WARNING

You can’t create XML-based component definitions for classes on the hotdeployment
classpath (sourced from the src/action folder of WAR projects
created by seam-gen). The component scanner that processes the
component descriptors can’t “see” classes in the hot-deploy classloader.
This shortcoming may be resolved in a future release. Regardless, it
defeats the purpose of using the hot-deploy classloader since components
defined in component descriptors are not hot deployable (hotdeployable
components can only be defined using @Name). The component
descriptor can still be used to register initial property values for hotdeployable
components.

Although the general component descriptor may seem simple enough to manage
with just a few component definitions in it, the trouble is that its size can quickly get
out of hand as you start relying on it more heavily. Narrowing in on a single configuration
becomes as challenging as finding a matching sock in a laundry pile. To prevent
this melting pot of configurations, let’s consider how to segment declarations by component
class using fine-grained component descriptors.

5.1.3 Fine-grained component descriptors
The fine-grained component descriptor is designed to make it more intuitive for the
developer to locate the configuration for a class, since it’s adjacent to the class it configures,
and to make the content of that descriptor be “task-oriented” since it focuses
on a single class. It also offers a nice alternative to using Seam component annotations—
especially if you shiver at the thought of using a lot of annotations on your
class—without losing the benefit of being in close proximity to the class.
Fine-grained descriptors are identified by the .component.xml file extension and
are used to configure a neighboring Java (or Groovy) class as a Seam component. The
name of the class to which the fine-grained descriptor corresponds is derived by stripping
the .component.xml extension from the descriptor’s resource path and converting
slashes (/) to dots (.). For example, the fine-grained component descriptor whose
resource path is org/open18/auth/PasswordBean.component.xml is used to configure
the org.open18.auth.PasswordBean class. The reverse logic is used to derive the
resource path of the fine-grained descriptor from the name of a class. Seam searches
in both directions when preparing the component definition.
You saw this dispersed approach to XML-based configuration in chapter 3 when
you were introduced to fine-grained page descriptors. They differ in that a finegrained
page descriptor only deals with a single element, whereas the finegrained
component descriptor is capable of accepting one or more elements,
depending on whether the root tag is or . If you
intend on declaring only a single component definition, you use as the
root element. A fine-grained descriptor with a single component definition need not
declare the class attribute, as the class name is derived according to the conversion
logic just described. The content of a fine-grained descriptor is shown here, which
includes the optional XML namespace declarations:

If you intend on using multiple declarations in the fine-grained descriptor, you make
the root element. However, by not using as the root element,
you lose the benefit of the implied value of the class attribute, which is now
required. What you do gain is the full capacity of the component descriptor for configuring
the class. You can assign one or more roles using multiple elements,
or you can use auxiliary elements like and , which are both
covered in the next chapter.

WARNING

There’s nothing stopping you from putting arbitrary component definitions—
definitions not related to the adjacent class—in a fine-grained
descriptor that uses as the root element. However, this
practice is discouraged because it hides component definitions in unexpected
places.


The downside of the fine-grained descriptor is that it is yet another XML file that you
have to manage. You also lose the type safety that annotations afford you. Fortunately,
there’s a compromise. Seam offers “type-safe” XML elements through the use of XML
namespaces and XML Schema, thus reducing the pain involved in working with XML.

5.2 XML namespaces in the component descriptor
You can hardly ignore the XML namespace declarations at the top of the component
descriptors presented thus far. In fact, they account for more than half of the characters
in the documents! Let’s see what this gratuitous metadata is all about and what it
buys you.

5.2.1 The purpose of XML namespace declarations
The namespace declarations that attach to the root element of the component
descriptor import a vocabulary of XML elements and attributes defined in W3C XML
Schema for creating and configuring components. The reason XML Schema is used is
because it offers a rich typing system and allows the vocabulary to be extended
through custom namespaces (akin to a Java package). That means the names, classes,
and bean properties of components can be reflected in the names of the XML elements
and attributes, and that strict validation of the markup can be enforced. It’s for
this reason that the XML is considered “type-safe.”

DON’T BE SO GENERIC
The http://jboss.com/products/seam/components namespace represents the
generic Seam component vocabulary, which provides the element
already covered. Aside from the root element, , the other elements in
this namespace are (for setting a property value), (which we
examine later in this chapter), and and , both of which are
described in the next chapter. Using this namespace alone, you don’t see much benefit
from using XML Schema over a less verbose alternative like DTD because the property
names in generic definitions can’t be validated. The benefit comes
in the extensive set of component-specific namespaces that Seam provides that widen
this vocabulary and make it type safe. You can also define your own XML namespaces,
as you’ll learn to do later in this section.

NOTE Each namespace that you import provides an XML vocabulary that maps
one-to-one with the names of component classes and their bean properties.
Therefore, I’ll refer to the namespaces from this point forward as
component XML namespaces.

Let’s look at an example where an element from a component XML namespace is used
to replace a generic element. The built-in component named org.jboss.seam.
core.init has a property named debug that controls Seam’s debug mode. With the
generic component namespace already declared, the debug mode property is set to true
using the following stanza, which references this component by its name (not class):

true

Instead of using generic elements to define or configure components, you can use
custom elements and attributes imported from a component XML namespace. The
vocabulary associated with the built-in http://jboss.com/products/seam/core
namespace includes the XML element (which maps to the Seam component
class org.jboss.seam.core.Init) and a set of XML attributes (which fit to the properties
of the class). By importing this namespace into the component descriptor and
binding it to the namespace alias core, it’s possible to use the qualified
element to set the debug property of the corresponding component to true, shown
here in bold:

This declaration assigns an initial value to the property of a built-in component, which
you’ll learn more about in section 5.3. The key point is that both the property name
and value are validated by the schema. Although the namespace declarations are
quite verbose, they help cut down on the number of characters needed throughout
the remainder of the component descriptor.

INFO


If you aren’t accustomed to XML Schema-based configuration, you may
be turned off by the clutter they introduce to the root element. However,
these formalities are your ticket to a friendly development experience.
The xsi:schemaLocation attribute maps the XML namespaces to XML
Schema Documents (XSDs), which the IDE retrieves and interprets to
provide you with XML tag completion, similar to what you get with Java
syntax. If you don’t need the IDE support, you can leave off the
namespace declarations.

Most built-in Seam components are associated with a namespace, which we explore
next. After a survey of the built-in namespaces, we tackle the mapping between XML
elements in a component namespace and Java classes.

A SURVEY OF SEAM’S BUILT- IN COMPONENTS
An itemization of all of the built-in components in Seam would be in vain, as they
are ever-changing. I want to at least give you a snapshot of the functional areas of
Seam. Table 5.3 lists Seam’s built-in namespaces along with a description of the components
they include.


NOTE
The version in the .xsd filename must match Seam’s major version. Thus,
if you’re using Seam 2.1.0.GA, the version should be 2.1. Table 5.3 lists
the 2.0 versions.

To register a component namespace in your component descriptor, first choose a component
namespace from table 5.3 and declare it as an XML namespace using an alias of
your choice. Next, add the namespace URI and schema location to the xsi:schema-
Location attribute on the root node. Then, you can use tag completion support in the
IDE to discover the available components since all of Seam’s built-in namespaces are
backed by an XML Schema vocabulary. If you are the exploratory type, I encourage you
to just add all of the namespaces from this table and see what your XML editor gives you.
While having fun with tag completion and exploring the built-in components that
Seam offers through XML, you may be wondering what these elements have to do with
Java classes and component definitions. The first step to understanding this relationship
is learning how the namespaces are associated with Java packages.

NAMESPACES AND JAVA PACKAGES
An XML namespace is a URI—a fancy way of saying a unique name. A namespace
looks like a URL, but it’s not mandatory that it resolve to a public document. The
namespace is mapped to an alias, such as core in the previous example. The name of
the alias is arbitrary. It’s used as a prefix on element names, such as , to
associate these elements with a particular namespace. The prefix isn’t required for
elements in the default namespace, which is set using the xmlns attribute. Typically,
component descriptors declare http://jboss.com/products/seam/components as
the default namespace. As such, the element doesn’t need a prefix.
Namespaces are similar to Java packages. In fact, Seam enforces a one-to-one relationship
between namespaces in the component descriptor and Java packages. You’ll
soon learn that the elements are transformations of Java class names and the attributes
the bean properties. The use of XML namespaces in the component descriptor
is the closest you can get to writing Java without actually using the Java syntax.
Let’s draft an XML namespace for the Open 18 application to replace the use of
the generic element. This lesson should also help you understand how
elements in a component XML namespace are interpreted so that you can make sense
of the syntax used to configure one of Seam’s built-in components.

5.2.2 Defining an XML @Namespace for components in a package
An XML namespace URI can be associated with a Java package using the @Namespace
annotation, summarized in table 5.4. @Namespace is a package-level annotation, which
means it’s placed above the package declaration in the package-info.java file.2 When
Seam encounters an XML element that’s not in the generic component namespace, it
looks for a @Namespace annotation to make the connection between the namespace
URI of that element and a Java package. Seam 2.1 supports an implied mapping
between namespace URI and Java package, making the @Namespace annotation just a
formality. The mechanics of this mapping are addressed in the next section.

Let’s create a namespace for the authentication package in the Open 18 application.
The contents of the package-info.java file in the org.open18.auth package are shown
here:
@Namespace(value="http://open18.org/components/auth")
@AutoCreate
package org.open18.auth;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Namespace;
Notice that in addition to the @Namespace declaration, other Seam component annotations
can be added to the package-info.java file to set defaults for any component in
that package. In this case, all components in the org.open18.auth package support
the autocreate functionality.

WARNING

As of Seam 2.0, the component scanner does not pick up @Namespace
annotations located on the hot-deploy classpath (sourced from the src/
action directory of seam-gen WAR projects). They must be on the main
classpath (e.g., the src/model directory).

You find similar declarations scattered throughout the Seam code base defining the
namespaces shown in table 5.3. Having created a component namespace of our own,
let’s see how it’s used to enable domain-specific markup in the component descriptor.

5.2.3 How XML namespaces are interpreted
The @Namespace declaration establishes a link between an XML namespace and
a Java package. (Again, in Seam 2.1, this mapping can be implied.) This relationship
is the key to extensible XML authoring of components. In other words, you
can define your components using custom XML elements just like Seam does its
built-in components.
As with Seam’s built-in namespaces, begin by adding the namespace from the
@Namespace annotation to the component descriptor. Then choose a namespace alias
for associating XML elements with this URI. Listing 5.2 shows the declaration of the
http://open18.org/components/auth namespace bound to the auth namespace
alias and a definition of a component in the associated Java package.

The element is a type-safe way of declaring the PasswordBean
class as a component. You don’t actually have to declare the XML namespace in the
root element. You have the option of using the namespace directly in the prefix of
the element:

The namespace alias is merely a shorthand syntax. Either way, the result is equivalent
to what’s achieved using the following generic component definition:
class="org.open18.auth.PasswordBean" scope="event"/>
Let’s explore how Seam interprets the type-safe declaration to derive the same set of
information provided by the generic component definition.

TRANSLATING XML INTO JAVA
The translation from the
element to a fully qualified Java class is shown in
figure 5.1.
When Seam encounters an XML element in
the auth namespace, it looks to see if there’s a
@Namespace annotation with a value that matches
the XML namespace URI. Indeed, the namespace
maps to a Java package as follows:
http://open18.org/components/auth -> org.open18.auth

As of Seam 2.1, there are two ways the Java packages can be derived from the XML
namespace URI if a matching @Namespace annotation isn’t defined. When the scheme
of the URI is http://, Seam implies the package name by stripping the www prefix (if
present), reversing the domain name, and appending the trailing paths as subpackages
(converting slashes to dots):
http://www.open18.org/auth -> org.open18.auth
When the namespace scheme is java:, rather than http://, the part of the URI that follows
the scheme is used as the package name:
java:org.open18.auth -> org.open18.auth
At this point, the namespace URI has served its purpose and Seam ceases to do anything
more with it. It just helps put Seam in the right playing field. Next, the simple
name of the class—the class name without the Java package—is derived from the local
name of the XML element, which in this case is password-bean. This conversion
occurs by making the first letter and any letter after a hyphen uppercase and dropping
the hyphens:
password-bean -> PasswordBean
The package org.open18.auth and the simple class name PasswordBean are assembled
to form the fully qualified Java class. The complete translation is as follows:
-> org.open18.auth.PasswordBean

NOTE

The benefit of using a custom XML element is that it eliminates the
need to specify the class attribute on the component definition.
Instead, the class is derived by adjoining the Java package assigned to
the element’s namespace URL and a translation of the element’s local
name from the XML element. If a Java package can’t be resolved from
the XML namespace URI of the element, an exception is thrown during
deployment that prevents the application from loading.

That takes care of the component class, but as you’ve learned, every Seam component
must be associated with a name and scope. Let’s see how they are assigned.
RESOLVING A COMPONENT NAME AND SCOPE
XML elements in a component namespace are treated as extensions to the
element. That means they inherit all of the standard attributes used to define a
component that were listed in table 5.2. Because the standard attributes are inherited,
the component name and scope can be specified as attributes on the custom element:

However, declaring the component name on a custom element isn’t necessary. Seam
uses the following search order to locate a name to assign to a component defined
using a namespace element in the component descriptor:
■ The name attribute on the custom XML element
■ The @Name annotation on the associated Java class
■ A value derived from the local name of the XML element
If a component name is not specified in the name attribute of the XML element or the
@Name annotation, Seam gets its hands dirty and derives a name from the Java class.
Seam begins by lowercasing the first letter of the simple name of the class to arrive at
the unqualified component name:
PasswordBean -> passwordBean
This is where the @Namespace annotation comes back into play. The @Namespace annotation
has a prefix attribute, whose value is used to qualify a component name just as
a Java package qualifies a class name. If the prefix attribute on the @Namespace annotation
is empty, then the unqualified component name is equivalent to the fully qualified
component name. In this example, the prefix attribute is empty, so the
component name remains passwordBean.
If the prefix attribute is not empty, the fully qualified component name is constructed
by combining the value of the prefix attribute with the unqualified component
name, separated by the dot (.) character. Assume for a moment that the
namespace had been defined as follows:
@Namespace(value = "http://open18.org/components/auth",
prefix = "org.open18.auth")
The component name derived from the declaration becomes
org.open18.auth.passwordBean. Don’t confuse this with the fully qualified class name.

To locate a scope, Seam consults the scope attribute on the custom XML element,
then checks the @Scope annotation on the class. If neither one is present, a scope is
chosen automatically according to table 4.6 in chapter 4.
You should now understand how Seam gets from to the built-in Seam
component name org.jboss.seam.core.init, shown in an earlier example, knowing
that there’s a @Namespace annotation declared on the org.jboss.seam.core package
with a prefix of the same name and namespace URI http://jboss.com/products/
seam/core. Let’s see how to get the IDE to make this type of association for our component
namespace.
ENABLING VALIDATION AND IDE TAG COMPLETION
Declaring the auth namespace alias in your component descriptor isn’t enough to get
the XML to validate or to give the IDE the information it needs to provide tag completion.
You still need to provide an XML Schema Document (XSD) for each of the component
namespaces that you declare. The XML Schema vocabulary for the auth
namespace, auth-1.0.xsd, is not shown here, but it’s available in the book source code
for this chapter. Once you’ve written that file, you add it to the xsi:schemaLocation
attribute in the component descriptor, shown here in bold:
http://jboss.com/products/seam/components"
xmlns:auth="http://open18.org/components/auth"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://open18.org/components/auth
http://open18.org/components/auth-1.0.xsd
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd">
...

If the XSD isn’t available at the URL provided, you need to make the correlation
between the auth-1.0.xsd file and its namespace in your XML editor to achieve XML
tag completion and validation. This works automatically for Seam’s built-in
namespaces since the XSD files are published to a public web server.
Although creating component XML namespaces may appear challenging, the
good news is that you are more often a consumer than you are a creator. Most of the
time you find yourself using the namespaces included with Seam to configure Seam’s
built-in components. Speaking of Seam’s built-in components, you may notice that all
of Seam’s components have fully qualified component names to avoid naming conflicts.
However, these long component names can be cumbersome to type. Let’s see
how to import context variable prefixes so it’s possible to address component
instances by their base names.
5.2.4 Importing a context variable prefix
Just as packages are used in Java to avoid naming conflicts between classes, prefixes
are used to avoid naming conflicts between component names. A context variable prefix
even uses the same dot (.) notation that you’re familiar with from Java packages.
To make referring to these names more convenient, Seam offers a way to import a set
of qualified context variable prefixes just like the import statement works in Java.
A context variable prefix is imported using the element in the component
descriptor so that you can reference the context variable according to its last segment—
its unqualified name. Assume for a moment that we assigned the org.
open18.auth prefix to the component namespace mapped to the auth XML namespace.
The context variable prefix could be imported with the following declaration in the
component descriptor:
org.open18.auth
This import statement applies globally across the application. It allows you to reference
the context variable for the PasswordBean component as passwordBean rather
than org.open18.auth.passwordBean.

TIP
I encourage you to use fully qualified context variable names for your
application’s components and then import the context variable prefixes
using the tag as needed. This becomes especially important if
you are building reusable Seam libraries.

All built-in Seam components use qualified component names to be polite and avoid
“stealing” context variable names you might need to use. You can find a complete list
of Seam’s built-in components in the Seam reference documentation. Given that
many of these components are so commonly used, Seam imports their context variable
prefixes automatically. At the time of this writing, that list includes the following:
■ org.jboss.seam.bpm
■ org.jboss.seam.captcha
■ org.jboss.seam.core
■ org.jboss.seam.faces
■ org.jboss.seam.framework
■ org.jboss.seam.international
■ org.jboss.seam.jms
■ org.jboss.seam.mail
■ org.jboss.seam.pageflow
■ org.jboss.seam.security
■ org.jboss.seam.security.management (Seam 2.1 or greater)
■ org.jboss.seam.security.permission (Seam 2.1 or greater)
■ org.jboss.seam.theme
■ org.jboss.seam.transaction
■ org.jboss.seam.web
This set of imports is especially convenient for those often-used Seam components.
One common component to reference in the UI is the FacesMessages class, bound to
the org.jboss.seam.faces.facesMessages context variable. You can see that the
namespace used by the context variable appears in the default list of imports, so you
can instead reference it using the abbreviated context variable name facesMessages.
You may use this component, for example, to iterate over the global JSF messages,
without having to use with the globalOnly flag:

#{msg.summary}

Component names are used as context variables to access Seam components, which
you learned about in the previous chapter. They also provide a means of configuring
initial property values for a component, which are applied to an instance after it’s created
(either by Seam or by a collaborating container). In the next section, you’ll learn
how to set the initial state of an instance by supplementing its component definition
with property values.

5.3 Configuring component properties
In the previous chapter you learned how a class enters into “component-hood” by way
of annotations and, in this chapter, as a result of XML-based declarations. But alone,
these definitions are just a fancy way of instantiating a class and weaving services into
it. Oftentimes, a component becomes useful only once its state is initialized, which
entails assigning initial values to its properties. This initialization comes in the form of
a simple property value, such as a connection string, or as a reference to another component,
effectively wiring components together. The property value assignments
occur prior to the instance being put to work as a context variable.

NOTE

The @Create and @PostConstruct life-cycle callback methods are executed
after the initial property values have been assigned to the component
instance.

In this section, you’ll learn how these initial property values are declared and what
types of values can be supplied. Before we get into the mechanics, let’s consider the
benefit of establishing the initial state of a component and find out what is meant by a
component property.

5.3.1 Component definitions as object prototypes
Where a component definition pays off is when it’s used to produce an object prototype.
As part of the component definition, you can store a set of property names and
associated values, which Seam picks up on startup and subsequently transfers to the
component instance after it’s instantiated. The prototype might serve to prepopulate
a form or to fill in fixed values that aren’t modifiable by the user, such as the date a
record is created. Let’s see how these properties get mapped to the class.
A property name is mapped to either a JavaBean-style “setter” method or a field on
the target object, herein referred to generally as a bean property. The field name shares
the same name as the property, whereas the setter method is derived by capitalizing
the property name and prefixing it with set (e.g., the createdDate property maps to
the setCreatedDate() method). If a property name matches both a field and a setter
method, the setter method takes precedence. The property value is then injected into
the method or field using reflection. When the dependency being injected is a reference
to another component instance, the mechanism is referred to as dependency
injection (DI), or more informally “component wiring” to borrow from the Spring
term “bean wiring.”


NOTE

The access level of the method or field on the target object doesn’t matter.
Seam can assign a value to a method or field of any access level, even
if it’s private—a privilege granted to reflection.


Unlike other parts of the component definition, component properties must be
defined in external configuration, rather than in annotations.3 Although Seam tries to
avoid unnecessary external configuration, namely XML, configurable properties is
one case where it makes sense to take advantage of the decoupling. Declaring a property
value outside of the Java source code allows you to achieve any of the following:
■ Adjust the runtime behavior of the application without having to recompile
(e.g., timeout period, debug mode, maximum number of query results)
■ Define different property values for different component roles
■ Declare references to other component instances, known as component wiring
It’s up to you to decide when and where to use component properties in your application.
The next choice to make is where to define that initial property value.


5.3.2 Where component properties are defined
You can add an initial property value to a component definition by declaring it in one
of three places, listed in the order of increasing precedence:
■ Component descriptor
■ Servlet context parameter
■ seam.properties
Chances are, you’ll use the component descriptor a vast majority of the time, given
that it’s the most flexible and convenient. Earlier you learned to use the component
descriptor to define Seam components with either a generic element or
an element bound to a component XML namespace. You can use the same elements
to assign property metadata, to augment the component definition, or to configure
an existing component. Section 5.4 clarifies this distinction.
As an alternative to using the component descriptor, you can configure component
properties using the standard Java properties syntax, herein referred to as external
property settings. External property settings can be defined in either the
seam.properties file or as servlet context parameters. Seam employs a simple naming
convention to determine how the property key is mapped to the bean property of a
Seam component, which we’ll go over when we look at this technique.

The remainder of this section takes a hands-on approach to explaining how to use
the formats just mentioned, drawing on use cases from the Open 18 application.
Given that you already have the component descriptor open, we’ll start with the XMLbased
component configuration.
DEFINING PROPERTIES IN THE COMPONENT DESCRIPTOR
Properties can be associated with any component definition declared in the component
descriptor. If you use the generic element, the properties are configured
using nested elements. The name of the property being configured is specified
in the name attribute of the element, and the value to be assigned is
specified in the body of the element (or within a nested element).
To associate the configuration properties with an existing component definition,
you specify the component’s name in the name attribute of the element.
If you want the declaration to also serve as a component definition, you
must also supply the class attribute. To learn the distinction between the two, see section
5.4.
Let’s assume that we want to configure the hashing algorithm and character set
used in the PasswordManager component, shown in listing 5.3. The digestAlgorithm
property determines the type of hash that’s calculated from the plain-text password,
and the charset property determines the encoding scheme applied to the password
prior to hashing it.

Initial property values must be assigned to customize the behavior of the hash()
method for the current application (and to prevent a NullPointerException). The
following declaration configures the digestAlgorithm and charset values for a component
named passwordManager using nested elements:

SHA-1
UTF-8

If there is no component named passwordManager in the Seam container, this configuration
serves no purpose. When the passwordManager component name is
requested, Seam instantiates a new instance of the corresponding class and then
applies the property values using reflection. The net effect is equivalent to the following
Java code:
PasswordManager passwordManager = new PasswordManager();
passwordManager.setDigestAlgorithm("SHA-1");
passwordManager.setCharset("UTF-8");
Contexts.getEventContext().set("passwordManager", passwordManager);
This snippet is intended to provide a general picture of how the property values affect
the instance. Of course, this is a generalization of what happens when a component is
instantiated. In actuality, there are a plethora of method interceptors that are wrapped
around the instance followed by execution of its life-cycle methods, if defined.
To make for a more concise declaration, properties can be defined as attributes on
the element. The component configuration for PasswordManager has
been modified to take advantage of this shorthand:

However, the attribute syntax comes with a very important disclaimer. You can’t use
the attribute syntax to configure a property whose name is a reserved word in the
XML vocabulary of the element. The reserved words follow; these attribute
names are interpreted as part of the component definition:
■ name
■ class
■ scope
■ auto-create
■ installed
■ startupDepends (Seam 2.1 or greater)
■ startup
■ precedence
■ jndi-name

Memorize this list or keep it close at hand. Otherwise, you’ll be very confused as to
why you get an error (or a silent failure) when you try to assign a value to a bean property
whose name is in this reserved list using an attribute on the element.
One way to handle this case is to use a nested (or namespace
element). Another option is to use an external property setting, covered later.
The third syntax for registering an initial property value in the component descriptor
is to use a nested element whose name is equivalent to the property that you’re
configuring. Once again, the component configuration for PasswordManager has
been rewritten to reflect this syntax:

SHA-1
UTF-8

If you’ve been following along with your XML editor, you know that the XML Schema
validator is not happy with these last two variations—using an attribute on
or nested element that shares the name of the property. That’s because the
generic component XML vocabulary doesn’t declare any attributes or elements with
the names digestAlgorithm or charset.
So what good is this syntax if it doesn’t validate? Well, it just doesn’t validate yet. You
simply need to educate the XML validator. As you learned, an element in a component
XML namespace extends from the generic element. You have to extend
the XML Schema of the generic namespace and append any custom attributes or
nested element names used by your component namespace. That requires that you
create an XSD, as covered in section 5.2.3. Then you can use the element-based syntax
to configure the property of a component and still have the document validate.
Granted, if you aren’t concerned about the document validating and just want to
make your XML cleaner without the overhead of creating an XSD, then there’s nothing
stopping you. Seam doesn’t mandate that the document validate against the
schema so as to avoid imposing an arbitrary restriction on you. Use of XSD is merely a
best practice to get the type safety when developing. However, be warned that the XML
editor may not be so forgiving and may complain loudly about the custom attribute
and element names being invalid. Fortunately, all of the built-in Seam components
already have corresponding XSDs, so you can use custom attribute and element names
to define properties for these components and the document will validate.
Assuming that you’ve imported the appropriate XML vocabulary, then the configuration
for the PasswordManager component can take advantage of the shorthand syntax,
as shown in listing 5.4, and still validate.

I threw a curve ball at you in that last excerpt. Can you tell what it is? I changed the
name of the element to digest-algorithm. Seam always converts
hyphenated element names, attribute names, and the value of the name attribute on
the element to their camel-case equivalents. This conversion is consistent
with what’s done to derive the simple name of the class from the element name, as
you learned in section 5.2.3.
Thus, you could have written the element for the digestAlgorithm
property using the hyphenated form:
SHA-1
or, if the namespace vocabulary supports it, written the attribute name as

NOTE
The XML namespace alias (e.g., auth) doesn’t play a role in the processing
of elements that map to properties as it does when top-level elements
mapped to component classes are interpreted (in the latter case, the alias
is used to resolve a Java package as described in section 5.2.3). In the previous
example, you could have used the unqualified element
and, disregarding the whining from the XML editor, the
property value would have been assigned properly.

To summarize, you can configure a component property using a nested
element, an attribute with the same name as the property on the component element,
or a nested element with the same name as the property. The important question to
ask is, “Does it validate?” In all cases, the nested element validates, though
without any guarantee of type safety. The component XML namespace vocabulary dictates
the custom attributes and nested elements you can use to configure a component’s
properties. Seam’s built-in component namespaces mostly rely on the syntax of
attribute names in hyphenated form. See section 5.5 for more examples of this syntax.
If you’re defining your own XML Schema, you may choose to adopt Seam’s style as
your standard.
That covers the variety of syntaxes you can use to define component properties in
XML. Let’s move on to property configuration using external property settings.
EXTERNAL PROPERTY SETTINGS
The component descriptor allows you to declare a component and configure its properties.
External property settings only allow you to configure the properties of existing
Seam components. That means the class must have a @Name annotation or it must be
declared as a component in a component descriptor (it must also meet the conditions
to be installed).
To define the property of a component using an external property, the property
key is constructed by joining the name of the component with the name of the bean
property on the component, separated by a dot (.) character. The value associated
with that key is used as the value to assign to the bean property. Note that in this case
the hyphenated form isn’t acknowledged, so the key must reference the property
name verbatim.
Let’s return to our PasswordManager component. For the purpose of this example,
we’ll assume that the PasswordManager has been defined as a Seam component
named passwordManager. To assign values to the digestAlgorithm and charset properties
using external property settings, you add the following two lines to a seam.properties
file:
passwordManager.digestAlgorithm=SHA-1
passwordManager.charset=UTF-8
If you assigned the fully qualified name org.open18.auth.passwordManager to the
PasswordManager component, the assignments appear as follow:
org.open18.auth.passwordManager.digestAlgorithm=SHA-1
org.open18.auth.passwordManager.charset=UTF-8
The location of the seam.properties file is described in section 4.5.1 of chapter 4. When
you define the property settings in the seam.properties file, you specify the value in the
standard java.util.Properties syntax, separating the name of the configuration
property from the value with either an equals sign (=) or a space. For more complex
string values than what is shown here, see the JavaDoc for java.util.Properties for
a more complete explanation of the standard rules.


WARNING

In seam-gen projects, you must use the seam.properties from the
resources folder, not the one from src/action or src/model. The latter
two locations are ignored by the build.

Instead of registering initial property values in a seam.properties file, you can set them
up as servlet context parameters in the web application’s WEB-INF/web.xml file. The
same java.util.Properties syntax applies. Note that these parameters aren’t
defined in the JSF servlet definition, but rather as context-wide initialization parameters
using top-level elements:

passwordManager.digestAlgorithm
SHA-1


passwordManager.charset
UTF-8

Using servlet context parameters is arguably less flexible since it requires a servlet
environment for the properties to take effect. This limitation can make it difficult to
create basic unit tests because it forces you to bootstrap the servlet environment.


WARNING
Keep in mind that the component name (the context variable name) is
used as the first half of the property key, not the class name of the component.
Many of the built-in Seam components have names that closely
resemble the class that they represent, so don’t confuse the two. In addition,
don’t confuse the dots in the component name with the final dot
used to isolate the name of the bean property.

Just to get a taste for a more advanced property key, let’s consider how you might set
the property on one of Seam’s built-in components. Figure 5.2 shows how to disable
the transaction management in Seam by setting the transactionManagementEnabled
property on the built-in org.jboss.seam.init component to false. Transaction
management is covered in chapter 9. For now, we’re merely playing around with the
setting for demonstration purposes.


Assigning values to the bean properties of a component using external property settings
is a simpler and more terse alternative than XML. In addition, if you define a value
for a component property using an external property setting, it takes precedence over
the configuration of the same property in the component descriptor. This override can
be useful for adjusting property values for different deployment environments.
Those are the basics of configuring components, but your exposure has been limited
to basic string properties. Let’s step it up a notch and explore the more complex
property types that Seam gives you the capability to configure.
5.3.3 Property value types
The values that are assigned to the properties of a component can be any of the
following:
■ Basic types (strings, numbers, Booleans, enums, characters, and class names)
■ EL (value and method expressions)
■ Collections (where each individual value can be any item in this list)
■ Replacement tokens (a name surrounded by @ symbols)
Let’s cover a couple more of the basic types before moving further down the list.
BASIC VALUE TYPES
The basic types are straightforward. A value begins its life as a string when it’s read
from the configuration file. Seam then determines the correct type for the value
based on the target property’s type and converts it before performing the assignment.
If a converter isn’t registered, an exception is thrown at startup. If the value can’t be
converted, an exception is thrown when the component is instantiated.


TIP
You can create your own property converter by implementing the Converter
interface on org.jboss.seam.util.Conversions and registering
it using the static putConverter() method on that class. However, right
now you have to subclass SeamListener in order to install the converter
before Seam initializes.


In the case of the PasswordManager component, no conversion occurs since both
properties, digestAlgorithm and charset, are strings. But Seam can handle most of
the common conversations that you’d expect to be supported. Let’s look at a couple
more examples.
Most golf courses have 18 holes (those that don’t have nine holes). Let’s use component
configuration to set a sensible default value for the numHoles property on a
transient Course instance:

18

The property numHoles is a primitive integer. Seam performs a basic conversation in this
case using Integer.valueOf(String). You can expect the same conversion to be done
for all types represented by primitive wrapper classes (and thus have the valueOf
(String) method). Two additional types that Seam supports are java.lang.Class and
java.lang.Enum. A class is derived from a string using Class.forName(String) and an
enum is selected by matching the string against the literal value of the constant.
Let’s assume that the type property on the Facility entity has been changed
from a string to the FacilityType enum defined as follows:
public enum FacilityType {
PUBLIC, PRIVATE, SEMI_PRIVATE, RESORT, MILITARY;
}
You could set the default type to PUBLIC on the Facility prototype as follows:

PUBLIC


WARNING
The tricky conversion is that of Booleans, which Seam converts from a
string using Boolean.valueOf(String). This method only considers a
value true if it matches the string “true,” ignoring case. All other values
are interpreted as false.


Component configuration gets interesting when the initial value is an EL expression
since it’s capable of injecting a dynamic, contextual value. The value may even be an
instance of another Seam component. If the Spring-Seam bridge is configured, which
is covered in chapter 15 (online), you can even inject a Spring bean into a Seam component
using the EL. Let’s dig into EL property values and review some examples.
EXPRESSION LANGUAGE VALUES
It’s been pretty much hammered into your brain by this point that Seam relies heavily
on the EL as a means of getting a handle on a component instance or other context
variable. The EL’s API agnostic syntax is the reason Seam is able to unify such a wide
range of technologies in a straightforward way. So it should be no surprise that Seam
turns to the EL again for assigning dynamic property values. Using the EL to define
property values is a powerful concept for two reasons:
■ You can leverage existing knowledge of the EL.
■ Any value accessible via the EL can be assigned (not just component instances).
That’s right. Rather than inventing yet another XML vocabulary for wiring Java objects
together, Seam leverages the EL to establish references between components. You’ll
learn about component wiring in the next section. The EL can also be used for calculating
a value and injecting the result. There’s really nothing new here, which is a
good thing. Let’s explore the mechanics of how the EL is handled as an initial property
value and when it is evaluated.

INFO

In Spring, you have to choose the appropriate XML element depending
on what you’re injecting. For instance, to inject a reference to another
bean you use or . To assign a null value, you use . In
Seam, all of those details ar

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/21802202/viewspace-1023514/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/21802202/viewspace-1023514/

你可能感兴趣的:(SEAM IN ACTION 第5章)