Expert one-on-one J2EE Design and Development phần 8 ppsx

69 379 0
Expert one-on-one J2EE Design and Development phần 8 ppsx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Reference data that can be shared between users should always be held at application level. Data that is unlikely to be reused should probably just be retrieved again if it's ever needed. For example, a primary key might be held instead of a large volume of data retrieved from a database; this is a good sacrifice of performance in rare cases for a real benefit in scalability. Use Fine-grained, Rather than Large Monolithic, Session State Objects It's usually best to break session state into a number of smaller objects than one large, composite object. This means that only those objects that have changed will need replication in a cluster. As there's no easy way to establish whether the state of a Java object has changed, some servers, such as WebLogic, only replicate session state in a cluster when an object is rebound in an HttpSession, although the Servlet 2.3 specification doesn't specify what behavior is expected here. (The Servlet 2.4 specification may define standard semantics for session replication.) Such selective replication can deliver a big performance gain when objects stored in a session are updated at varying rates. Thus, to ensure correct replication behavior, always rebind a session attribute when the session data is changed. This won't pose a problem in any server. Consider Optimizing the Serialization of Session Data Sometimes by overriding the default serialization behavior, we can greatly improve the performance and reduce the size of serialized objects. This is a specialized optimization that is rarely necessary. We'll discuss serialization optimization in Chapter 15. Session State Held in the Browser Some session state must always be held in the user's browser. As HTTP is a stateless protocol, a J2EE server can only retrieve a user's session object if it finds a cookie or a special request parameter containing a unique key for that user's session. By holding session state in the browser, we lose the advantage of transparent server management of session state, but can achieve greater scalability and simplify clustering. Session State Management with Cookies In this approach, we use cookies to hold session state. The Servlet API makes it easy to set cookies, although we must be aware of the restrictions on the characters allowed in cookie values. The advantage of this approach is that it allows us to make an application's web tier stateless, which has the potential to improve scalability dramatically and simplify clustering. However, there are several disadvantages that often rule out this approach in practice: o Users must accept cookies. This isn't always feasible, as we don't have control over the client browser and often have little control over user behavior. o By rejecting server state management, we must build our own infrastructure for encoding state into cookies. Typically this involves using reflection to extract object properties, and using an ASCII-encoding algorithm to put that state into legal cookie values. (There are restrictions on the characters allowed in cookie values.) Theoretically such support could be added to a W«l application framework, but I've not seen this done in practice. 490 Brought to you by ownSky Web-Tier MVC Design o The amount of state that can be held in a cookie is limited to 2K. We could choose to use multiple cookies, but this increases the complexity of this approach. o All session data must be sent to and from the server with each request. However, this objection isn't so important in practice, as this approach isn't appropriate if there's a lot of state, and the page weight on typical web sites dwarves anything we might put in cookies. Sometimes this approach can be used with good results. In one real application, I successfully used cookies in place of session state where user information consisted of three string values (none longer than 100 characters) and three Boolean values. This approach delivered business value because the application needed to run on several geographically dispersed servers, ruling out replication of server-managed session state. I used an infrastructure class to extract property values from the session object using reflection and generate an acceptable ASCII-encoded cookie value. Session State Management with Hidden Form Fields Another time-honored approach to session state management, which also isn't J2EE-specific, involves an application trailing hidden form fields from page to page containing session state. I don't recommend this approach. It has several serious disadvantages: o It's relatively hard to implement. o It makes application logic vulnerable to errors in views such as JSP pages. This can make it harder to ensure a clean separation of the roles of Java developer and markup developer. o Like cookie state management, it requires session data to be stored purely as strings. o It makes it impossible to use ordinary hyperlinks for navigation within a site. Every page transaction needs to be a form submission. This usually means that we must use JavaScript links (to submit the form containing the hidden fields) instead of ordinary hyperlinks, complicating markup. o As with the use of cookies, bandwidth is consumed sending hidden form field values to and from the server. However, as with cookies, this objection is seldom a major problem in practice. o The user can easily see session state by viewing the page source. o Session state may be lost if the user leaves the site temporarily. This problem doesn't affect server state management or the use of cookies. In some applications, session state can be held in cookies. However, when we need session state it's usually safer - and much simpler - to rely on the web container to manage HttpSession objects, ensuring that we do all we can to ensure efficient replication of session state in a clustered environment. 491 Brought to you by ownSky Processing User Input One of the most tedious tasks in implementing web interfaces is the need to accept user input from form submissions and use it to populate domain objects. The central problem is that all values resulting from HTTP form submissions are strings. When domain object properties aren't strings, we need to convert values, checking parameter types as necessary. This means that we need to validate both the type of parameter submissions and their semantics. For example, an age parameter value of 23x isn't valid as it isn't numeric; but a numeric value of -1 is also invalid as ages cannot be negative. We might also need to check that the age is within a prescribed range. We also need to check for the presence of mandatory form fields, and may need to set nested properties of domain objects. A web application framework can provide valuable support here. Usually we want to take one of the following actions on invalid data: o Reject the input altogether, treating this as an invalid submission. This approach is appropriate, for example, when a user could only arrive at this page from a link within the application. As the application, not the user, controls the parameters, invalid parameter values indicate an internal error. o Send the user back the form allowing them to correct the errors and resubmit. In this case, the user will expect to see the actual data they entered, regardless of whether that input was valid or invalid, or even of the correct type. The second action is the most commonly required and requires the most support from a web application framework. The discussion in this section concentrates on the submission of a single input form, rather than "wizard" style submissions spread over several pages. Many of the principles applicable to single form submission apply to wizard style submission, although it is naturally harder to implement multi-page forms than single-page forms (it may be modeled as several individual form submissions onto different JavaBeans, or multiple submissions updating different properties of the same bean stored in the session). Data Binding and Displaying Input Errors for Resubmission Often, rather than perform low-level processing of individual request parameters, we want to transparently update properties on a JavaBean. The operation of updating object properties on HTTP form submission is often called data binding. In this approach, a JavaBean - typically a command - will expose properties with the same names as the expected request parameters. Web application framework code will use a bean manipulation package to populate these properties based on the request parameters. This is the same approach as tha used by the <jsp:useBean> action, although, as we've noted, this doesn't provide a sophisticated enough implementation. Ideally, such a command bean will not depend on the Servlet API, so once its properties are set it can be passed as a parameter to business interface methods. 492 Brought to you by ownSky Web-Tier MVC Design Many frameworks, such as Struts and WebWork, use data binding as their main approach to request parameter processing. However, data binding isn't always the best approach. When there are few parameters, it may be inappropriate to create an object. We may want to invoke business methods that don't take arguments, or take one or two primitive arguments. In such cases, application controllers can themselves process request parameters using the getParameter(String name) method of the HttpServletRequest interface. In such cases, creating a command object would waste a little time and memory, but - more seriously - make a very simple operation seem more complex. With a pure data-binding approach, if we have a separate command per bean, as in Struts, we can easily end up with dozens of action form classes that add very little. Also, sometimes dynamic data to a request isn't contained in request parameters. For example, sometimes we might want to include dynamic information in a virtual URL. For example, a news article URL might include an ID in the servlet path, without requiring a query string, as in the following example: /articles/article3047.html This approach may facilitate content caching: for example, by a servlet filter or in the browser. Note that processing individual request parameters places the onus on application controller code to check that parameters are of the required type: for example, before using a method such as Integer.parselnt(String s) to perform type conversion. Where data binding does shine is where there are many request parameters, or where resubmission is required on invalid input. Once user data is held in a JavaBean, it becomes easy to use that object to populate a resubmission form. Approaches to Data Binding in MVC Frameworks There seem to be two basic approaches to data binding in Java web application frameworks, both based on JavaBeans: o Keep the data (valid or invalid) in a single JavaBean, allowing a resubmission form's field values to be populated easily if necessary This is the Struts ActionForm approach. It has the disadvantage that the data in the ActionForm bean isn't typed. Since all properties of the ActionForm are strings, the ActionForm can't usually be a domain object, as few domain objects hold only string data. This means that when we've checked that the data the ActionForm contains is valid (and can be converted to the target types) we'll need to perform another step to populate a domain object from it. In this model, validation will occur on the form bean, not the domain object; we can't attempt to populate a domain object from the form bean until we're sure that all the form bean's string property values can be converted to the necessary type. Note that we will need to store error information, such as error codes, somewhere, as the form bean can store only the rejected values. Struts holds error information for each rejected field in a separate ActionErrors object in the request. 493 Brought to you by ownSky o Keep errors in a separate object This approach attempts to populate the domain object, without using an intermediate holding object such as an "action form". The domain object we're trying to bind to will have its fields updated with the inputs of the correct type, while any inputs of an incorrect type (which couldn't be set on the domain object) will be accessible from a separate errors object added to the request. Semantic (rather than syntactic) validation can be performed by domain objects after population is complete. Web Work uses a variant of this approach. If a Web Work action implements the webwork.action. IllegalArgumentAware interface, as does the webwork.action.ActionSupport convenience superclass, it will receive notification of any type mismatches when its properties are being set, making it possible to store the illegal value for display if necessary. In this model, type mismatches are transparently handled by the framework, and application code doesn't need to consider them. Application validation can work with domain objects, not dumb storage objects. Since WebWork combines the roles of command and controller (or "action" in WebWork terms), a WebWork controller is not a true domain object because it depends on the WebWork API. However, this is a consequence of Web Work's overall design, not its data binding approach. The second approach is harder to implement in a framework, but can simplify application code, as it places the onus on the framework to perform type conversion. Such type conversion can be quite complex, as it can use the standard JavaBeans PropertyEditor machinery, enabling complex objects to be created from request parameters. The second approach is used in the framework for the sample application, although this framework also supports an action form-like approach like that of Struts, in which string properties are used to minimize the likelihood of errors. JSP Custom Tags How do we know what data to display on a form? Usually we'll want to use the same template (JSP or other) for both fresh input forms and resubmissions, as the prospect of different forms containing hundreds of lines of markup getting out of synch is unappetizing. Thus the form must be able to be populated with no object data behind it; with data from an existing object (for example, a user profile retrieved from a database); or with data that may include errors following a rejected form submission. In all cases, additional model data may be required that is shared between users: for example, reference data to populate dynamic lists such as dropdowns. In such cases, where data may come from different sources - or simply be blank if there is no bean from which to obtain data -JSP custom tags can be used to move the problem of data acquisition from template into helper code behind the scenes in tag handlers. Many frameworks use a similar approach here, regardless of how they store form data. If we want to perform data binding, the form usually needs to obtain data using special tags. The tags in our framework with the sample application are conceptually similar to those with Struts or other frameworks. Whether or not the form has been submitted, we use custom tags to obtain values. 494 Brought to you by ownSky Web-Tier MVC Design For each field, we can use custom tags to obtain any error message resulting from a failed data-binding attempt along with the rejected property value. The tags cooperate with the application context's internationalization support to display the error message for the correct locale automatically. For example, the following fragment of a JSP page uses cooperating custom tags to display an error message if the e-mail property of the user bean on the page failed validation, along with a pre-populated input field containing the rejected value (if the submitted value was invalid) or current value (if there was no error in the e-mail value submitted). Note that only the outer <i21:bind> tag is specific to this framework. It exposes data to tags nested within it that enable it to work with JSP Standard Tag Library tags such as the conditional <c:if> and output <c:out> tags. This means that the most complex operations, such as conditionals, are performed with standard tags. (We discuss the JSTL in detail in the next chapter.) The <i21: bind> tag uses a value attribute to identify the bean and property that we are interested in. The bean prefix is necessary because these tags and the supporting infrastructure can support multiple bind operations on the same form: a unique capability, as far as I know. The <i21:bind> tag defines a bind scripting variable, which can be used anywhere within its scope, and which exposes information about the success or failure of the bind operation for this property and the display value. The tag arrives at the string value to display by evaluating the following in order: any rejected input value for this field; the value of this attribute of a bean with the required name on the page (the same behavior as the <jsp:getProperty> standard action); and the empty string if there was no bean and no errors object (the case on a new form with no backing data). The bind scripting variable created by the <i21:bind> tag also exposes methods indicating which of these sources the displayed value came from. Another convenient tag evaluates its contents only if there were data binding errors on the form: <i21:hasBindErrors> <font color="red" size="4"> There were <%=count%> errors on this form </font> </i21:hasBindErrors> We'll look at custom tags in more detail in the next chapter, but this demonstrates a very good use for them. They completely conceal the complexity of data binding from JSP content. Note that it is possible to handle form submission using Struts or the framework designed in this chapter without using JSP custom tags; custom tags just offer a simple, convenient approach. 495 Brought to you by ownSky Data Validation If there's a type mismatch, such as a non-numeric value for a numeric property, application code shouldn't need to perform any validation. However, we may need to apply sophisticated validation rules before deciding whether to make the user resubmit input. To ensure that our display approach works, errors raised by application code validation (such as age under 18 unacceptable) must use the same reporting system as errors raised by the framework (such as type mismatch). Where Should Data Validation be Performed? The problem of data validation refuses to fit neatly into any architectural tier of an application. We have a confusing array of choices: Q Validation in JavaScript running in the browser. In this approach, validity checks precede form submission, with JavaScript alerts prompting the user to modify invalid values. Q Validation in the web tier. In this approach a web-tier controller or helper class will validate the data after form submission, and return the user to the form if the data is invalid, without invoking any business objects. Q Validation in business objects, which may be EJBs. Making a choice can be difficult. The root of the problem is the question "Is validation business logic?' The answer varies in different situations. Validation problems generally fall into the categories of syntactic and semantic validation. Syntactic validation encompasses simple operations such as checks that data is present, of an acceptable length, or in the valid format (such as a number). This is not usually business logic. Semantic validation is trickier, and involves some business logic, and even data access. Consider the example of a simple registration form. A registration request might contain a user's preferred username, password, e-mail address, country, and post or zip code. Syntactic validation could be used to ensure that all required fields are present (assuming that they're all required, which might be governed by a business rule, in which case semantics is involved). However, the processing of each field is more complicated. We can't validate the post or zip code field without understanding the country selection, as UK postcodes and US zip codes, for example, have different formats. This is semantic validation, and approaching business logic. Worse, the rules for validating UK postcodes are too complex and require too much data to validate on the client-side. Even if we settle for accepting input that looks like a UK postcode but is semantic nonsense (such as Z10 8XX), JavaScript will still prove impracticable if we intend to support multiple countries. We can validate e-mail address formats in JavaScript, and perform password length and character checks. However, some fields will pose a more serious problem. Let's assume that usernames must be unique. It is impossible to validate a requested username without access to a business object that can connect to the database. All the above approaches have their advantages and disadvantages. UsingJavaScript reduces the load on the server and improves perceived response time. If multiple corrections must be made before a form is submitted, the load reduction may be significant and the user may perceive the system to be highly responsive. 496 Brought to you by ownSky Web-Tier MVC Design On the other hand, complex JavaScript can rapidly become a maintainability nightmare, cross-browser problems are likely, and page weight may be significantly increased by client-side scripts. In my experience, maintaining complex JavaScript is likely to prove much more expensive than maintaining comparable functionality in Java. JavaScript being a completely different language from Java, this approach also has the serious disadvantage that, while Java developers write the business logic, JavaScript developers must write the validation rules. JavaScript validation is also useless for non-web clients, such as remote clients of EJBs with remote interfaces or web services clients. Do not rely on client-side JavaScript validation alone. The user, not the server, controls the browser. It's possible for the user to disable client-side scripting, meaning that it's always necessary to perform the same checks on the server anyway. Validation in the web tier, on the other hand, has the severe disadvantage of tying validation logic -which may be business logic - to the Servlet API and perhaps also a web application framework. Unfortunately, Struts tends to push validation in the direction of the web tier, as validation must occur on Struts ActionForm objects, which depend on the Servlet API, and hence cannot be passed into an EJB container and should not be passed to any business object. For example, validation is often accomplished by overriding the org.apache.struts.action. ActionForm validate method like this: I consider this - and the fact that ActionForm objects must extend a Struts superclass dependent on the Servlet API - to be a major design flaw. Struts 1.1 also provides declarative validation, controlled through XML configuration files. This is powerful and simple to use (it's based on regular expressions), but whether validation rules are in Java code or XML they're still often in the wrong place in the web tier. Validation should depend on business objects, rather than the web tier. This maximizes the potential to reuse validation code. 497 Brought to you by ownSky However, there is one situation in which validation code won't necessarily be collocated with business objects: in architectures in which business objects are EJBs. Every call into the EJB tier is potentially a remote call: we don't want to waste network roundtrips exchanging invalid data. Thus the best place to perform validation is in the same JVM as the web container. However, validation need not be part of the web interface logical tier. We should set the following goals for validation: o Validation code shouldn't be contained in web-tier controllers or any objects unique to the web tier. This allows the reuse of validation objects for other client types. o To permit internationalization, it's important to separate error messages from Java code. Resource bundles provide a good, standard, way of doing this. o Where appropriate we should allow for parameterization of validation without recompiling Java code. For example, if the minimum and maximum password lengths on a system are 6 and 64 characters respectively, this should not be hard-coded into Java classes, even in the form of constants. Such business rules can change, and it should be possible to effect such a change without recompiling Java code. We can meet these goals if validators are JavaBeans that don't depend on web APIs, but may access whatever business objects they need. This means that validators must act on domain objects, rather than Servlet API-specific concepts such as HttpServletRequests or framework-specific objects such as Struts ActionForms. Data Validation in the Framework Described in this Chapter Let's look at how this approach works in the framework described in this chapter, and so in the sample application. All validators depend only on two non web-specific framework interfaces, com.interface21.validation.Validator and com.interface21.validation.Errors. The Validator interface requires implementing classes to confirm which classes they can validate, and implement a validate method that takes a domain object to be validated and reports any errors to an Errors object. Validator implementations must cast the object to be validated to the correct type, as it is impossible to invoke validators with typed parameters in a consistent way. The complete interface is: public interface Validator { boolean supports (Class clazz); void validate (Object obj, Errors errors); } Errors are added to the Errors interface by invoking the following method: void rejectValue(String field, String code, String message); Errors objects expose error information that is used to back display by the custom tags shown above. The same errors object passed to an application validator is also used by the framework to note any type conversion failures, ensuring that all errors can be displayed in the same way. Let's consider a partial listing of the Validator implementation used in the sample application to validate user profile information held in RegisteredUser objects. A RegisteredUser object exposes e-mail and other properties including postcode on an associated object of type Address. 498 Brought to you by ownSky Web-Tier MVC Design The DefaultUserValidator class is an application-specific JavaBean, exposing minEmail and maxEmail properties determining the minimum and maximum length acceptable for e-mail addresses: package com.wrox.expertj2ee.ticket.customer; import com.interface21.validation.Errors; import com.interface21. validation.FieldError; import com.interface21.validation.Validator ; public class DefaultUserValidator implements Validator { public static final int DEFAULT_MIN_EMAIL = 6; public static final int DEFAULT_MAX_EMAIL = 64; private int minEmail = DEFAULT_MIN_EMAIL; private int maxEmail = DEFAULT_MAX_EMAIL; public void setMinEmail(int minEmail) { this .minEmail = minEmail; } public void setMaxEmail(int maxEmail) { this. maxEmail = maxEmail; } The implementation of the supports( ) method from the Validator interface indicates that this validator can handle only RegisteredUser objects: public boolean supports(Class clazz) { return clazz.equals(RegisteredUser.class) ; } The implementation of the validate() method from the Validator interface performs a number of checks on the object parameter, which can safely be cast to RegisteredUser: 499 Brought to you by ownSky [...]... object containing information about the performance and its parent show This is of type o com.wrox.expertj2ee.ticket.referencedata.Performance o priceband o An object displaying information such as the name (in the above screenshots, "Premium Reserve") and price of the type of seat requested This is of type o com.wrox.expertj2ee.ticket.referencedata.PriceBand o reservation o An object containing information... JSP and some leading alternative view technologies for J2EE applications and how to decide which view technology is best to solve a given problem We'll look at the strengths and weaknesses of each of the following view technologies, and look at them in use: o JSP We'll look at traps that must be avoided in using JSP, and how best to use JSP pages to ensure that web applications using them are clean and. .. expanded one Show and one Performance: The Performance interface, part of the model for the "Show Reservation" view, extends Ref erenceltem to expose a link to the parent Show object, as well as the performance's date and time and a list of PriceBand objects for that performance: public interface Performance extends Ref erenceltem { Show getShow(); Date getWhen(); List getPriceBands(); } PriceBand... you by ownSky Web-Tier MVC Design we looked at the problem of data binding in web applications (the population of Java object properties from request parameter values), and how web application frameworks address the problem of form submission and validation Finally, we looked at the design and implementation of the web tier in the sample application, showing how it meets the design goals we set ourselves... highestperforming view option o JSP is one of the J2EE standards There's a wealth of literature on JSP authoring, and substantial tool support Many developers are familiar with JSP o JSP's support for custom tags (discussed below) means that third-party tag libraries are available for a variety of purposes and that the capabilities of JSP can be extended in a standard way Standardization is usually a good thing... Calendar and BoxOffice interfaces It uses a single controller, com.wrox.expertj2ee.ticket.web.TicketController, which extends the MultiActionController framework superclass, discussed above, to handle all application URLs Views are JSP pages, which display model beans returned by the controller, which aren't web specific, such as the Reservation object These JSP pages don't perform any request processing and. .. the com.wrox.expertj2ee.ticket.web.TicketController class This method handles submission of a form that allows the user to request the reservation of a number of seats of a specified type (such as "Premium Reserve") for a particular performance Information Presented and Required Formatting Two views can result from processing this request: o A view displaying the newly created reservation, and inviting... of the seats, and the cost of purchasing these seats, including the booking fee Currency display and date and time display should be appropriate to the user's locale There is one variant of this view, which is displayed if there were enough seats to satisfy the user's request, but they are not adjacent In this case, the user should be given the opportunity to abandon this reservation and try to book... the populated command to a business interface and choose a view depending on the result Note that this method doesn't take request or response objects These are unnecessary unless we need to manipulate the user's session (the request and response objects are available through overriding other onSubmit ( ) methods): the request parameters have already been extracted and bound to command properties, while... reservation o An object containing information about the user's reservation, such as the seats reserved the quoted price and whether or not the seats are adjacent This is of type o com.wrox.expertj2ee.ticket.boxoffice.Reservation 521 Brought to you by ownSky The performance and priceband objects are reference data, shared among all users of the application The reservation object is unique to the current . application-specific JavaBean, exposing minEmail and maxEmail properties determining the minimum and maximum length acceptable for e-mail addresses: package com.wrox.expertj2ee.ticket.customer; import com.interface21.validation.Errors;. needs to do to the response: protected ModelAndView onSubmit(Object command) { RegisteredUser user = (RegisteredUser) command; Return new ModelAndView(getSUccessView(), “email”, user.getEmail());. Calendar and BoxOffice interfaces. It uses a single controller, com.wrox.expertj2ee.ticket.web.TicketController, which extends the MultiActionController framework superclass, discussed above, to handle

Ngày đăng: 13/08/2014, 12:21

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan