Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 62 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
62
Dung lượng
1 MB
Nội dung
32 CHAPTER 2 Building web applications ServletContext sc = getServletContext(); dbUrl = sc.getInitParameter("dbUrl"); driverClass = sc.getInitParameter("driverClass"); user = sc.getInitParameter("user"); password = sc.getInitParameter("password"); } private void addPoolToApplication(DbPool dbPool) { getServletContext().setAttribute(CONN_POOL_ID, dbPool); } private DbPool createConnectionPool() { DbPool p = null; try { p = new DbPool(driverClass, dbUrl, user, password); } catch (SQLException sqlx) { getServletContext().log("Connection Pool Error", sqlx); } return p; } The Catalog class starts by declaring constants for SQL access and for member variables. The first of the servlet-specific declarations is for the init() method. Because it is the first servlet called in the application, it is responsible for creating the database connection pool used by the rest of the application. It is a common practice to use connection pools in web applications, and most application servers and frameworks include connection pool classes. Our sample uses a homegrown connection pool class called DbPool , which offers rudimentary database connec- tion pooling. The source for it is trivial and is available as part of the source code archive, but won’t be shown here for space considerations. The init() method handles two jobs: getting the init parameters from the servlet context and adding the connection pool to the application context. The database connection definitions appear in the web.xml file as global init parame- ters. This is a common practice because it allows the developer to change such characteristics as the driver class and login information without having to recom- pile the application. The getPropertiesFromServletContext() method retrieves the pertinent values from the configuration file and populates the servlet’s mem- ber variables. The second chore handled by the init() method is to create the connection pool and place it in a location where all the other servlets can access it. The cre- ateConnectionPool() method builds the connection pool from the supplied parameters and returns it. If an error occurs, the cause of the exception is logged Building web applications with servlets 33 via the servlet context’s log() method. The pool is then placed in the servlet context for the application. This is the global context, meaning that the pool will be accessible to the other servlets. The next method of interest in the Catalog servlet is the doPost() method. It appears in listing 2.3. public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = generatePagePrelude(response, "Catalog"); String userName = validateUser(request, out); DbPool pool = getConnectionPool(); Connection con = null; try { con = pool.getConnection(); handleReturnOrNewUser(out, userName, con); out.println("</h3><p>"); addUserToSession(request, userName); displayCatalog(out, con); generatePagePostlude(out); } catch (SQLException sqle) { getServletContext().log("SQL error", sqlx); } finally { pool.release(con); } } The general rule of thumb in high-quality applications (and indeed for the rest of the code in the book) is to create very granular, cohesive methods. Cohesive methods perform a single task and no more. Making your methods cohesive leads to granularity, meaning the methods are very small (like grains of sand) and numerous. If successful, the public methods in a class should read like an outline of what the method does, with the details submerged in private methods. Applica- tions using this coding pattern also generate more readable stack traces when you’re debugging. The doPost() method in the Catalog servlet is an example of this technique. The first job of this method concerns the generation of the page prelude. This code must appear at the top of the HTML document generated by this servlet. To handle this job, the doPost() method calls the generatePagePrelude() method (see listing 2.4). Listing 2.3 The doPost() method of the Catalog servlet 34 CHAPTER 2 Building web applications private PrintWriter generatePagePrelude(HttpServletResponse response) throws IOException { response.setContentType(CONTENT_TYPE); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head><title>Logon</title></head>"); out.println("<body>"); return out; } This method creates a print writer object (which it returns) and uses it to create the standard HTML elements for the top of the page. This method does not appear in the Catalog servlet. It appears instead in a base class servlet named EMoth- erServletBase . As with any application, common tasks exist that every servlet must perform. For example, every servlet in this application must get a reference to the connection pool and generate headers and footers for the HTML document. One of the side benefits of creating granular, cohesive methods is the ability to float them up in the hierarchy to the base class. In other words, it helps you identify the methods that may be generalized into a parent class, making the code easier to reuse. The more single-purposed the methods are, the more likely that they can be reused. The common methods for this application have been promoted to the base class servlet, which appears in listing 2.5. package com.nealford.art.history.servletemotherearth; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; import com.nealford.art.history.servletemotherearth.lib.DbPool; import java.sql.SQLException; public class EMotherServletBase extends HttpServlet { static final protected String CONN_POOL_ID = "DbPool"; static final protected String CONTENT_TYPE = "text/html"; protected DbPool getConnectionPool() { DbPool pool = (DbPool) getServletContext(). getAttribute(CONN_POOL_ID); if (pool == null) getServletContext().log("Pool cannot be loaded"); return pool; } Listing 2.4 The generatePagePrelude() method Listing 2.5 EMotherServletBase consolidates common servlet methods. Building web applications with servlets 35 protected PrintWriter generatePagePrelude( HttpServletResponse response, String title) throws IOException { response.setContentType(CONTENT_TYPE); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head><title>" + title + "</title></head>"); out.println("<body>"); return out; } protected void generatePagePostlude(PrintWriter out) { out.println("</body></html>"); } protected HttpSession getSession(HttpServletRequest request, HttpServletResponse response) throws IOException { HttpSession session = request.getSession(false); if (session == null) response.sendRedirect("Welcome.html"); return session; } } Creating a base class servlet to consolidate common methods is a common prac- tice, made more effective by cohesive methods. The next task that Catalog ’s doPost() method handles is to validate the user. Validation is handled in the method validateUser() , which returns the user- name. Listing 2.6 shows this method. private String validateUser(HttpServletRequest request, PrintWriter out) { String userName = request.getParameter("username"); if (userName.equals("")) out.println("<h1>Error! You must enter a user name!"); out.println("<h3>Hello, " + userName + "."); return userName; } The doPost() method next sets up the servlet to handle database access. To do so, it calls the getConnectionPool() method from the base class (see listing 2.5). Note the disassociation of the init() method from the remainder of the serv- let. This servlet is the one that placed the pool in the application context in the Listing 2.6 The validateUser() method ensures that the user entered a value. 36 CHAPTER 2 Building web applications beginning, so it could avoid going back to the servlet context to get a reference to the pool. Instead, it could hold onto the reference generated at the top. However, we chose to go ahead and get the connection in this servlet exactly as the others would: by using the common method. This approach adds consistency to the application and ensures that nothing will break if you need to add code to the base class later to enhance its functionality. The doPost() method next establishes a database connection within a try … finally block to ensure that the connection always closes. This resource-protection requirement drives the structure of the interior of this method, because the con- nection must be established and freed within this context. Next, doPost() gener- ates a different message for existing or new users, which is handled by the handle- ReturnOrNewUser() method (see listing 2.7). private void handleReturnOrNewUser(PrintWriter out, String userName, Connection con) throws SQLException { if (isNewUser(con, userName)) out.println("Welcome back to the store!"); else { addUser(con, userName); out.println("Welcome to the store! We'll add " + "you to the user database"); } out.println("</h3><p>"); } This method is itself composed of other helper methods, with the goal of creating the most granular code possible. The isNewUser() method (listing 2.8) checks to see whether the user is already present in the database. private boolean isNewUser(Connection c, String userName) throws SQLException { PreparedStatement ps = c.prepareStatement(SQL_SEL_USERS); ps.setString(1, userName); ResultSet rs = ps.executeQuery(); return rs.next(); } Listing 2.7 This method decides what message to present and whether to add a new user to the database. Listing 2.8 The isNewUser() method Building web applications with servlets 37 If the ResultSet contains a record, then that means the user is already present in the database and the next() method returns true. Otherwise, the user does not currently exist, so the application automatically adds that user. This is not typical behavior for most e-commerce sites, which go through a vetting process to add new users. Our vendor doesn’t care, and will gladly add new users (even if they typed in the wrong username by accident). Of course, we could write more code to expand this behavior. If a user must be added, the addUser() method handles the task. This method is shown in listing 2.9. private void addUser(Connection c, String userName) throws SQLException { PreparedStatement psi = c.prepareStatement(SQL_INS_USERS); psi.setString(1, userName); psi.executeUpdate(); } The next task performed by the doPost() method is to create a session and add the user to it. This task is handled by a very short method: private void addUserToSession(HttpServletRequest request, String userName) { HttpSession session = request.getSession(true); session.setAttribute("user", userName); } It is worth creating separate methods even for two lines of code (in fact, it is some- times worthwhile for a single line of code). The entries in the public methods should be consistent and perform the same level of work. It is undesirable to inter- sperse utility code like this among other high-level method calls. The high-level method calls should be descriptive enough to eliminate the need for additional comments. Maintaining comment synchronization is error-prone, so let the code speak for itself. Use method, variable, class, and interface names that don't need comments to convey their purpose. It is also likely that more code will accrue over time, making the public method longer. Any candidate for a nice cohesive method should be extracted. The code is consequently much more readable. The display of the catalog occurs next. It is handled by the aptly named dis- playCatalog() method, which appears in listing 2.10. Listing 2.9 This method adds new users to the database. 38 CHAPTER 2 Building web applications private void displayCatalog(PrintWriter out, Connection con) { HtmlSQLResult output = new HtmlSQLResult(SQL_SEL_PRODS, con); output.setShoppingForm(true); out.println("<h1>Products</h1><p>"); out.println(output.toString()); } At first glance, it would seem that this method would be much more complex. It offloads much of the complexity to a helper class named HtmlSQLResult . This utility class takes a database connection and a SQL statement and renders the results into an HTML table. It also has an option for creating another column with a text field and a button that allows the user to purchase items. This class appears in listing 2.11. package com.nealford.art.history.servletemotherearth.lib; import java.sql.*; import java.text.NumberFormat; public class HtmlSQLResult { private String sql; private Connection con; private boolean shoppingForm; public HtmlSQLResult(String sql, Connection con) { this.sql = sql; this.con = con; } /** * The <code>toString()</code> method returns a * <code>java.sql.ResultSet</code> formatted as an HTML table. * * NB: This should be called at most once for a given set of * output! * @return <code>String</code> formatted as an HTML table * containing all the elements of the result set */ public String toString() { StringBuffer out = new StringBuffer(); try { Statement stmt = con.createStatement(); stmt.execute(sql); ResultSet rs = stmt.getResultSet(); ResultSetMetaData rsmd = rs.getMetaData(); Listing 2.10 displayCatalog() shows the entire catalog of products. Listing 2.11 The HtmlSQLResult class Generates the table from the ResultSet Building web applications with servlets 39 int numCols = rsmd.getColumnCount(); setupTable(out); generateHeaders(out, rsmd, numCols); while (rs.next()) { generateStandardRow(rs, rsmd, numCols, out); generateShoppingForm(out, rs.getInt("id")); endRow(out); } endTable(out); } catch (SQLException e) { out.append("</TABLE><H1>ERROR:</H1> " +e.getMessage()); } return out.toString(); } private void endTable(StringBuffer out) { out.append("</TABLE>\n"); } private void endRow(StringBuffer out) { out.append("</TR>\n"); } private void generateShoppingForm(StringBuffer b, int currentId) { if (shoppingForm) { b.append("<TD>"); b.append("<form action='ShowCart' method='post'>"); b.append("Qty: <input type='text' size='3' " + "name='quantity'>"); b.append("<input type='hidden' name='id' " + "value='"+ currentId + "'>"); b.append("<input type='submit' name='submit' " + "value='Add to cart'>"); b.append("</form>"); } } private void generateStandardRow(ResultSet rs, ResultSetMetaData rsmd, int numCols, StringBuffer out) throws SQLException { NumberFormat formatter = NumberFormat.getCurrencyInstance(); out.append("<TR>"); for (int i = 1; i <= numCols; i++) { Object obj = rs.getObject(i); if ((obj != null) && (rsmd.getColumnType(i) == java.sql.Types.DOUBLE)) out.append("<TD align='right'> " + Iterates over the ResultSet and generates rows Builds a form element for each row 40 CHAPTER 2 Building web applications formatter.format(rs.getDouble(i))); else if (obj == null) out.append("<TD> "); else out.append("<TD>" + obj.toString()); } } private void generateHeaders(StringBuffer out, ResultSetMetaData rsmd, int numcols) throws SQLException { for (int i = 1; i <= numcols; i++) { out.append("<TH>"); out.append(rsmd.getColumnLabel(i)); } if (shoppingForm) out.append("<TH>" + "Buy"); out.append("</TR>\n"); } private void setupTable(StringBuffer out) { out.append("<TABLE border=1>\n"); out.append("<TR>"); } public boolean isShoppingForm() { return shoppingForm; } public void setShoppingForm(boolean value) { shoppingForm = value; } } We included listing 2.11 primarily to make a point about developing with servlets. Anytime you need to generate a large HTML data structure like a table, you are always better off building it generically because the complexity of the mixed Java and HTML generation is overwhelming. This code is best developed once and reused rather than generated anew for ad hoc situations. In the next section, you’ll see how JSP offers an alternative for this problem. With the help of the utility class in listing 2.11, the remainder of Catalog ’s doPost() method, the generatePagePostlude() method, comes free of charge from the base class (listing 2.5). This method generates the required footer infor- mation for the page. Building web applications with servlets 41 The third page: ShowCart The third page (and corresponding servlet) in the application shows the contents of the user’s shopping cart thus far, with an option at the bottom for completing the purchase. This page is shown in figure 2.4. The source for the ShowCart servlet appears in its entirety in listing 2.12. package com.nealford.art.history.servletemotherearth; import com.nealford.art.history.servletemotherearth.lib.*; import java.io.*; import java.sql.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class ShowCart extends EMotherServletBase { static final private String SQL_GET_PRODUCT = "select * from products where id = ?"; public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = generatePagePrelude(response, "Cart"); HttpSession session = getSession(request, response); String userName = (String) session.getAttribute("user"); ShoppingCart sc = getShoppingCart(session); out.println("<h3>" + userName + ", here is your shopping cart:</h3>"); int itemId = Integer.parseInt(request.getParameter("id")); Listing 2.12 This servlet shows the contents of the shopping cart. Figure 2.4 The Shopping Cart page of the application shows the current contents of the shopping cart and allows the user to specify credit card information to make the purchase. Isolates HTML generation [...]... starting point and truly leverage the potential of web development in Java In chapter 3, we solve some of the shortcomings of servlets and JSP by using JSP custom tags Creating custom JSP tags This chapter covers I I I Building custom JSP tags Using the Java Standard Tag Library Using other third-party JSP tags 61 62 CHAPTER 3 Creating custom JSP tags In chapter 2, we used the building blocks of web. .. CHAPTER 2 Building web applications Listing 2. 19 import= "java. sql.*"%> import= "java. text.NumberFormat"%> import= "java. sql.*"%> import= "java. text.NumberFormat"%> . listing 2. 12. package com.nealford .art. history.servletemotherearth; import com.nealford .art. history.servletemotherearth.lib.*; import java. io.*; import java. sql.*; import java. util.*; import javax.servlet.*; import. in listing 2. 5. package com.nealford .art. history.servletemotherearth; import javax.servlet.*; import javax.servlet.http.*; import java. io.*; import java. util.*; import com.nealford .art. history.servletemotherearth.lib.DbPool; import. API that helped the presenta- tion layer was the development of JavaServer Pages. 2. 2 Building web applications with JSP JSP aided the development of the presentation layer immensely by helping