powerwind 发表于 2006-10-14 23:53

Jpetstore阅读心得

虽然对Spring不熟悉,又不懂iBatis,还是硬着头皮开始阅读Spring包自带的Jpetstore经典J2EE例子。

开始的时候,只想从大体上理解它的结构,或者说从某个部分开始了解这个经典的宠物店。

首先可以肯定,Jpetstore是按照MVC的模式设计的。持久化层用iBatis(这个我不懂,我多么希望它是用Hibernate啊!),控制器的servlet有两个选择,一个是用Struts,另一个是Spring。这些具体的以后慢慢看,慢慢理解吧。
今天主要了解到,Jpetstore使用了门面模式、单例模式,对数据厍操作用DAO对象。
门面接口 PetStoreFacade
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);
}
门面接口的实现类 PetStoreImpl

public class PetStoreImpl implements PetStoreFacade, OrderService {

        private AccountDao accountDao;

        private CategoryDao categoryDao;

        private ProductDao productDao;

        private ItemDao itemDao;

        private OrderDao orderDao;

        // -------------------------------------------------------------------------
        // Setter methods for dependency injection
        // -------------------------------------------------------------------------

        public void setAccountDao(AccountDao accountDao) {
                this.accountDao = accountDao;
        }

        public void setCategoryDao(CategoryDao categoryDao) {
                this.categoryDao = categoryDao;
        }

        public void setProductDao(ProductDao productDao) {
                this.productDao = productDao;
        }

        public void setItemDao(ItemDao itemDao) {
                this.itemDao = itemDao;
        }

        public void setOrderDao(OrderDao orderDao) {
                this.orderDao = orderDao;
        }

        // -------------------------------------------------------------------------
        // Operation methods, implementing the PetStoreFacade interface
        // -------------------------------------------------------------------------

        public Account getAccount(String username) {
                return this.accountDao.getAccount(username);
        }

        public Account getAccount(String username, String password) {
                return this.accountDao.getAccount(username, password);
        }

        public void insertAccount(Account account) {
                this.accountDao.insertAccount(account);
        }

        public void updateAccount(Account account) {
                this.accountDao.updateAccount(account);
        }

        public List getUsernameList() {
                return this.accountDao.getUsernameList();
        }

        public List getCategoryList() {
                return this.categoryDao.getCategoryList();
        }

        public Category getCategory(String categoryId) {
                return this.categoryDao.getCategory(categoryId);
        }

        public List getProductListByCategory(String categoryId) {
                return this.productDao.getProductListByCategory(categoryId);
        }

        public List searchProductList(String keywords) {
                return this.productDao.searchProductList(keywords);
        }

        public Product getProduct(String productId) {
                return this.productDao.getProduct(productId);
        }

        public List getItemListByProduct(String productId) {
                return this.itemDao.getItemListByProduct(productId);
        }

        public Item getItem(String itemId) {
                return this.itemDao.getItem(itemId);
        }

        public boolean isItemInStock(String itemId) {
                return this.itemDao.isItemInStock(itemId);
        }

        public void insertOrder(Order order) {
                this.orderDao.insertOrder(order);
                this.itemDao.updateQuantity(order);
        }

        public Order getOrder(int orderId) {
                return this.orderDao.getOrder(orderId);
        }

        public List getOrdersByUsername(String username) {
                return this.orderDao.getOrdersByUsername(username);
        }

}
暂时不管 OrderService 接口。
PetStoreImpl的那些setter方法正是spring的注入方法。
在配置文件中:
        <bean id="petStore" class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
                <property name="accountDao" ref="accountDao"/>
                <property name="categoryDao" ref="categoryDao"/>
                <property name="productDao" ref="productDao"/>
                <property name="itemDao" ref="itemDao"/>
                <property name="orderDao" ref="orderDao"/>
        </bean>
单例模式的实现并没有使用特别的工厂方法。原文这样注释:
* There is one instance of this class in the JPetStore application. In Spring
* terminology, it is a "singleton". This means a per-Application Context
* singleton. The factory creates a single instance; there is no need for a
* private constructor, static factory method etc as in the traditional
* implementation of the Singleton Design Pattern.
使用Spring的BeanFactory,可以轻易实现单例的。在Struts当控制器时,它是这样实现的。
BaseAction 类,整个应用程序的action都不再直接继承自Action类,而是BaseAction 类。
public abstract class BaseAction extends Action {

        private PetStoreFacade petStore;

        public void setServlet(ActionServlet actionServlet) {
                super.setServlet(actionServlet);
                if (actionServlet != null) {
                        ServletContext servletContext = actionServlet.getServletContext();
                        WebApplicationContext wac = WebApplicationContextUtils
                                        .getRequiredWebApplicationContext(servletContext);
                        this.petStore = (PetStoreFacade) wac.getBean("petStore");
                }
        }

        protected PetStoreFacade getPetStore() {
                return petStore;
        }

}
暂时这么多,不知理解是否有误,以后慢慢去理解学习。

powerwind 发表于 2006-10-15 09:45

对Struts的ActionForm的使用和对Action的使用类似,先写了个继承自ActionForm的类BaseActionForm,作为其它actionform的基类。

public class BaseActionForm extends ActionForm {

        /* Public Methods */

        public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
                ActionErrors actionErrors = null;
                ArrayList errorList = new ArrayList();
                doValidate(mapping, request, errorList);
                request.setAttribute("errors", errorList);
                if (!errorList.isEmpty()) {
                        actionErrors = new ActionErrors();
                        actionErrors.add(ActionErrors.GLOBAL_ERROR, new ActionError("global.error"));
                }
                return actionErrors;
        }

        public void doValidate(ActionMapping mapping, HttpServletRequest request, List errors) {
        }

        /* Protected Methods */

        protected void addErrorIfStringEmpty(List errors, String message, String value) {
                if (value == null || value.trim().length() < 1) {
                        errors.add(message);
                }
        }

}
为了需要安全检查的验证,还特别写了个SecureBaseAction的类。
public abstract class SecureBaseAction extends BaseAction {

        public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
                        HttpServletResponse response) throws Exception {
                AccountActionForm acctForm = (AccountActionForm) request.getSession().getAttribute("accountForm");
                if (acctForm == null || acctForm.getAccount() == null) {
                        String url = request.getServletPath();
                        String query = request.getQueryString();
                        if (query != null) {
                                request.setAttribute("signonForwardAction", url + "?" + query);
                        } else {
                                request.setAttribute("signonForwardAction", url);
                        }
                        return mapping.findForward("global-signon");
                } else {
                        return doExecute(mapping, form, request, response);
                }
        }

        protected abstract ActionForward doExecute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
                        HttpServletResponse response) throws Exception;

}
因为它的身份验证是单一(即没有多种身份多种权限),直接在session中检查是否有帐户。
在有比较多字段需要检查的时候,就在doValidate方法内进行验证(在有些地方,作者选择了直接覆盖execute方法),其它一般都是看看是否为空,所以用了一个通用的方法 addErrorIfStringEmpty(这个方法在BaseActionForm中定义)。

powerwind 发表于 2006-10-15 18:31

分层结构

从分层角度考虑,我把Jpetstore这样分开来看

DAO层:DAO接口(操作数据对象)
域模型层:持久化实体类(POJO对象)
业务逻辑层:(调用DAO,提供服务,似乎可以称为服务层)
注:这里没有考虑WEB层。

它的包结构如下图所示:

powerwind 发表于 2006-10-15 23:15

由于ORM框架使用我不懂的iBatis,我特意去看看它的实现,发现它的JAVABEAN没有像 hibernate 那样与数据库的表一一对应(虽然hibernate有时候不是这样,我还是这样说它)。
比如帐户(Account),在account数据表中的字段并不多,但那个JAVABEAN里属性还是很多的。因为在映射文件作了相应配置。如:
<select id="getAccountByUsername" resultMap="result">
    select
          signon.username as userid,
          account.email,
          account.firstname,
          account.lastname,
          account.status,
          account.addr1,
          account.addr2,
          account.city,
          account.state,
          account.zip,
          account.country,
          account.phone,
          profile.langpref,
          profile.favcategory,
          profile.mylistopt,
          profile.banneropt,
          bannerdata.bannername
    from account, profile, signon, bannerdata
    where account.userid = #value#
      and signon.username = account.userid
      and profile.userid = account.userid
      and profile.favcategory = bannerdata.favcategory
</select>
如果使用hibernate,通过配置实体的关系,然后使用面向对象的HQL查询就可以,而不用自己写这样的SQL查询语句。
我在想,如果逻辑层的BEAN的属性数据不适合在视图层使用,是否应该使用DTO呢?
虽然这是个经典的J2EE实例,但毕竟算是比较简单的,应该还有好些东西没有用到。

[ 本帖最后由 powerwind 于 2006-10-16 18:57 编辑 ]

powerwind 发表于 2006-10-17 22:46

今天并没有从Jpetstore的源码中读出什么心得,倒是从《J2EE Development without EJB》这本书的第十六章看到了些对Jpetstore的介绍。发觉我前面发的帖中内容作者都讲到了,可能是太明显重要的东西,也可能因为以前不觉意看了有点印象,然后一看源码就有那种“体会”来了。
    一开始我就希望持久层的ORM框架使用Hibernate,而作者说这样做是为了和原来用iBatis做的版本作比较而已。作者还提到:iBatis可以把不同数据表中的字段映射到单个的对象上。如果数据模型给定,这个技术就非常适合了。而Hibernate目前还不支持这种粗粒度对象。
   我猜想,如果要改成Hibernate的话,由于是细粒度对象,是不是应该要用到TDO来实现层之间的数据传输呢?我还没有打算花时间去修改练习,只能想一下。
   至于作者说到的分布式,目前还没有能力去理解,放着先。

powerwind 发表于 2006-10-18 23:57

一行一行地读,还是一个类一个类地读?一时间感觉没什么好读。
   于是我决定去看看它的WEB显示层,看看JSP文件。结果发现并没有什么特别,都是HTML和JSTL,极少用到Struts或Spring的标签(其实我也偏爱JSTL标签)。分页显示,只分上一页与下一页这样。看来看去,都觉得很普通。
    几乎每个JSP页面都包含一个头文件和一个尾文件。
   是了,它没有用到 titles 部局。

过两天再想想,再看看,一定还有不少值得学习的地方。

海上飞洪 发表于 2006-10-22 11:32

楼主是如何安装PETSTROE的?

powerwind 发表于 2006-10-22 11:57

很惭愧地告诉楼上的,我安装过,但没成功(登录时出现数据库错误)。
所以现在只是阅读它的代码,没有去执行程序。

活在阳光下 发表于 2006-11-2 17:25

楼主你的文章被转载了...
http://java.chinaitlab.com/Spring/529192.html
页: [1]
查看完整版本: Jpetstore阅读心得