-
Notifications
You must be signed in to change notification settings - Fork 17
Performance
The WComponents library has been written with performance in mind but it is still possible to write applications which perform poorly. This section details solutions to some common application problems which are specifically related to they way in which WComponents are used.
In general applications do not have CPU performance issues which are related to their use of WComponents. Data access and impacts on databases or other systems should be addressed at an application level by implementing caching.
Early versions of WComponents exclusively used Velocity templates for rendering. WComponents memory and CPU usage was examined as part of a continuous improvement process. It was found that Velocity rendering was about four times as slow as emitting the mark-up directly from Java code. It was considered worthwhile to fix even though the Velocity render times were only a few milliseconds for a large UI.
The result is that WComponents now emit all their mark-up directly from Java code; though use of templates is still supported. The WComponents API includes a set of general-purpose layout managers which can be used to arrange components in a manner suited to most intranet use cases and many general web application use cases.
Developers should try to minimise the amount of data which is stored in the session. The session must not be used as an application cache. In general only conversation state should be kept in the session:
- Application state such as the unique identifier of a record which is being viewed.
- UI navigational state such as the active tab in a tabset, which is stored automatically by WComponents.
- Partial data which has been entered by the user and not persisted, which is also stored automatically by WComponents.
The rest of this section describes some of the common coding patterns which may result in excessive memory usage.
The WComponents API provides two methods for directly storing data in the session: storage of data directly in the UIContext's framework attribute map; or in a WComponent's attribute map. These can quickly become dumping grounds for unstructured data and thereby become difficult to manage and bloat the session. Developers should use ComponentModel extensions or bean binding to store data rather than using the attribute maps. The UIContext itself should never be used to cache application data. A properly managed application cache should be used for this purpose.
Store a key to the data in a structured component model.
public class MyContainer extends WContainer {
public void handleRequest(final Request request) {
MyAppComponentModel model = (MyAppComponentModel) getComponentModel(uic);
int customerId = model.getCustomerId();
CustomerData data = MyCachingCustomerService.getCustomer(customerId);
// etc...
}
}
Store the entire data in a structured component model.
public class MyContainer extends WContainer {
public void handleRequest(final Request request) {
MyAppComponentModel model = (MyAppComponentModel) getComponentModel();
CustomerData data = model.getCustomerData();
// etc...
}
}
Store the entire data in the unstructured map.
public class MyContainer extends WContainer {
public void handleRequest(final Request request) {
CustomerData data = (CustomerData) this.getAttribute(uic, "customerData");
// etc...
}
}
Most component attributes can be set at either application or per-user level. Default values for a component should usually be set at application level.
A common set of options are set per user and will be stored in the user's session.
public class MyContainer extends WContainer {
private final WDropdown dropdown = new WDropdown();
protected void preparePaintComponent(final Request request) {
dropdown.setOptions(uic, MyUtilClass.getCountryList());
}
}
Options are set once for the application.
public class MyContainer extends WContainer {
private final WDropdown dropdown = new WDropdown();
public MyContainer() {
dropdown.setOptions(MyUtilClass.getCountryList());
}
}
Options can also be set in a dropdown's constructor.
public class MyContainer extends WContainer {
private final WDropdown dropdown = new WDropdown(MyUtilClass.getCountryList());
}
Dynamic UI structures use significant amounts of session memory as they store the component and the default model in the session as well as the session data. The WComponents library includes components which assist in developing dynamic interfaces without the need to create components on a per-user basis.
WComponents provides a "WCardManager" component to simplify switching between different screens in an application. Individual screens (components) are added to a WCardManager instance, and the WCardManager is used to switch between which component is visible.
The same effect can be accomplished without a WCardManager by toggling the visibility of different sections of the UI. This approach requires a bit more effort to implement, and is more suited to toggling visibility of small sections of UI within a screen.
The UI for all states of the application should be pre-built during the application's construction phase if possible. Small sections of UI can then be changed by toggling visibility of the pre-built components.
Extra text field is added dynamically.
public class MyContainer extends WContainer {
private final WCheckBox flag = new WCheckBox();
private WTextField extraInput;
protected void preparePaintComponent(final Request request) {
removeAll();
add(flag);
if (flag.isSelected()) {
extraInput = new WTextField();
add(extraInput);
}
}
}
Application-wide text field with dynamic visibility.
public class MyContainer extends WContainer {
private final WCheckBox flag = new WCheckBox();
private final WTextField extraInput = new WTextField();
protected void preparePaintComponent(final Request request) {
extraInput.setVisible(flag.isSelected(uic));
}
}
The WRepeater component should be used to implement sections of repeating UI rather than creating and adding individual components. If a repeater is not used each newly created component and its shared & user models will be stored in the user's session. When a WRepeater is used only the user-specific component models will be stored in the user session.
Addition of multiple fields.
public class MyContainer extends WContainer {
protected void preparePaintComponent(final Request request) {
removeAll();
for (MyQuestionDefinition question : getQuestions(uic)) {
add(new MyInputPanel(question));
}
}
}
Use a WRepeater.
public class MyContainer extends WContainer {
private final WRepeater repeater = new WRepeater(new MyInputPanel());
public MyContainer() {
add(repeater);
}
protected void preparePaintComponent(final Request request) {
repeater.setData(getQuestions());
}
}
WComponents store the bulk of their state information in component models. Each component will have a shared component model which defines the component's default state and a user model which is specific to a particular user's session. The WComponents library tries to automatically clean up component models which are no longer necessary since the user models are stored in the user's session.
At the end of each user's request cycle the WComponents tree is traversed to search for component models which are no longer necessary. A component's model is eligible for clean-up if the component is in its "default" state. If any of the component's attributes are different between the shared and user models the user model will remain in the user's session.
When developing reusable custom components, it is important to consider whether the attributes can be derived from the component's data or other state information. We could, for example, be developing a component which renders a dash ("-") rather than a blank if its text is null/empty (though this should not be done as it results in poor usability, potential accessibility errors and possible data integrity issues). Consider the following two implementations:
public final class DashableText extends WText { protected void preparePaintComponent(final Request request) { String text = getData(); setText(Util.empty(text) ? "-" : text); } }
2. ``` java
public final class DashableText extends WText {
public String getText() {
String text = super.getText();
return Util.empty(text) ? "-" : text;
}
}
The first implementation overrides the preparePaintComponent
method to set the text to a dash if there is no text present. This pushes the dash into the component model causing it to differ from the shared model and be ineligible for clean-up. This is sometimes referred to as a "push" approach: where state information is pushed into the model ahead of time.
The second implementation overrides the getText
method to determine what text is returned. This means that the component model may be eligible for clean-up. This is sometimes referred to as a "pull" approach: as the state is determined when requested. This approach can easily be applied to other component attributes such as visibility.
When a component is directly bound to a bean the entire bean will be stored in the users session. This could, depending on the bean's internal implementation, result in storing enormous amounts of unrelated data. The bean-provider API should be used where possible as it allows the bean to be fetched (from e.g. an application cache) whenever it is needed. Only the bean's identifying information will be stored in the user session.
The WRepeater component allows repeated data to be changed between requests including addition, removal and re-ordering of list items. Due to this flexibility the WRepeater uses the row beans to look-up component models for each row. This means that the entire bean will be stored in the session resulting in the same problem as bean-binding. If your beans have a property which is guaranteed to be unique (e.g. primary key) this can be used instead of storing the entire bean in the session. See the setRowIdProperty(String)
methods in the WRepeater class.
If it is not possible to use bean-binding and an identifying key then consideration should be given to reducing the amount of data contained in the bean. A summary version of the bean, for example, could only store the data required in the UI and not store any data which can be derived from the bean's other attributes.
The WComponents library includes a tool for diagnosing session memory usage problems. The UIContextDumpInterceptor
can be enabled by setting the flag bordertech.wcomponents.developer.UIContextDump.enabled
to true
. It is recommended that this flag only be set in the LDE as it can cause the VM to run out of memory when the session object graph is too large.
When the context dump interceptor is enabled the contents of the UIContext will be dumped to the log after each request along with an approximation of each item's contribution to the overall session size. The data is output in XML format which makes it easier to manipulate or view in viewers which allow folding of elements.
When the interceptor is activated the first user access will produce a dump of the UI structure. This can be used later on as a reference to link data back to sections of the UI. After the UI dump (and on subsequent accesses) the contents of the UIContext will be dumped.
A dump from on the initial access to the WComponents PetStore example is shown below. This shows the minimum session size for a WComponents application: there is no application data stored in the session - only the information stored directly by the UIContext. The first line of the dump shows that the initial session size is 404 bytes .
<!-- TODO replace with performance dump example -->
The session size will increase due to user interaction. A second dump is included below showing that the approximate session size has increased to 1890 bytes due to the inclusion of some user-specific component models. The component models are stored in a map and are keyed by the component which they belong to.
<!-- TODO replace with performance dump example -->
One of the models in the context dump is a "CartModel" which is an application-specific component model. One of the PetStore components has created component model extension to store structured data for users' shopping carts. The CartModel defines a single field "cart" which stores a list of beans.
class CartModel extends ComponentModel {
private final List<CartBean> cart = new ArrayList<CartBean>();
// ...
// methods omitted for clarity
}
An expanded view of the "CartModel" is shown below. It shows that the cart model contains a single cart bean for a product with the id of "1". This is a good approach to use as the Object representing the product will be much larger. Fields marked with an asterisk (such as "cart*") contain values which differ from the values in the component's shared model. Only component models with at least one different field will be stored in the user's session.
The other fields in the model are all inherited from the ComponentModel class. Some of these fields may be marked as different but are ignored when determining a component model's eligibility for clean-up.
<!-- TODO replace with performance dump example -->
WComponents rendering has been designed with efficiency in mind. Reducing bandwidth use was one of the primary reasons for switching to client-side transformation of declarative XML rather than HTML. This section describes application coding or implementation issues which can result in excessive XML payload sizes.
Well-designed use of AJAX can be used to reduce payload sizes. This depends on usage scenarios.
Client-side data lists can be used to significantly reduce the amount of data sent to the client when there are list controls (e.g. drop-downs) which contain a large number of options. For details, see DataList.
If bandwidth is still an issue after following the previous suggestions then the application UI design may need to be revisited. Breaking down the UI into logical sections will reduce the amount of data which needs to be sent to the client. Some components which may help are WTabSet, WCollapsible, WMenu and WCardManager.