Servlets, and by extension, JavaServer Pages, are inherently stateless. That is, they will be used simultaneously by many threads and clients. Because of this, they must not store (in instance variables) any properties or values that are specified to any single client.
This creates a frustration for developers, because ordinary programming techniques must be avoided. Instead, client-specific state and data must be stored in the HttpSession or as HttpServletRequest attributes. This is an awkward and limiting way to handle both transient state (state that is only needed during the actual processing of the request) and persistent state (state that should be available during the processing of this and subsequent requests).
Tapestry bypasses most of these issues by not sharing objects between threads and clients. For the duration of a request, a page and all components within the page are reserved to the single request. There is no chance of conflicts because only the single thread processing the request will have access to the page. At the end of the request cycle, the page is reset back to a pristine state and returned to the shared pool, ready for reuse by the same client, or by a different client.
In fact, even in a high-volume Tapestry application, there will rarely be more than a few instances of any particular page in the page pool.
For this scheme to work it is important that at the end of the request cycle, the page must return to its pristine state. The prisitine state is equivalent to a freshly created instance of the page. In other words, any properties of the page that changed during the processing of the request must be returned to thier initial values.
Tapestry separates the persistent state of a page from any instance of the page. This is very important, because from one request cycle to another, a different instance of the page may be used ... even when clustering is not used. Tapestry has many copies of any page in a pool, and pulls an arbitrary instance out of the pool for each request.
In Tapestry, a page may have many properties and may have many components, each with many properties, but only a tiny number of all those properties needs to persist between request cycles. On a later request, the same or different page instance may be used. With a little assistance from the developer, the Tapestry framework can create the illusion that the same page instance is being used in a later request.
Each persistent page property is stored individually as an HttpSession attribute. Like the Servlet API, persistent properties work best with immutable objects such as String and Integer;. For mutable objects (including List and Map), Tapestry makes a copy of the property. In the worst case, Tapestry may have to serialize and deserialize the object to make a copy. Using several properties with simple, immutable types is therefore much less expensive than using a single, custom, complex, mutable object.
Persistent properties make use of a <property-specification> element in the page or component specification. Tapestry does something special when a component contains any such elements; it dynamically generates a subclass that provides the desired fields, methods and whatever extra initialization or cleanup is required.
You may also, optionally, make your class abstract, and define abstract accessor methods that will be filled in by Tapestry in the generated subclass. This allows you to read and update properties inside your class, inside listener methods.
![]() | Note |
---|---|
Properties defined this way may be either transient or persistent. It is useful to define even transient properties using the <property-specification> element because doing so ensures that the property will be properly reset at the end of the request (before the page is returned to the pool for later reuse). |
Example 3.3. Persistent Page Property: Java Class
public abstract class MyPage extends BasePage { abstract public int getItemsPerPage(); abstract public void setItemsPerPage(int itemsPerPage); } |
Example 3.4. Persistent Page Property: Page Specification
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE page-specification PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN" "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd"> <page-specification class="MyPage"> <property-specification name="itemsPerPage" persistent="yes" type="int" initial-value="10"/> </page-specification> |
Again, making the class abstract, and defining abstract accessors is optional. It is only useful when a method within the class will need to read or update the property. It is also valid to just implement one of the two accessors. The enhanced subclass will always include both a read and a write accessor.
This exact same technique can be used with components as well as pages.
A last note about initialization. After Tapestry invokes the finishLoad() method, it processes the initial value provided in the specification. If the initial-value attribute is ommitted or blank, no change takes place. Tapestry then takes a snapshot of the property value, which it retains and uses at the end of each request cycle to reset the property back to its "pristine" state.
This means that you may perform initialization for the property inside finishLoad() (instead of providing a initial-value). However, don't attempt to update the property from initialize() ... the order of operations when the page detaches is not defined and is subject to change.