Editor's note: It's usually better to solve problems with simplicity and finesse rather than muscle. That's the premise behind the recently released Better, Faster, Lighter Java. Its authors, Bruce Tate and Justin Gehtland, put this belief to the test in this two-part series on Spring, excerpted from Chapter 8 of their book. This week Bruce and Justin continue where they left off in part one, taking you through adding persistence to the Pet Store example, and looking at the area of presentation logic in the Spring framework.
Adding PersistenceThe CartItem object does not necessarily need to be persistent. On the other hand, you'd expect to pull products and categories from a database. J2EE application developers have long searched for a clean approach to persistence without much success. The best persistence frameworks allow transparency and do not invade the domain model. Spring lets you separate your transparent object from the data access layer. Spring then makes it easy to layer on persistence. You can use a JDBC abstraction layer, which abstracts away many of the tedious and error-prone aspects of JDBC, such as connection management and error handling. The Spring JDBC layer uses a feature called callback templates to pass control from your application to the framework. With this strategy, Spring removes the need to manage connections, result sets, and RDBMS-specific errors. This framework is useful when you want to use JDBC directly to process relational queries.
Often, you'd rather deal with objects instead of relations. Spring also has an appropriate model for transparent persistence. The jPetStore uses Spring's OR mapping layer, which provides a variety of prepackaged choices. Spring now supports mapping layers for basic JDBC DAO, Hibernate, and JDO. This example uses a DAO framework called iBATIS SQL Maps to implement a Spring DAO layer.
The ModelEach of the Spring solutions starts with a transparent domain model. Example 8-3 starts with the transparent model object, a product.
Example 8-3. Product.java
public class Product implements Serializable { private String productId; private String categoryId; private String name; private String description; public String getProductId( ) { return productId; } public void setProductId(String productId) { this.productId = productId.trim( ); } public String getCategoryId( ) { return categoryId; } public void setCategoryId(String categoryId) { this.categoryId = categoryId; } public String getName( ) { return name; } public void setName(String name) { this.name = name; } public String getDescription( ) { return description; } public void setDescription(String description) { this.description = description; } public String toString( ) { return getName( ); } }
There's nothing special here. It consists purely of properties, accessed through getters and setters, and one utility method, toString. When you look into the jPetStore application, you'll find similar classes for each of the other persistent objects in the domain: Account, Order, Category, Item, and LineItem.
The MappingAs with Hibernate, the iBATIS SQL Maps framework has a mapping file. In it, each persistent property in your Java bean maps onto a single database column. Using SQL Maps, create all of your SQL within that mapping file as well, isolating all SQL to your XML mapping files. Example 8-4 shows the XML mapping support for Product.
Example 8-4. Product.xml
[1] <sql-map name="Product"> [2] <cache-model name="oneDayProduct" reference-type="WEAK" <flush-interval hours="24"/> </cache-model> [3] <result-map name="result" class="jpetstore.domain.Product"> <property name="productId" column="PRODUCTID" columnIndex="1"/> <property name="name" column="NAME" columnIndex="2"/> <property name="description" column="DESCN" columnIndex="3"/> <property name="categoryId" column="CATEGORY" columnIndex="4"/> </result-map> [4] <mapped-statement name="getProduct" result-map="result"> select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT where PRODUCTID = #value# </mapped-statement> [5] <mapped-statement name="getProductListByCategory" result-map="result"> select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT where CATEGORY = #value# </mapped-statement> [6] <dynamic-mapped-statement name="searchProductList" result-map="result"> select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT <dynamic prepend="WHERE"> <iterate property="keywordList" open="(" close=")" conjunction="OR"> lower(name) like #keywordList[]# OR lower(category) like #keywordList[]# OR lower(descn) like #keywordList[]# </iterate> </dynamic> </dynamic-mapped-statement> </sql-map>
Here's what the annotations mean:
[1] Each mapping file corresponds to a domain object. The domain object in this case relates to the return types of the queries specified for this DAO.
[2] Other information about the DAO layer, like caching strategies, also belong in the mapping file. Here, iBatis maintains a cache for 24 hours and then flushes it.
[3] Each of these queries returns, of course, a product. This mapping ties each column of the result set to one of the fields in product.
[4] This SQL statement finds a product, given a productID.
[5] This SQL statement finds all products in a category. It returns a list of products.
[6] This SQL statement is dynamic. iBatis iterates over the keyword list and forms a dynamic query.
So far, you've seen the domain model for Product and its mapping, which contains queries. You're most of the way home.
The DAO InterfaceSomehow, the application must integrate with both Spring and SQL Maps. The application ties the two concepts together with a DAO interface, and a concrete implementation. Example 8-5 is the interface.
Example 8-5. ProductDAO.java
public interface ProductDao { List getProductListByCategory(String categoryId) throws DataAccessException; List searchProductList(String keywords) throws DataAccessException; Product getProduct(String productId) throws DataAccessException; }
That's simple enough. You can see an interface for each of the queries defined in the mapping. Specifically, you can see an interface getProduct that finds a product by ID, one for getProductListByCategory that returns all products in a category, and one for the dynamic query based on keywords. Now, the DAO throws Spring exceptions; any logic that uses the DAO will have consistent exceptions, even if you later decide to change implementations.
The DAO ImplementationAll that remains is to implement the interface with SQL Map. Example 8-6 is the SQL Map implementation for Product.
Example 8-6. SqlMapProductDao.java
public class SqlMapProductDao extends SqlMapDaoSupport implements ProductDao { [1] public List getProductListByCategory(String categoryId) throws DataAccessException { return getSqlMapTemplate( ).executeQueryForList("getProductListByCategory", } [1] public Product getProduct(String productId) throws DataAccessException { return (Product) getSqlMapTemplate( ).executeQueryForObject("getProduct", productId); } [1] public List searchProductList(String keywords) throws DataAccessException { Object parameterObject = new ProductSearch(keywords); return getSqlMapTemplate( ).executeQueryForList("searchProductList", parameterObject); } /* Inner Classes */ [2] public static class ProductSearch { private List keywordList = new ArrayList( ); public ProductSearch(String keywords) { StringTokenizer splitter = new StringTokenizer(keywords, " ", false); while (splitter.hasMoreTokens( )) { this.keywordList.add("%" + splitter.nextToken( ) + "%"); } } public List getKeywordList( ) { return keywordList; } } }
Here's what the annotations mean:
[1] These methods provide the SQL Map implementation of the interface. Other implementations might use Hibernate, JDO, or straight JDBC. In this case, the getTemplate call instructs Spring to get the template for iBATIS SQL Map support and execute the appropriate query using the framework.
[2] I'm not a big fan of inner classes, but that's what they used to implement the keyword search. In this case, the inner class supports the searchProductList method by implementing getKeywordList. The inner class helps to organize the code base, keeping all of the support in one location, with the rest of the DAO implementation.
Now you've seen the mapping, the model, and the DAO. You have a fully persistent model. Next, access the DAO layer with code. jPetStore funnels all DAO access through a façade layer.
Using the Model Through a FaçadeJust as in Chapter 3, it often makes sense to have a higher-level interface for a model, called the façade. In this case, the jPetStore façade serves three purposes:
Consolidates all of the clients of the data access layer. Presents a single, common user interface for the rest of the applications. Serves as an attachment point for other services, such as transaction support.In this case, the façade is a very thin layer around all of the DAO. Through configuration and method interceptors, Spring attaches declarative transaction support to the façade. In this case, the façade is in two parts: the interface and the implementation. The interface allows you to change the implementation of the façade without impacting the rest of the code. Example 8-7 shows the interface.
Example 8-7. PetStoreFacade.java
public interface PetStoreFacade { Account getAccount(String username); Account getAccount(String username, String password); void insertAccount(Account account); void updateAccount(Account account); List getUsernameList( ); List getCategoryList( ); Category getCategory(String categoryId); List getProductListByCategory(String categoryId); List searchProductList(String keywords); Product getProduct(String productId); List getItemListByProduct(String productId); Item getItem(String itemId); boolean isItemInStock(String itemId); void insertOrder(Order order); Order getOrder(int orderId); List getOrdersByUsername(String username); }
Think of this interface as a consolidated list of every method that creates, reads, updates, or deletes any Pet Store object. Notice that you do not see every method from all of the DAO. You see only the methods that we wish to expose to the rest of the world. Also, notice the naming consistency within the interface. This is important because within our configuration file, you saw the transaction support configured to propagate methods beginning with get, search, update, or insert.
The implementation simply calls the underlying DAO to do the appropriate job. It must implement all of the methods in the interface. Example 8-8 is the implementation of the methods related to the ProductDAO.
Example 8-8. Excerpt fromPetStoreImpl.java
[1] private ProductDao productDao; ... public void setProductDao(ProductDao productDao) { this.productDao = productDao; } ... [2] public List getProductListByCategory(String categoryId) { return this.productDao.getProductListByCategory(categoryId); } public List searchProductList(String keywords) { return this.productDao.searchProductList(keywords); } ...
Here's what the annotations mean:
[1] Shows the DAO access (includes the bold text). The Spring framework inserts the DAO into the façade using reflection. That means the façade must support a set method and a private member variable.
[2] The methods that provide data access use the underlying DAO to do the actual work (includes the bold text).
Of course, I haven't shown the implementation of all of the interface's methods. These are only the methods related to product. They come in two parts.
First, the application context wired each DAO to the façade. Spring uses reflection and the bean factory to create the product DAO and set it using the setProductDAO API. To support this, the façade needs a variable to hold the DAO and a set method to access it through reflection.
Second, the implementation is simple. The façade merely passes the request through to the model layer underneath. The ultimate implementation is much more powerful, though. The façade functions like an EJB session bean with respect to declarative transaction support. Through configuration, the POJO becomes a declarative transaction coordinator! It's also a central point of control for the entire database layer. All that remains is to configure the DAO layer.
Configuration for the DAO LayerRecall that you have seen only the configuration for the model. Example 8-9 shows the configuration of the data layer for a single database with simple transaction management. As you'd expect, you'll see the configuration of the JDBC driver and the declaration of all of the DAO beans.
Example 8-9. dataAccessContext-local.xml
<beans> [1] <bean id="propertyConfigurer" class="org.springframework.beans.factory. config.PropertyPlaceholderConfigurer"> <property name="location"><value>/WEB-INF/jdbc.properties</value></property> </bean> [2] <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"><value>${jdbc.driverClassName}</value></property> <property name="url"><value>${jdbc.url}</value></property> <property name="username"><value>${jdbc.username}</value></property> <property name="password"><value>${jdbc.password}</value></property> </bean> [3] <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"><ref local="dataSource"/></property> </bean> [4] <bean id="sqlMap" class="org.springframework.orm.ibatis.SqlMapFactoryBean"> <property name="configLocation"> <value>classpath:/sql-map-config.xml</value></property> </bean> [5] <bean id="accountDao" class=" jpetstore.dao.ibatis.SqlMapAccountDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> <bean id="categoryDao" class="jpetstore.dao.ibatis.SqlMapCategoryDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> <bean id="productDao" class=" jpetstore.dao.ibatis.SqlMapProductDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> <bean id="itemDao" class=" jpetstore.dao.ibatis.SqlMapItemDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> <bean id="orderDao" class=" jpetstore.dao.ibatis.SqlMapOrderDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> <property name="sequenceDao"><ref local="sequenceDao"/></property> </bean> <bean id="sequenceDao" class="jpetstore.dao.ibatis.SqlMapSequenceDao"> <property name="dataSource"><ref local="dataSource"/></property> <property name="sqlMap"><ref local="sqlMap"/></property> </bean> </beans>
Here's what the annotations mean:
[1] This bean handles the JDBC configuration. The JDBC configuration properties are in a standard JDBC configuration file, making them easier to maintain and read. Spring provides a configuring class that makes it easy to read property files without converting them to XML.
[2] Here you see the data source. It's a standard J2EE data source. Many J2EE applications and frameworks hard-wire an application or framework to a given data source. Configuring them instead makes it easy to choose your own source (and thus your pooling strategy).
[3] The applicationContext.xml configuration sets the transaction policy. This configuration specifies the implementation. This application uses the data source transaction manager, which delegates transaction management to the database via JDBC (using commit and rollback).
[4] The iBATIS SQL Map utility for building DAO must be configured. It's done here.
[5] Finally, you see the actual DAO configuration. As you may remember, the applicationContext.xml file referred to each of these beans by name.
This configuration accomplishes more than just decoupling the persistence tier from the model or the view. We've also decoupled transaction management from the persistence layer, separated the transaction policy from the implementation, and isolated the data source. Take a look at the broader benefits that have been gained beyond configuration.
The BenefitsThat's all of the persistence code for the Product. The code for the rest of jPetStore is similar. The application effectively isolates the entire domain model within a single layer. The domain has no dependencies on any services, including the data layer. You've also encapsulated all data access into a clean and concise DAO layer, which is independent of data store. Notice what you don't see:
Data source configurationHandled by the Spring framework. You don't have to manage a whole bunch of singletons, for session management, data sources, and the like. You can also delay key decisions such as the type of data source until deployment time.
Connection processingThe Spring framework manages all of the connection processing. One of the most common JDBC errors is a connection leak. If you're not very careful about closing your connections, especially within exception conditions, your application can easily lose stability and crash.
Specialized exceptionsMany frameworks pass SQL exceptions to the top. They frequently have SQL codes built in that may be specialized to your own RDBMS, making it difficult to code portable applications. Spring has its own exception hierarchy, which insulates you from these issues. Further, should you change approaches to Hibernate or JDO, you won't need to change any of your exception processing.
The end result of what we've done so far is pretty cool. We have a clean, transparent domain model and a low-maintenance service layer that's independent of our database. Each layer is neatly encapsulated. Now that we have looked at the backend logic, it's time to put a user interface on this application.
PresentationIn most places, the Spring framework doesn't reinvent working technologies. In the area of presentation logic, though, Spring introduces a simple model-view-controller framework called MVC Web that has many competing architectures, like Struts and Java Server Faces (JSF). Take solace, though. You don't have to use MVC Web to use Spring. But if you decide to do so, you will find a few advantages:
MVC Web does not dictate your choice of view. Other frameworks tend to provide better support for favored view technologies, such as Velocity (proprietary) and Struts (JSP). For example, Struts exposes the model via request attributes. As a result, you need to build a bridge servlet to use a technology such as Velocity that doesn't understand the Servlet API. Spring exposes the model through a generic map, so it can work with any view technology.
MVC Web provides consistent configuration across all aspects of a Spring application. It uses the same inversion-of-control paradigm that the other frameworks use.
MVC Web makes testing easier. Since you don't have to extend another class (like Action or ActionForm in Struts), you can easily mock the request and response.
If you've ever used Struts, you're familiar with the basic paradigm of MVC Web. Figure 8-4 shows how it works. Controllers basically handle all incoming requests from input views. If the input request is a submitted form, the controller calls a business validation routine, created and configured by the programmer, and sends either the associated error view or success view back to the user, based on results.
Figure 8-4. The MVC Web framework works much like Struts
As with other elements of the Spring framework, when you're trying to understand a new application, start with the configuration files and drill down from there. In this example, the user interface is configured in the petstore-servlet.xml file.
Consider HTML pages that search for products in a category, and search for products based on keywords. The configuration file needs two controllers to the application context file. Each entry specifies a controller and the model object, like in Example 8-10.
Example 8-10. Excerpt from web.xml
<bean name="/shop/searchProducts.do" class="jpetstore.web.spring.SearchProductsController"> <property name="petStore"><ref bean="petStore"/></property> </bean> <bean name="/shop/viewProduct.do" class="org.springframework.samples.jpetstore.web. spring.ViewProductController"> <property name="petStore"><ref bean="petStore"/></property> </bean>
Recall that all access to our data layer goes through the façade. As you'd expect, these bean ID entries specify the façade, called petstore. Each form in the application works in the same way. Let's drill down further and look at the controller for searchProducts.
ControllersFor MVC Web, each form generally shares a single instance of a controller, which routes all requests related to a given form. It also marshals the form to the correct validation logic and returns the appropriate view to the user. Example 8-11 shows the controller for the searchProducts view.
Example 8-11. SearchProductsController.java
public class SearchProductsController implements Controller { [1] private PetStoreFacade petStore; public void setPetStore(PetStoreFacade petStore) { this.petStore = petStore; } [2] public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { [3] if (request.getParameter("search") != null) { String keyword = request.getParameter("keyword"); if (keyword == null || keyword.length( ) == 0) { return new ModelAndView("Error", "message", "Please enter a keyword to search for, then press the search button."); } else { [4] PagedListHolder productList = new PagedListHolder( this.petStore.searchProductList(keyword.toLowerCase( ))); productList.setPageSize(4); request.getSession( ).setAttribute( "SearchProductsController_productList", productList); [5] return new ModelAndView("SearchProducts", "productList", productList); } } else { [6] String page = request.getParameter("page"); PagedListHolder productList = (PagedListHolder) request.getSession( ). getAttribute("SearchProductsController_productList"); if ("next".equals(page)) { productList.nextPage( ); } else if ("previous".equals(page)) { productList.previousPage( ); } return new ModelAndView("SearchProducts", "productList", productList); } } }
Here's what the annotations mean:
[1] Each controller has access to the appropriate domain model. In this case, it's natural for the view to access the model through our façade.
[2] A controller has an interface like a servlet, but isn't actually a servlet. User requests instead come in through a single dispatcher servlet, which routes them to the appropriate controller, populating the request membermeter. The controller merely responds to the appropriate request, invoking business data and routing control to the appropriate page.
[3] In this case, the request is to "search." The controller must parse out the appropriate keywords.
[4] The controller invokes the business logic with the keywords provided by the user.
[5] The controller routes the appropriate view back to the user (with the appropriate model).
[6] In this case, the request is "page." Our user interface supports more products than might fit on a single page.
FormsJust like Struts, Spring can map HTML forms onto Java objects. Example 8-12 is the Java bean that's returned when a Pet Store user registers an account.
Example 8-12. AccountForm.java
public class AccountForm { private Account account; private boolean newAccount; private String repeatedPassword; public AccountForm(Account account) { this.account = account; this.newAccount = false; } public AccountForm( ) { this.account = new Account( ); this.newAccount = true; } public Account getAccount( ) { return account; } public boolean isNewAccount( ) { return newAccount; } public void setRepeatedPassword(String repeatedPassword) { this.repeatedPassword = repeatedPassword; } public String getRepeatedPassword( ) { return repeatedPassword; } }
Each of these bean fields corresponds directly to an HTML input field or control. The Spring framework translates a submit request to the form, which can then be accessed as a POJO for validation, mapping input data, or other purposes. With Spring, unlike Struts, form objects can be any Java bean. There's no need to extend ActionForm. That's important, because you don't need to copy properties from an ActionForm to a domain object or value object.
ValidationYou may have noticed validation logic within the original applciationContext.xml. These beans are generally considered business logic, but they've got a tight relationship to the user interface and they're invoked directly by the Spring framework. When a user submits a form, Spring fires the validation logic. Based on the result, Spring routes control to the appropriate page. Example 8-13 shows the AccountValidator class, which validates the account form.
Example 8-13. AccountValidator.java
public class AccountValidator implements Validator { public boolean supports(Class clazz) { return Account.class.isAssignableFrom(clazz); } public void validate(Object obj, Errors errors) { ValidationUtils.rejectIfEmpty(errors, "firstName", "FIRST_NAME_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "lastName", "LAST_NAME_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "email", "EMAIL_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "phone", "PHONE_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "address1", "ADDRESS_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "city", "CITY_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "state", "STATE_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "zip", "ZIP_REQUIRED", "ZIP is required."); ValidationUtils.rejectIfEmpty(errors, "country", "COUNTRY_REQUIRED", } }
In this example, the Spring framework makes life easier for developers in several ways. The developer does not need to write validation methods by hand. Also, many prepackaged methods exist. The framework takes care of validation and routing. The framework takes care of routing control based on success or failure.
One chapter on the Spring framework cannot do it justice, but you've seen the overall gist of it. The advantages of the framework—and more importantly, this coding style—should jump off the page at you if you haven't seen it before. In particular, notice the clarity and simplicity that a cleanly layered architecture provides. You can probably imagine how easy it is to incorporate business logic with a transparent framework like Spring.
SummaryI've chosen the jPetStore application for a variety of reasons. The biggest is that you can quickly see the difference between a simple, fast, light application and the alternative. If you are not yet a believer, I challenge you to look up the EJB version of Pet Store. If you've never seen it, you'll be blown away by the difference. Our version is transparent and independent; the EJB example is invasive and dependent on the container. Ours is easy to understand, whereas the J2EE counterpart was buried under the complexity of EJB best practices.
I haven't always been a believer. In fact, I didn't know who Rod Johnson was before we were introduced in Boston at a conference. I've since come to appreciate this simple framework as elegant and important. If you're new to Spring, you've seen only a single application. I hope that through it, you can see how it embraces the principles in this book:
Keep it simpleSpring's easy to use and understand. In a single chapter, our example covers an application with transactions, persistence, a full web frontend, and a completely modular configuration engine.
Do one thing, and do it wellSpring's framework has many different aspects and subframeworks. However, it separates each concept nicely. The fundamental value of Spring is the bean factory and configuration service, which let you manage dependencies without coupling your code. Each additional layer of Spring is cleanly decoupled and independent.
Strive for transparencySpring applications don't need to rely on the basic container at all. In fact, they can easily exist outside of the container. You need only create and configure them manually. This ability makes Spring applications a joy to test.
You are what you eatSpring makes smart choices in the frameworks that it includes. The respected Apache projects for data sources and logging form a good foundation. Spring allows many configurable choices, letting you choose the best frameworks for a given solution.
Allow for extensionSpring may be the most open, extensible container in existence today. It allows effective and rapid extension with common configuration services and clean abstractions.
I haven't covered Spring in its entirety. My goal is only to show you that it's possible to build real-world applications that embrace the concepts set out in the first six chapters of this book. If you decide that you'd like to see more, make sure that you look into Spring's advanced features:
Integration with Hibernate and JDO AOP concepts Transactional templates with JTA supportThe authors of Better, Faster, Lighter Java continue to explore practical examples of the principles of their book in other chapters. Pick up your copy to see, for example, an implementation of a service called Simple Spider, and see that service integrated into Spring. Then, you'll be able to see the benefits of improved maintenance of a framework like this going forward.
本文地址:http://com.8s8s.com/it/it15830.htm