Profil de javax咖啡生活PhotosBlogListes Outils Aide

Blog


31 mai

Hibernate查询解决方案

 这两个星期以来,我把原来用struts开发的一个测试工具改用struts+hibernate来实现,首先从心情上来,整个开发过程中始终保持愉快和平和,“原来开发可以这样愉快?”,再一点就是开发效率上高效了许多。
      现在sun又加入jdocentral.com开始着手JDO2.0,想想看等它出台以后将是一个怎样激动人心得场面,让我们拭目以待。
     
      用Hibernate来操纵持久数据非常简单,在这里一些简单的查询我会一笔带过,本文着重说明在综合查询兼有分页的时候我的一些经验,如果网友觉得我的方案还有不足的地方,也请和我讨论,我的email:plateau_t@sina.com.
     
      第一部分:Hibernate提供的查询接口或其方法(此部分不做深究,请参考hibernate手册)
     
       1。根据ID查询
    要用到Session接口的load方法。
    load(Class theClass, Serializable id)
    load(Class theClass, Serializable id, LockMode lockMode)
    load(Object object, Serializable id) 
    
       2。HQL语句进行查询
      
       2。1 利用Query接口,Query由Session里的createQuery()来产生一个查询
        1)不带参数的查询(这类比较简单)
        Query query=session.createQuery("select user from User as user");
        2)带参数的查询
        Query query=session.createQuery("select user from User as user where user.name=?");
        query.setString(0,name)//假设name为传过来的参数
        Query query=session.createQuery("select user from User as user where user.name=:name");
        query.setString("name",name)//假设name为传过来的参数
        (多个参数以此类推)
       
        利用Session接口的find查询
        find(String query)
        find(String query, Object[] values, Type[] types)
        find(String query, Object value, Type type)    均返回list  
        如:
        List list=session.find("select user from Users as user where user.name=?",name,Hibernate.STRING)
        List list=session.find("select user from Users as user where user.name=? and             user.pw=?",new Object[]{name,pw},new Type[]{Hibernate.STRING,Hibernate.STRING})
       
        {推荐使用Query的方法进行查询}  
       
      第二部分:hibernate综合查询解决方案 (此部分详细实例说明,如有不足的地方请写信给我)        
     
       大家从第一部分可以看到,带有参数的查询,必须使用到Query接口,如上边:
        Query query=session.createQuery("select users from Users as users where users.name=?");
        query.setString(0,name)//假设name为传过来的参数   
       但是在系统中如何才能写一个公用的查寻方法呢?咋一看,似乎是不可以的,因为每一次查询的参数不一样,参数的数量不一样(如下代码),那么我们如何提取共性呢?   
         Query query=session.createQuery("select users from Users as users where users.name=? and users.pw=?");
        query.setString(0,name)//假设name为传过来的参数 
        query.setString(1,pw);
      
      首先说明,我的解决方案是从Seesion接口的find方法找到出口的,如下为Session接口得find()方法之一:
        find(String query, Object[] values, Type[] types) 
      其中Object[]为存放参数值的数组,Type[]为存放参数类型的数组,他们的顺序是和query里“?” 的顺序是相同的。那么我为什么不用该find方法呢,因为如果有分页的情况,那么该方法将不适用。
    
      下面详细要说明的解决方案:
      首先我想创建三个新的对象:Paras.java(参数对象) ParasList.java(参数集合对象)HQuery.java
     (感谢我的同事camel提供注释良好的代码)
     1。Paras.java(参数对象)
    
  package com.ifreeway.homegrown.testing.waf;
  
  /**
   *
   * <p>Title:定义一个sql语句的条件参数类 </p>
   * <p>Description: 可以使用有序的参数集合传送给sql/hql语句 </p>
   * <p>Copyright: Copyright (c) 2003</p>
   * <p>Company: ifreeway</p>
   * @author camel
   * @version 1.0
   */
  
  public class Paras {
   /**
    * 参数名称
    */
   private Object pName;
   /**
    * 参数类型编码,于java.sql.types中的类型保持一致
    */
   private int typeNo;
  
   public Object getPName() {
    return pName;
   }
   public void setPName(Object pName) {
    this.pName = pName;
   }
   public int getTypeNo() {
    return typeNo;
   }
   public void setTypeNo(int typeNo) {
    this.typeNo = typeNo;
   }
  } 
 
 2。ParasList.java(参数集合对象) 
  package com.ifreeway.homegrown.testing.waf;
  
  import java.util.ArrayList;
  
  /**
   *
   * <p>Title: 参数集合类</p>
   * <p>Description: 封装sql/hql的参数到该集合类,便于处理和传递</p>
   * <p>Copyright: Copyright (c) 2003</p>
   * <p>Company: ifreeway</p>
   * @author camel
   * @version 1.0
   */
  
  public class ParaList extends ArrayList {
  
    /**
     * 在指定位置添加一个参数对象
     * @param index:参数的索引值
     * @param p:需要加入的参数对象
     */
    public  void addParas(int index,Paras p){
        super.add(index,p);
    }
  
    /**
     * 在集合的最后位置添加一个参数对象
     * @param p:需要加入的参数对象
     */
    public void addParas(Paras p){
      super.add(p);
    }
  
    /**
     * 取得指定位置的参数对象
     * @param index:参数的索引值
     * @return:参数对象
     */
    public Paras getParas(int index){
        return (Paras)super.get(index) ;
    }
    /**
     * 取得指定参数的索引
     * @param p:参数对象
     * @return:参数索引
     */
    public int indexofParas(Paras p){
       return super.indexOf(p) ;
    }
  
    /**
     * 从集合中去掉一个指定的参数对象
     * @param index:参数索引
     */
    public void removeParas(int index){
      super.remove(index) ;
    } 
  
  } 
 3。HQuery.java
  package com.ifreeway.homegrown.testing.waf;
  
  
  /**
   *
   * <p>Title: HQL的语句封装类</p>
   * <p>Description: 该对象封装HQL的查询语句,参数集合,排序参数,分组参数,单页起始地址  </p>
   * <p>Copyright: Copyright (c) 2003</p>
   * <p>Company:ifreeway </p>
   * @author camel
   * @version 1.0
   */
  
  public class HQuery {
  
    /**
     * HQL查询语句
     */
    private String queryString;
    /**
     * 参数集合对象
     */
    private ParaList paralist;
    /**
     * 排序字段
     */
    private String orderby;
    /**
     * 分组字段
     */
    private String groupby;
    /**
     * 分页起始查询地址
     */
    private int pageStartNo;
  
    /**
     * 取得一个Hibernate的Query对象
     * @return:Query对象
     */
    public String getQueryString() {
      return queryString;
    }
  
    /**
     * 设置一个HQL查询字符串
     * @param queryString:查询字符串
     *
     */
    public void setQueryString(String queryString) {
  
     this.queryString =queryString;
  
    }
  
    /**
     * 取得参数集合对象
     * @return:参数集合对象
     */
    public ParaList getParalist() {
      return paralist;
    }
  
    /**
     * 设置参数集合对象
     * @param paralist:参数集合对象
     */
    public void setParalist(ParaList paralist) {
      this.paralist = paralist;
    }
  
    /**
     * 取得排序字段
     * @return:排序字段
     */
    public String getOrderby() {
      return orderby;
    }
  
    /**
     * 设置排序字段
     * @param orderby
     */
    public void setOrderby(String orderby) {
      this.orderby = orderby;
    }
  
    /**
     * 取得分组字段
     * @return
     */
    public String getGroupby() {
      return groupby;
    }
  
    /**
     * 设置分组字段
     * @param groupby
     */
    public void setGroupby(String groupby) {
      this.groupby = groupby;
    }
  
    /**
     * 取得页起始地址
     * @return
     */
    public int getPageStartNo() {
      return pageStartNo;
    }
  
    /**
     * 设置页起始地址
     * @param pageStartNo
     */
    public void setPageStartNo(int pageStartNo) {
      this.pageStartNo = pageStartNo;
    }
  } 
  
 上面三个对象的关系是:
 
 用Paras来装载每一个查询参数
  Paras paras=new Paras();
  paras.setPName(...);
  paras.setTypeNo(...);
 然后放在ParasList中
  ParasList paraslist=new ParasList();
  paraslist.add(paras)
 最后把填充以后的ParasList集合给HQuery 
  HQuery hquery=new HQuery();
  hquery.setParalist(paraslist);
  
 先面我们写一个公用查寻方法,来实现我们的综合查询:
 
 /**
  *
  *  综合查询,首先实例化HQuery
  * @see com.ifreeway.homegrown.testing.common.waf.DBHandler#find(com.ifreeway.homegrown.testing.common.waf.HQuery)
  */
 public List find(HQuery _query) throws HibernateException {
  List itr = null;
  try {
   StringBuffer query_str = new StringBuffer(_query.getQueryString());
   //是否要排序
   if (_query.getOrderby() != null) {
    query_str.append(_query.getOrderby());
   }
   //是否要分组
   if (_query.getGroupby() != null) {
    query_str.append(_query.getGroupby());
   }
   Session session = getSession();
   Query query = session.createQuery(query_str.toString());
   if (_query.getParalist() != null) {
    List list = _query.getParalist();
    for (int i = 0; i < list.size(); i++) {
     Paras param = (Paras) list.get(i);
     switch (param.getTypeNo()) {//此处要根据参数类型的增加要增加相应的“case”
      case Types.VARCHAR :
       query.setString(i, param.getPName().toString());
       break;
      case Types.INTEGER :
       query.setInteger(
        i,
        ((Integer) param.getPName()).intValue());
       break;
      case Types.DATE :
       query.setDate(i, (java.sql.Date) param.getPName());
       break;
      case Types.DOUBLE :
       query.setDouble(
        i,
        ((Double) param.getPName()).doubleValue());
       break;
      case Types.BOOLEAN :
       query.setBoolean(
        i,
        ((Boolean) param.getPName()).booleanValue());
       break;
      case Types.CHAR :
       query.setCharacter(
        i,
        ((Character) param.getPName()).charValue());
       break;
      case Types.JAVA_OBJECT :
       query.setEntity(i, (BaseModel) param.getPName());
       break;
     }
    }
   }
   //是否存在分页,当_query.getPageStartNo()==0是不分页
   if (_query.getPageStartNo() != 0) {
    int pageno = _query.getPageStartNo();
    query.setFirstResult((pageno - 1) * Constants.RECORD_PER_PAGE);
    query.setMaxResults((pageno) * Constants.RECORD_PER_PAGE);
   }
   itr = query.list();
   closeSession();
  } catch (Exception e) {

  }
  return itr;
 } 
    
      好了一旦我们做好了上边的工作,查询对我们来说将是很容易的一件事情,而且可以达到公用,是不是省了许多力气?下面我将实例化一个例子来进一步说明:
     
      例子:
      HQuery hquery=HQuery();
      hquery.setQueryString("select users from Users as users where users.name=? and users.sex=?");
      hquery.setOrderby("order by users.age desc");
     
      //如果要分页,把当前页curpage传递给hquery
      hquery.setPageStartNo(curpage);
     
      //实例化参数,本例为两个参数
      Paras paras1=new Paras();
      paras1.setPName(name);
      paras1.setTypeNo(Types.VARCHAR);
     
      Paras paras2=new Paras();
      paras2.setPName(sex);
      paras2.setTypeNo(Types.INTEGER);
     
      ParasList paraslist=new ParasList();
      paraslist.add(paras1);
      paraslist.add(paras2);//注意顺序
     
      hquery.setParalist(paraslist);
     
      //好了,做好准备工作,调用查寻方法得到结果
      List list=find(hquery);
     
      完成,有兴趣的网又可以据此跳到find中看看具体执行情况,如果这样相信你会有更进一步得了解。还是那句话,这个解决方案也有不足的地方,如果你有更好的意见或方法,请和我联系。
     
      Jplateau


29 mai

JavaServer Faces框架使用的设计模式

本文中,作者 Anand Joshi 使用 JSF 框架中的设计模式阐释了 JavaServer™ Faces (JSF) 体系结构。他讨论了 JSF 体系结构中使用的 GoF 设计模式,以及这些模式在 JSF 框架中的作用。任何对设计模式和 JSF 体系结构有一定了解的人都能从 Anand 详细的介绍中有所收获。*读者应该对 GoF 设计模式和 JSF 技术有很好的了解。

  设计模式可以帮助用户在更高层次上抽象细节,更好地理解体系结构。如果比较熟悉 GoF 设计模式和 JavaServer Faces (JSF) 框架,本文可以帮助您洞察 JSF 框架中使用的设计模式,深入理解其工作原理。

  本文探讨了 JSF 框架中使用的设计模式。详细讨论的设计模式包括 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 模式。

设计模式和 JavaServer Faces (JSF) 技术

首先简要地介绍一下模式和 JSF 框架。

  • 模式。设计模式是对问题和解决方案进行抽象的普遍适用的方法。因为模式是所有开发人员和架构师公认的,所以模式可以节约时间和资源。用外行话来说,模式就是关于某个人所共知的问题的经过验证的解决方案。模式可以重用,重用使得解决方案更健壮。
  • Java Server Faces。 JSF 体系结构是一种 Web 应用程序框架。它是 Java Community Process (JCP) 推动的,有望成为 Web 应用程序开发的标准框架。目前用于开发 Web 应用程序的框架有 50 多个,这说明迫切需要实现框架的标准化,这正是 JSF 框架的目标!

深入剖析 JSF 模式

  现在我们来讨论 JSF 体系结构中的各种设计模式。本文将详细讨论 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 设计模式。我将分析每种模式的用途及其在 JSF 框架中的作用。

Singleton 模式

  Singleton 模式的目的是保证类只有一个实例被加载,该实例提供一个全局访问点。当启动具有 JSF 支持的 Web 应用程序时,Web 容器初始化一个 FacesServlet 实例。在这个阶段,FacesServlet 对每个 Web 应用程序实例化 Application 和 LifeCycle 实例一次。这些实例就采用众所周知的 Singleton 模式,通常只需要该类型的一个实例。

使用 JSF 的 Web 应用程序只需要 Application 和 LifeCycle 类的一个实例。LifeCycle 管理多个 JSF 请求的整个生命期。因为其状态和行为在所有请求之间共享,这些对象采用 Singleton 模式合情合理。LifeCycle 维护的 PhaseListeners 也是 Singleton 模式的。PhaseListeners 由所有 JSF 请求共享。在 JSF 框架中可以广泛使用 Singleton 模式,以减少内存占用和提供对象的全局访问。NavigationHandler(用于确定请求的逻辑结果)和 ViewHandler(用于创建视图)也是使用 Singleton 模式的例子。

Model-View-Controller (MVC)

  MVC 模式的目的是从数据表示(View)中将数据(即 Model)分离出来。如果应用程序有多种表示,可以仅替换视图层而重用控制器和模型代码。类似的,如果需要改变模型,可以在很大程度上不改变视图层。控制器处理用户动作,用户动作可能造成模型改变和视图更新。当用户请求一个 JSF 页面时,请求发送到 FacesServlet。FacesServlet 是 JSF 使用的前端控制器 servlet。和其他很多 Web 应用程序框架一样,JSF 使用 MVS 模式消除视图和模型之间的耦合。为了集中处理用户请求,控制器 servlet 改变模型并将用户导航到视图。

  FacesServlet 是 JSF 框架中所有用户请求都要经过的控制器元素。FacesServlet 分析用户请求,使用托管 bean 对模型调用各种动作。后台(backing)或托管(managed)bean 就是该模型的例子。JSF 用户界面(UI)组件是视图层的例子。MVC 模式把任务分解给具有不同技能的开发人员,使这些任务能够同时进行,这样 GUI 设计人员就可以使用丰富的 UI 组件创建 JSF 页面,同时后端开发人员可以创建托管 bean 来编写专门的业务逻辑代码。

Factory Method 模式

  Factory Method 模式的目的是定义一个用于创建对象的接口,但是把对象实例化推迟到子类中。在 JSF 体系结构中,Factory Method 模式被用于创建对象。LifeCycleFactory 是一个创建和返回 LifeCycle 实例的工厂对象。LifeCycleFactory 的 getLifeCycle (String LifeCycleId) 方法采用 Factory Method 模式,根据 LifeCycleId 创建(如果需要)并返回 LifeCycle 实例。自定义的 JSF 实现可以重新定义 getLifeCycle 抽象方法来创建自定义的 LifeCycle 实例。默认的 JSF 实现提供默认的 LifeCycle 实例。此外,对于每个 JSF 请求,FacesServlet 都从 FacesContextFactory 得到 FacesContext。FacesContextFactory 是一个抽象类,公开了 getFacesContext API,JSF 实现提供了 FacesContextFactory 和 getFacesContext API 的具体实现。这是另外一个使用 Factory Method 模式的例子,具体的 FacesContextFactory 实现创建 FacesContext 对象。

State 模式

  State 模式的目的是在表示状态的不同类之间分配与状态有关的逻辑。FacesServlet 对 LifCycle 实例调用 execute 和 render 方法。LifeCycle 协调不同的 Phrase 以便执行 JSF 请求。在这里 JSF 实现就遵循了 State 模式。如果没有使用这种模式,LifeCycle 实现就会被大量的条件(即 “if” 语句)搅得一塌糊涂。JSF 实现为每个状态(或阶段)创建单独的类并调用 step。phase 是一个抽象类,定了每个 step 的公共接口。在 JSF 框架中定义了六个 phrase(即 step):RestoreViewPhase、ApplyRequestValues、ProcessValidationsPhase、UpdateModelValuesPhase、InvokeApplicationPhase 和 RenderResponsePhase。

  在 State 模式中,LifeCycle 把 FacesContext 对象传递给 phase。每个阶段或状态改变传递给它的上下文信息,然后设置 FacesContext 本身中的标志表明下一个可能的步骤。JSF 实现在每个步骤中改变其行为。每个阶段都可以作为下一个阶段的起因。FacesContext 有两种标志 renderResponse 和 responseComplete 可以改变执行的顺序。每个步骤执行完成后,LifeCycle 检查上一阶段是否设置了这些标志。如果设置了 responseComplete,LifeCycle 则完全放弃请求的执行。如果经过某个阶段后设置了 renderResponse 标志,JSF 就会跳过剩下的阶段而直接进入 Render Response 阶段。如果这两个标志都没有设置,LifeCycle 就会按顺序继续执行下一步。

Composite 模式

  Composite 模式让客户代码能够统一处理复合对象和基本对象。复合对象是基本对象的容器。在第一阶段(Restore View 阶段)和最后一个阶段(Render Response 阶段),使用 JSF UI 组件构造 UI View。UIComponentBase 就是 Composite 模式中 Component 抽象类的一个例子。UIViewRoot 是 Composite 类,而 UIOutput(比方说)就是叶子(或者基本类)。UIComponentBase 类定义了叶子和复合对象的公共方法,如编码/解码值和子节点管理函数。子节点管理函数,如 getChildren,对于叶子节点返回空列表,对于复合节点则返回其子节点。

Decorator 模式

  Decorator 模式的目的是不通过子类化动态扩展对象的行为。JSF 框架有很多扩展点(即可插入机制)。JSF 实现可使用 Decorator 模式替换默认的 PropertyResolver、VariableResolver、ActionListener、NavigationHandler、ViewHandler 或 StateManager。通常自定义实现接受通过构造函数传递给它的默认实现的引用。自定义实现仅仅改写功能的一个子集,而将其他功能委托给默认实现。如果希望实现自定义的 ViewHandler,改写默认 ViewHandler 实现的 calculateLocale 方法,可以像 清单 1 那样编写 CustomViewHandler 类:
清单 1. CustomViewHandler 片段

public class CustomViewHandler extends ViewHandler {
public CustomViewHandler(ViewHandler handler) {
super();
oldViewHandler = handler;
} private ViewHandler oldViewHandler = null;
public void renderView(facesContext context, UIViewRoot view) {
//delegate method to oldViewHandler
oldViewHandler.renderView(context, view);
} //custom implementation of calculateLocale
public Locale calculateLocale(FacesContext context) {
}
}

Strategy 模式

  Strategy 模式的目的是封装不同的概念。JSF 框架采用 Strategy 模式使用委托实现模型呈现 UI 组件。JSF 技术支持两种呈现模型。在直接实现模型中,UI 组件对收到的请求中的数据进行解码,然后编码这些数据进行显示。在委托实现模型中,解码和编码操作委托给和组建关联的专门呈现器。后一种模型利用了 Strategy 设计模式,比直接实现更灵活。在 Strategy 模式中,将不同的算法封装在单独的对象中,从而可以动态地改变算法。JSF 实现可以用已有的 renderkit 实例注册另外的呈现器,当应用程序启动的时候,JSF 实现读取配置文件将这些呈现器和 UI 组件联系在一起。

Template Method 模式

  Template Method 模式的目的是将变化的步骤推迟到子类中,而在父类中定义那些固定的算法步骤。JSF 框架通过 PhraseListeners 展现了 Template Method 模式提供的功能。采用 Template Method(或者 “hook”)使得 Web 作者可以为不同阶段之间的可选步骤提供实现,而主要阶段仍然和 JSF 框架的定义一致。JSF 框架提供了 PhaseListeners,概念上类似于 Template Method 模式中的可变步骤。JSF 框架有六个预定义的阶段,在每个阶段之间,Web 作者可以实现 PhaseListeners 来提供类似于 Template Method hook 的 hook。事实上,这种结构比 Template Method 模式更具有扩展性。可以通过注册 PhraseId 为 ANY_PHRASE 的 PhaseListener 在每个阶段后提供 hook。如果 PhaseId 是 ANY_PHASE,JSF 实现就会在每个阶段之前和之后调用该 PhaseListener。JSF 框架中的实现略有不同,因为可以根本没有 PhaseListener,但是在 Template Method 模式中,子类通常重新定义父类中抽象的可变步骤。

Observer 模式

  Observer 模式的目的是当目标对象的状态改变时自动通知所有依赖的对象(即观察器)。JSF 在 UI 组件中实现了 Observer 模式。JSF 有两类内建事件:ActionEvent 和 ValueChangedEvent。ActionEvent 用于确定用户界面组件(如按钮)的激活。当用户单击按钮时,JSF 实现通知添加到该按钮上的一个或多个动作监听程序。于是该按钮被激活,或者说按钮(主体)的状态改变了。添加到按钮上的所有监听程序(即观察器)都收到通知该主体状态已经改变。类似的,当输入 UI 组件中的值改变时,JSF 实现通知 ValueChangeListener。

结束语

  JSF 框架利用了 Singleton、Model-View-Controller、Factory Method、State、Composite、Decorator、Strategy、Template Method 和 Observer 设计模式。因为它的体系结构建立在已经验证的设计模式的基础上,这是一个健壮的框架,模式在 JSF 框架中得到了很好的利用。

参考资料

学习


获得产品和技术

25 mai

Spring AOP中文教程

这是在网上发现的一篇关于Spring AOP编程的教程,读完这篇文章后,Spring AOP不再难以理解,因此我把它译成中文,推荐给Spring AOP的初学者。这是译文的链接

AOP正在成为软件开发的下一个圣杯。使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect。AOP可以防止代码混乱。
为了理解AOP如何做到这点,考虑一下记日志的工作。日志本身不太可能是你开发的主程序的主要任务。如果能将“不可见的”、通用的日志代码注入主程序中,那该多好啊。AOP可以帮助你做到。
Spring framework是很有前途的AOP技术。作为一种非侵略性的,轻型的AOP framework,你无需使用预编译器或其他的元标签,便可以在Java程序中使用它。这意味着开发团队里只需一人要对付AOP framework,其他人还是象往常一样编程。
AOP是很多直觉难以理解的术语的根源。幸运的是,你只要理解三个概念,就可以编写AOP模块。这三个概念是:advice,pointcut和advisor。advice是你想向别的程序内部不同的地方注入的代码。pointcut定义了需要注入advice的位置,通常是某个特定的类的一个public方法。advisor是pointcut和advice的装配器,是将advice注入主程序中预定义位置的代码。

既然我们知道了需要使用advisor向主要代码中注入“不可见的”advice,让我们实现一个Spring AOP的例子。在这个例子中,我们将实现一个before advice,这意味着advice的代码在被调用的public方法开始前被执行。以下是这个before advice的实现代码:

代码:
package com.company.springaop.test;

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;

public class TestBeforeAdvice implements MethodBeforeAdvice {

  public void before(Method m, Object[] args, Object target)
  throws Throwable {
    System.out.println("Hello world! (by "
        + this.getClass().getName()
        + ")");
  }
}
 


接口MethodBeforeAdvice只有一个方法before需要实现,它定义了advice的实现。before方法共用三个参数,它们提供了相当丰富的信息。参数Method m是advice开始后执行的方法。方法名称可以用作判断是否执行代码的条件。Object[] args是传给被调用的public方法的参数数组。当需要记日志时,参数args和被执行方法的名称,都是非常有用的信息。你也可以改变传给m的参数,但要小心使用这个功能;编写最初主程序的程序员并不知道主程序可能会和传入参数的发生冲突。Object target是执行方法m对象的引用。

在下面的BeanImpl类中,每个public方法调用前,都会执行advice:

代码:
package com.company.springaop.test;

public class BeanImpl implements Bean {

  public void theMethod() {
    System.out.println(this.getClass().getName()
        + "." + new Exception().getStackTrace()[0].getMethodName()
        + "()"
        + " says HELLO!");
  }
}


类BeanImpl实现了下面的接口Bean:

代码:
package com.company.springaop.test;

public interface Bean {
  public void theMethod();
}



虽然不是必须使用接口,但面向接口而不是面向实现编程是良好的编程实践,Spring也鼓励这样做。

pointcut和advice通过配置文件来实现,因此,接下来你只需编写主方法的Java代码:
代码:


package com.company.springaop.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Main {

  public static void main(String[] args) {
    //Read the configuration file
    ApplicationContext ctx
        = new FileSystemXmlApplicationContext("springconfig.xml");

    //Instantiate an object
    Bean x = (Bean) ctx.getBean("bean");

    //Execute the public method of the bean (the test)
    x.theMethod();
  }
}



我们从读入和处理配置文件开始,接下来马上要创建它。这个配置文件将作为粘合程序不同部分的“胶水”。读入和处理配置文件后,我们会得到一个创建工厂ctx。任何一个Spring管理的对象都必须通过这个工厂来创建。对象通过工厂创建后便可正常使用。

仅仅用配置文件便可把程序的每一部分组装起来。
代码:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <!--CONFIG-->
  <bean id="bean" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces">
      <value>com.company.springaop.test.Bean</value>
    </property>
    <property name="target">
      <ref local="beanTarget"/>
    </property>
    <property name="interceptorNames">
      <list>
        <value>theAdvisor</value>
      </list>
    </property>
  </bean>

  <!--CLASS-->
  <bean id="beanTarget" class="com.company.springaop.test.BeanImpl"/>

  <!--ADVISOR-->
  <!--Note: An advisor assembles pointcut and advice-->
  <bean id="theAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
      <ref local="theBeforeAdvice"/>
    </property>
    <property name="pattern">
      <value>com\.company\.springaop\.test\.Bean\.theMethod</value>
    </property>
  </bean>

  <!--ADVICE-->
  <bean id="theBeforeAdvice" class="com.company.springaop.test.TestBeforeAdvice"/>
</beans>
 


四个bean定义的次序并不重要。我们现在有了一个advice,一个包含了正则表达式pointcut的advisor,一个主程序类和一个配置好的接口,通过工厂ctx,这个接口返回自己本身实现的一个引用。

BeanImpl和TestBeforeAdvice都是直接配置。我们用一个唯一的ID创建一个bean元素,并指定了一个实现类。这就是全部的工作。

advisor通过Spring framework提供的一个RegexMethodPointcutAdvisor类来实现。我们用advisor的一个属性来指定它所需的advice-bean。第二个属性则用正则表达式定义了pointcut,确保良好的性能和易读性。

最后配置的是bean,它可以通过一个工厂来创建。bean的定义看起来比实际上要复杂。bean是ProxyFactoryBean的一个实现,它是Spring framework的一部分。这个bean的行为通过一下的三个属性来定义:


  • 属性proxyInterface定义了接口类。
  • 属性target指向本地配置的一个bean,这个bean返回一个接口的实现。
  • 属性interceptorNames是唯一允许定义一个值列表的属性。这个列表包含所有需要在beanTarget上执行的advisor。注意,advisor列表的次序是非常重要的。


Spring工具

虽然你可以手工修改Ant构建脚本,但使用SpringUI(译注:SpringUI现在是Spring framework的一部分,并改名为spring-ide),使用Spring AOP变得很简单,只要点点鼠标即可。你可以把SpringUI安装成Eclipse的一个plug-in。然后,你只需在你的project上右击鼠标,并选择“add Spring Project Nature”。在project属性中,你可以在“Spring Project”下添加Spring配置文件。在编译前把下面的类库加入project:aopalliance.jar,commons-logging.jar,jakarta-oro-2.0.7.jar和spring.jar。运行程序时你会看到下面的信息:

... (logging information)
Hello world! (by com.company.springaop.test.TestBeforeAdvice)
com.company.springaop.test.BeanImpl.theMethod() says HELLO!


优点和缺点

Spring比起其他的framework更有优势,因为除了AOP以外,它提供了更多别的功能。作为一个轻型framework,它在J2EE不同的部分都可以发挥作用。因此,即使不想使用Spring AOP,你可能还是想使用Spring。另一个优点是,Spring并不要求开发团队所有的人员都会用它。学习Spring应该从Spring reference的第一页开始。读了本文后,你应该可以更好地理解Spring reference了。Spring唯一的缺点是缺乏更多的文档,但它的mailing list是个很好的补充,而且会不断地出现更多的文档。

24 mai

Spring Framework中的AOP编程之入门篇

作为这个介绍Spring框架中的面向方面编程(Aspect-Oriented Programming,AOP)的系列的第一部分,本文介绍了使您可以使用Spring中的面向方面特性进行快速开发的基础知识。使用跟踪和记录方面(面向方面领域的HelloWorld)作为例子,本文展示了如何使用Spring框架所独有的特性来声明切入点和通知以便应用方面。本系列的第二部分将更深入地介绍如何运用Spring中的所有通知类型和切入点来实现更实用的方面和面向方面设计模式。

  本文的目的不是要介绍构成模块化J2EE系统——即Spring框架——的所有重要元素,我们将只把注意力放在Spring所提供的AOP功能上。由于Spring的模块化设计方法,我们可以只使用该框架的AOP元素,而无需对构成Spring框架的其他模块做太多考虑。

  在AOP方面,Spring提供了什么?

  “它的目标不是提供最完善的AOP实现(虽然Spring AOP非常强大);而是要提供AOP实现与Spring IoC的紧密集成,以便帮助解决企业应用中的常见问题。”

  Spring Framework参考文档

  为了实现这个目标,Spring框架目前支持一组AOP概念,从切入点到通知。本文将展示如何使用Spring框架中所实现的如下AOP概念:

  通知(Advice):如何将before通知、afterReturning通知和afterThrowing通知声明为bean。

  切入点(Pointcut):如何声明静态切入点逻辑以将XML Spring Bean Configuration文件中的所有内容联系在一起。

  Advisor:关联切入点定义与通知bean的方式。

  设置场景:一个简单的例子应用程序

  “一般而言,Spring并不是预描述的。虽然使用好的实践非常容易,但是它避免强制推行一种特定的方法。”
Spring Framework参考文档

  要试用Spring框架的AOP功能,首先我们要创建一个简单的Java应用程序。IbusinessLogic接口和BusinessLogic类为Spring框架中的bean提供了简易构件块。虽然该接口对于我们的简单应用程序逻辑来说不是必需的,但是它是Spring框架所推荐的良好实践。

public interface IBusinessLogic
{
 public void foo();
}

public class BusinessLogic
implements IBusinessLogic
{
 public void foo()
 {
  System.out.println("Inside BusinessLogic.foo()");
 }
}

  可以编写MainApplication类,借此练习BusinessLogic bean的公有方法。

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class MainApplication
{
 public static void main(String [] args)
 {
  // Read the configuration file
  ApplicationContext ctx = new FileSystemXmlApplicationContext("springconfig.xml");

  //Instantiate an object
  IBusinessLogic testObject = (IBusinessLogic) ctx.getBean("businesslogicbean");

  // Execute the public
  // method of the bean
  testObject.foo();
 }
}

  在BusinessLogic类及其关联接口中没有什么需要注意的。但是,MainApplication类初始化BusinessLogic对象的方式很有意思。通过使用ctx.getBean("businesslogicbean")调用,MainApplication将加载和管理BusinessLogic类的bean实例的任务转交给了Spring框架。

  允许Spring控制BusinessLogic bean的初始化,这使得Spring运行时有机会在bean被返回给应用程序之前执行J2EE系统所需的所有与bean相关的管理任务。然后Spring运行时配置可以决定对bean应用哪些任务和模块。该配置信息由一个XML文件提供,类似于下面所示的:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC
"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<!-- Bean configuration -->
<bean id="businesslogicbean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>IBusinessLogic</value>
</property>
<property name="target">
<ref local="beanTarget"/>
</property>
</bean>
<!-- Bean Classes -->
<bean id="beanTarget"
class="BusinessLogic"/>

</beans>

  该配置文件,即springconfig.xml,指定要加载一个接口与IbusinessLogic相匹配的bean。该bean随后被关联到BusinessLogic实现类。看起来好像是费了很大力气只为了加载一个简单的bean并调用一个方法,但是您要知道,这个配置文件只是使Spring框架可以透明地对应用程序应用其组件的众多特性的一个体现。

  图1显示了基本的顺序图:MainApplication原样执行,没有应用方面。


图1.没有对BusinessLogic bean应用方面时的顺序图
 
 
应用方法跟踪(Method Tracing)方面

  可能最基本的方面就是方法跟踪方面了。这可能是您找得到的最简单的方面了,因此它是研究新的AOP实现的一个很好的起点。

  方法跟踪方面在一个目标应用程序内捕获对所跟踪的方法的调用以及方法的返回值,并以某种方式显示这种信息。在AOP中,通知的before和after类型用于捕获这些类型的联结点,因为这两种通知可以在方法调用联结点之前或之后触发。使用Spring框架,方法跟踪方面的before通知是在TracingBeforeAdvice类中声明的。

import java.lang.reflect.Method;
import org.springframework.aop. MethodBeforeAdvice;

public class TracingBeforeAdvice
implements MethodBeforeAdvice
{
 public void before(Method m, Object[] args, Object target)
 throws Throwable
 {
  System.out.println("Hello world! (by " + this.getClass().getName() + ")");
 }
}

  类似地,after通知可以在TracingAfterAdvice类中声明。

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;

public class TracingAfterAdvice
implements AfterReturningAdvice
{
 public void afterReturning(Object object, Method m, Object[] args, Object target)
 throws Throwable
 {
  System.out.println("Hello world! (by " + this.getClass().getName() + ")");
 }
}

  这两个类都通过实现Spring框架的适当通知接口而表示了特定的通知。每种类型的通知都指定实现before(..)或afterReturning(..)方法,以便使Spring运行时可以告诉通知适当的联结点会在何时出现。值得注意的是,TracingAfterAdvice实际上是从AfterReturningAdvice扩展而来的,表示只有在联结点在无异常的情况下获得返回值时才运行通知。

  为了将通知与应用程序中的适当联结点关联起来,必须对springconfig.xml进行一些修改。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC
"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<!-- Bean configuration -->
<bean id="businesslogicbean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>IBusinessLogic</value>
</property>
<property name="target">
<ref local="beanTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>theTracingBeforeAdvisor</value>
<value>theTracingAfterAdvisor</value>
</list>
</property>
</bean>
<!-- Bean Classes -->
<bean id="beanTarget"
class="BusinessLogic"/>

<!-- Advisor pointcut definition for before advice -->
<bean id="theTracingBeforeAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="theTracingBeforeAdvice"/>
</property>
<property name="pattern">
<value>.*</value>
</property>
</bean>

<!-- Advisor pointcut definition for after advice -->
<bean id="theTracingAfterAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="theTracingAfterAdvice"/>
</property>
<property name="pattern">
<value>.*</value>
</property>
</bean<

<!-- Advice classes -->
<bean id="theTracingBeforeAdvice"
class="TracingBeforeAdvice"/>
<bean id="theTracingAfterAdvice"
class="TracingAfterAdvice"/>

</beans>

  theTracingBeforeAdvisor和theTracingAfterAdvisor advisor被添加到前面所声明的businesslogicbean。每个advisor都可能截获所有bean所关联到的联结点。Advisor本身就是bean,而它唯一的作用就是将切入点定义与通知bean关联起来。本例中的切入点定义是在静态对象层次结构中指定相关联结点的正则表达式。

  因为本例中使用了org.springframework.aop.support.RegexpMethodPointcutAdvisor切入点advisor,切入点逻辑是使用正则表达式指定的。正则表达式用于识别公有接口对IbusinessLogici接口的联结点。下面是一些可以用来指定IBusinessLogic接口上的不同联结点集合的正则表达式例子:

<value>.*</value>:该表达式选择advisor所关联到的一个或多个bean上的所有联结点。
<value>./IBusinessLogic/.foo</value>:该表达式只选择IbusinessLogic接口上的foo()方法的联结点。如果是advisor所关联到的bean,则该表达式只选择IBusinessLogic接口上的联结点。

  springconfig.xml文件中最后的bean声明指定实现通知bean的类。

  既然已经指定了跟踪方面的正确配置,那么下一次执行MainApplication时,这些方面就会在初始化过程中被编织进去,而BusinessLogic bean中的所有方法都将被跟踪,如图2所示。



图2. 方法跟踪方面应用到BusinessLogic bean之后的顺序图(单击图像查看大图)

  方法跟踪方面和例子应用程序的源代码可在本文末尾的参考资料小节进行下载。
 
方面的重用

  可以对方法跟踪方面进行扩展,提供一个稍微复杂的记录(Logging)方面。记录方面提供了一个很不错的重用例子,因为记录方面所需的许多特性都已经包含在方法跟踪方面中了。

  在本例中,记录方面扩展了方法跟踪方面,以便显示附加的与(在应用程序的执行过程中)所引发的异常有关的信息。

  要完全使用记录方面,需要对应用程序做一些更改。BusinessLogicException异常类提供了一个可以由IBusinessLogicInterface接口和BusinessLogic实现类新增的void bar()方法引发的异常。

public class BusinessLogicException
extends Exception
{}

public interface IBusinessLogic
{
 public void foo();

 public void bar()
 throws BusinessLogicException;
}

public class BusinessLogic
implements IBusinessLogic
{
 public void foo()
 {
  System.out.println("Inside BusinessLogic.foo()");
 }

 public void bar()
 throws BusinessLogicException
 {
  System.out.println("Inside BusinessLogic.bar()");
  throw new BusinessLogicException();
 }
}

  MainApplication类现在将对void bar()方法进行一次额外的调用,并处理选中的、可能由该方法引发的异常。

import org.springframeworkcontext.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class MainApplication
{
 public static void main(String [] args)
 {
  // Read the configuration file
  ApplicationContext ctx = new FileSystemXmlApplicationContext( "springconfig.xml");

  //Instantiate an object
  IBusinessLogic testObject = (IBusinessLogic) ctx.getBean("businesslogicbean");

  //Execute the public methods of the bean
  testObject.foo();

  try
  {
   testObject.bar();
  }
  catch(BusinessLogicException ble)
  {
   System.out.println("Caught BusinessLogicException");
  }
 }
}

  来自方法跟踪方面的TracingBeforeAdvice和TracingAfterAdvice通知可以整体重用。LoggingThrowsAdvice类为新的异常记录提供了通知。

import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;

public class LoggingThrowsAdvice
implements ThrowsAdvice
{
 public void afterThrowing(Method method, Object[] args, Object target, Throwable subclass)
 {
  System.out.println("Logging that a " + subclass + "Exception was thrown.");
 } 
}


  应用记录方面的最后一步是修改springconfig.xml配置文件,使其包含新添加的LoggingThrowsAdvice通知。

  Click for larger view
图3显示了运行MainApplication并使用Spring框架应用了记录方面的UML顺序图。
  
  图3. 记录方面应用到BusinessLogic bean之后的顺序图(单击图像查看大图)   此处的记录方面清楚地说明了如何重用现有方面以及如何在Spring框架中使用通知的throws形式。通过为before和after通知声明新的通知来重写现有的方法跟踪方面实现,可以实现更复杂的记录方面,记录到更复杂的记录框架,比如LOG4J。关于记录方面和例子应用程序的源代码,请参见本文末尾的参考资料小节。

  结束语

  本文展示了使用Spring框架中的基本AOP结构所应用的一些简单方面。在本系列的下一篇文章中,我们将介绍一些更实用的方面,探讨方面的生命周期,使用Spring框架的around通知,并使用Spring来应用AOP模式。
 

AOP及其在Spring中的应用

AOP简介
OOP思想对现代编程产生了深远的影响,但在某些方面,OOP也有其不足之处。比如在logging(日志)、transaction(事务)等方面,应用OOP将这些内容封装为对象的行为则会产生大量的代码重复,虽然通过一些设计模式可以减少这种重复,但我们还有更好的解决办法,那就是AOP(Aspect Oriented Programming)。AOP是最近兴起的一种编程思想,它是OOP思想的补充,而不是其对立面。
AOP,从字面的理解来看就是面向切面的编程,用一个比较通俗的例子来说,比如在访问多个对象前需要进行权限检查,那么如果按照面向对象的思路来说,权限检查势必会成为这多个对象的行为。如果每个对象都需要去实现这些行为,势必会造成大量重复代码的产生,写程序也会变得枯燥无味。但我们可以将权限检查看作是一个切面,所有对这些对象的访问都要经过这个切面。要了解AOP,就必须先了解几个基本的且非常重要的概念。

Aspect(切面):对象操作过程中的截面。如权限检查、日志、事务处理等。
Join Point(连接点):程序运行中的某个阶段点。如某个方法调用,异常抛出等。
Advice(处理逻辑):某个连接点所采用的处理逻辑。
PointCut(切点):一系列连接点的集合,它指明Advice在什么时候被触发。

示例

还是用例子来说明一切,比如现在有一个DomainObjDAO接口以及其实现类DomainObjDAOImpl
DomainObjDAO.java:
public interface DomainObjDAO {
    public void save();
}
DomainObjDAOImpl:
    public class DomainObjDAOImpl implements DomainObjDAO {
    private Logger logger = Logger.getLogger(this.getClass().getName());
    public void save() {
        System.out.println("saving domain object......");
    }
现在需要在save方法中添加对该业务对象的锁,比如在save前后加锁和解锁。拿到这个需求,在不影响外部调用逻辑以及不对现有代码改动的前提下,Proxy模式(GOF)是个不错的选择,新增一个Proxy类同样实现DomainObjDAO接口,在其实现方法中代理DomainObjDAOImpl类的save方法,并在save的前后调用lock以及unlock方法。这种方法使得我们不必改动外部调用逻辑以及现有代码,但是如果有多个DomainObjImpl的情况下,该方法的弊端便暴露无遗,我们必须实现与DomainObjImpl个数相同的Proxy类来实现该功能,这对我们来说将是非常恐怖且不可接受的。
这个例子再次印证我们开始所描述的,针对这类问题,OOP显得有些力不从心,而AOP却能很好的解决它,JDK1.3后所提供的动态代理的特性为我们利用AOP的思想解决这个问题提供了很好的思路,下面我们来看它如何实现。

动态代理实现AOP

    public class LockHandler implements InvocationHandler {
    private Logger logger = Logger.getLogger(this.getClass().getName()); 
    private Object originalObject;
    
    public Object bind(Object obj) {
    logger.info("coming here...");
    this.originalObject = obj;
    return Proxy.newProxyInstance(
    obj.getClass().getClassLoader(),
    obj.getClass().getInterfaces(),this);}
    
    public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
        Object result=null;
        if(arg1.getName().startsWith("save")){
            lock();
            result=arg1.invoke(this.originalObject,arg2);
            unlock();
        }
        return result;
    }
    private void lock(){
        logger.info("lock object...");
    }
    private void unlock(){
        logger.info("unlock object...");
    }
}
上述代码中并没有出现与具体应用层相关的接口以及类的引用,所以对所有的类都适用。这便解决了用普通Proxy类实现的弊端。但是动态代理要求所代理的类必须是某个接口的实现(这点可以通过obj.getClass().getInterfaces()看出),不过这也符合面向对象的设计思想,如果所代理的类没有实现任何接口,可以通过GCLIB来实现,这里就不再详述。

最后我们写下一个TestCase来测试动态代理的实现,如下:
public class DyproxyTestCase extends TestCase {
   private LockHandler handler=null;
   private DomainObjDAOImpl daoImpl=null;
protected void setUp() throws Exception {
        // TODO Auto-generated method stub
        super.setUp();
        handler=new LockHandler();
        daoImpl=new DomainObjDAOImpl();
        
    }
    
    protected void tearDown() throws Exception {
        super.tearDown();
    }
    public void testSave(){
        ((DomainObjDAO)handler.bind(daoImpl)).save();
        }
}

运行结果如下:
2004-12-1 23:01:10 test.aop.dynamicproxy.LockHandler bind
信息: coming here...
2004-12-1 23:01:10 test.aop.dynamicproxy.LockHandler lock
信息: lock object...
saving domain object......
2004-12-1 23:01:10 test.aop.dynamicproxy.LockHandler unlock
信息: unlock object...

至此,我们用动态代理实现了AOP,Spring的AOP实现正是采用了动态代理,我将在下一个Blog中讨论其实现。

java框架之AOP框架

摘要:

AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。 AOP就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中。
 
一般情况下,对象是由一行行的代码“粘合”在一起的。创建这个对象。创建那个对象。为那个对象(其值为这个对象)设置属性。其间还点缀着一些用户数据。将一切搅拌在一起。这是现代程序员在进行设计和编码时经常做的事情。
      将多个这样的类组合起来形成具有一定功能的组件,而很多这样的组件以这种方式连接起来会出现这样的问题:要实现不同的方法时,需要花费大量时间编写同样的代码。这些代码行中往往会有以下操作:将这个方法的活动记录日志到一个文件中以备调试,运行安全性检查,启动一个事务,打开一个数据库连接,记住捕捉 C++ 异常或者 Win32 结构化异常以转换为 COM 异常,还要验证参数。而且,还要切记在方法执行完之后销毁方法开始时的设置。还有很多的事务机制、安全机制以及对象池或线程池等性能优化机制。
       这种重复代码经常出现的原因在于,开发人员被训练为根据软件项目需求中的名词来设计系统。如果设计的是银行系统,Account类和Customer 类必不可少,它们都将自己独特的详细信息收集到一处,但是它们的每个方法也都需要进行日志、安全检查、事务管理等操作。区别在于,日志等操作是一些与特定应用无关的系统方面。
      这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成。

    这样,我们已经有了一种分散关注的思路(separation of concerns)。
       将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。这就是分散关注(separation of concerns)。
    AOP就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中。
      面向方面编程 (AOP) 是施乐公司帕洛阿尔托研究中心 (Xerox PARC) 在20世纪 90 年代发明的一种编程范式,它使开发人员可以更好地将本不该彼此纠缠在一起的任务(例如数学运算和异常处理)分离开来。 AOP 方法有很多优点。首先,由于操作更为简洁,所以改进了性能。其次,它使程序员可以花费更少的时间重写相同的代码。总之,AOP 能够为不同过程提供更好的封装性,提高未来的互操作性。
      是什么使软件工程师都希望自己能成为硬件工程师呢?自从函数发明以来,程序员花费了大量时间(及其老板的大多数资金)试图设计这样的系统:它们不过是一些组合模型,由其他人创建的部件构成,布置成独特的形状,再覆盖上一些悦目的颜色。函数、模板、类、组件等等一切,都是软件工程师自己创建“软件集成电路”(模拟硬件设计师的电子器件)的种种尝试。
      我把这些都归咎于 Lego(乐高玩具)。把两个玩具块(即组件)拼起时发出的悦耳的咔哒声很让人上瘾,会促使许多程序员发明一种又一种新的封装和重用的新机制。这方面最新的进展就称为面向方面编程 (AOP) 。 AOP 的核心是安排(一个摞在另一个之上)组件的一种方式,可以获得其他种类基于组件的开发方法无法得到的重用级别。这种安排是在客户端和对象之间的调用堆栈中进行的,其结果是为对象创建了一种特定的环境。这种环境正是 AOP 程序员主要追求的东西。

      AOP是什么?
      AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
  举例:假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个访问类,专门用于在同一时刻访问这同一个数据对象。
  为了完成上述并发访问同一资源的功能,需要引入锁Lock的概念,也就是说,某个时刻,当有一个访问类访问这个数据对象时,这个数据对象必须上锁Locked,用完后就立即解锁unLocked,再供其它访问类访问。
  使用传统的编程习惯,我们会创建一个抽象类,所有的访问类继承这个抽象父类,如下:
    abstract class Worker{
      abstract void locked();
      abstract void accessDataObject();
      abstract void unlocked();
}
  这样做的缺点:
      accessDataObject()方法需要有“锁”状态之类的相关代码。
      Java只提供了单继承,因此具体访问类只能继承这个父类,如果具体访问类还要继承其它父类,比如另外一个如Worker的父类,将无法方便实现。重用被打折扣,具体访问类因为也包含“锁”状态之类的相关代码,只能被重用在相关有“锁”的场合,重用范围很窄。
      仔细研究这个应用的“锁”,它其实有下列特性:
      “锁”功能不是具体访问类的首要或主要功能,访问类主要功能是访问数据对象,例如读取数据或更改动作。
      “锁”行为其实是和具体访问类的主要功能可以独立、区分开来的。
      “锁”功能其实是这个系统的一个纵向切面,涉及许多类、许多类的方法。
        因此,一个新的程序结构应该是关注系统的纵向切面,例如这个应用的“锁”功能,这个新的程序结构就是aspect(方面)。
  在这个应用中,“锁”方面(aspect)应该有以下职责:
  提供一些必备的功能,对被访问对象实现加锁或解锁功能。以保证所有在修改数据对象的操作之前能够调用lock()加锁,在它使用完成后,调用unlock()解锁。

      什么是方面?
      在考虑对象及对象与其他对象的关系时,我们通常会想到继承这个术语。例如,定义某一个抽象类 — Dog 类。在标识相似的一些类但每个类又有各自的独特行为时,通常使用继承来扩展功能。举例来说,如果标识了 Poodle,则可以说一个 Poodle 是一个 Dog,即 Poodle 继承了 Dog。到此为止都似乎不错,但是如果定义另一个以后标识为 Obedient Dog 的独特行为又会怎样呢?当然,不是所有的 Dogs 都很驯服,所以 Dog 类不能包含 obedience 行为。此外,如果要创建从 Dog 继承的 Obedient Dog 类,那么 Poodle 放在这个层次结构中的哪个位置合适呢?Poodle 是一个 Dog,但是 Poodle 不一定 obedient;那么 Poodle 是继承于 Dog 还是 Obedient Dog 呢?都不是,我们可以将驯服看作一个方面,将其应用到任何一类驯服的 Dog,我们反对以不恰当的方式强制将该行为放在 Dog 层次结构中。
      在软件术语中,面向方面的编程能够独立于任何继承层次结构而应用改变类或对象行为的方面。然后,在运行时或编译时应用这些方面。举一个关于 AOP 的示例,然后进行描述,说明起来比较容易。首先,定义四个关键的 AOP 术语,这很重要,因为我将反复使用它们:
      &#8226; 接合点 (Joinpoint) — 代码中定义明确的可识别的点。
      &#8226; 切点 (Pointcut) — 通过配置或编码指定接合点的一种方法。
      &#8226; 通知 (Advice) — 表示需要执行交叉切割动作的一种方法
      &#8226; 混入 (Mixin) — 通过将一个类的实例混入目标类的实例引入新行为。
      为了更好地理解这些术语,可以将接合点看作程序流中定义好的一点。说明接合点的一个很好的示例是:在代码调用一个方法时,发生调用的那一点被认为是一个接合点。
      切点用于指定或定义希望在程序流中截获的接合点。切点还包含一个通知,该通知在到达接合点时发生。因此,如果在一个调用的特定方法上定义一个切点,那么在调用该方法或接合点时,AOP 框架将截获该切点,同时还将执行切点的通知。
      通知有几种类型,但是最常见的情况是将其看作要调用的另一个方法。在调用一个带有切点的方法时,要执行的通知将是另一个要调用的方法。要调用的这个通知或方法可以是对象中被截获的方法,也可以是混入的另一个对象中的方法。

          AOP有必要吗?
  当然,上述应用范例在没有使用AOP情况下,也得到了解决,例如JBoss 3.XXX也提供了上述应用功能,但是没有使用AOP。
  但是,使用AOP可以让我们从一个更高的抽象概念来理解软件系统,AOP也许提供一种有价值的工具。可以这么说:因为使用AOP结构,现在JBoss 4.0的源码要比JBoss 3.X容易理解多了,这对于一个大型复杂系统来说是非常重要的。
  从另外一个方面说,好像不是所有的人都需要关心AOP,它可能是一种架构设计的选择,如果选择J2EE系统,AOP关注的上述通用方面都已经被J2EE容器实现了,J2EE应用系统开发者可能需要更多地关注行业应用方面aspect。

        AOP具体实现
  AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),将 AOP 用于多数大型系统或关键的生产系统还不完全成熟,但是随着语言支持的提高,AOP 的应用将更容易。另外,提高支持也是新的软件开发范例,例如利用面向方面的编程的软件工厂。目前有几种可用的 AOP 框架,每个框架都有其自己的方法、正面属性和负面属性。 目前AOP具体实现有以下几个项目:
  &#8226;AspectJ (TM) : 创建于Xerox PARC. 有近十年历史,成熟
  缺点:过于复杂;破坏封装;需要专门的Java编译器。
  &#8226;动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术。
  基于动态代理API的具体项目有:
  JBoss 4.0 JBoss 4.0服务器
        JAC (Java Aspect Components) — 是一个应用服务器。它为Java2平台、用于Java开发的企业开发环境(J2EE)、和基于Web的分布式应用,提供开放式资源的又一个选择(在GNU次常规公共许可证下发布)。JAC包括统一模型语言(UML)IDE,该UML IDE模块化应用商业逻辑并且自动生成和编译纯商业逻辑Java类。这些类,在JAC容器内执行,可从一组技术和/或商业的横切关系(crosscutting concerns)如:数据持久性、认证、配置文件管理、访问权限检测、演示、和负载平衡中无缝地受益。基于面向方面编程技术(AOP)的JAC将这些关系( concerns)从应用程序的核心商业逻辑中分离出来。
  nanning 这是以中国南宁命名的一个项目,搞不清楚为什么和中国相关?是中国人发起的?
  &#8226;基于字节码的项目有:
  aspectwerkz — 基于Java的简单、动态、轻量级、强大的AOP框架。既强大又简单,有助于更容易的集成AOP到新的或已存在的项目中。
  spring
        Spring.NET — 流行的 Java Spring 框架的一个 .NET 版本。在下一个版本中将实现 AOP。
        &#8226; DynamicAspects — 能够让你使用java编写的面向切面的程序设计,它使用在Sun JDK 1.5中介的"instrumentation"与"agent",Aspects能够软件各模块之间的关系在运行期安装与使用。
        &#8226; dynaop框架 — 使用一个基于运行时的编程机制将AOP代码插入对象中,而不是返回一个具有特征代码的对象。AOP将是面向对象设计(OO)的一个新的领域。
        &#8226; CAESAR — 是一个新的与Java兼容的AOP语言。所有java程序多能使用CAESAR。
        &#8226; PROSE — 是一个动态编排(weaving)工具(允许在运行期插入或抽取aspects)。PROSE aspects是规则的Java对象能够被发送到或从网络上的计算机接收。签名可被用于保证它们的完整性。一旦一个aspect插入到JVM中,任何事件的发生将影响在相应aspect advice执行的结果。假如一个aspect从JVM中撤消,aspect代码将被丢弃并且相应的拦截也将不会再发生。PROSE aspects是规则的Java对象能够被发送到或从网络上的计算机接收。签名可被用于保证它们的完整性。一旦一个aspect插入到JVM中,任何事件的发生将影响在相应aspect advice执行的结果。假如一个aspect从JVM中撤消,aspect代码将被丢弃并且相应的拦截也将不会再发生。
        &#8226; Encase — Encase 在运行时期间应用能够单独添加到对象的方面。
        &#8226; Aspect# — 一个针对 CLI 的 AOP 联合兼容框架,提供声明和配置方面的内置语言。
        &#8226; RAIL — RAIL 框架在虚拟机 JIT 类时应用方面。  
        &#8226; Eos — 用于 C# 的一个面向方面的扩展。

WEB开发中Spring AOP实际应用一例 --有关STRUTS框架

 在WEB开发中,用户对网页的访问权限检查是一个重要的环节。以STRUST为例,我们需要在Action的excute方法中编写相关的代码(一般是调用基类的函数),也很显然,在每个Action中这是一种重复劳动。
如果我们在excute运行之前,能够自动去调用基类的权限检查函数,这无疑是个好的解决办法。AOP就为我们提供了这样一种解决方法。

  下面以一个简化的实例介绍实现的办法。

  首先我们做一个接口:

public interface CheckInterface {
  public abstract void check(String name);
  public abstract void excute(String name);
}


  再做一个基类:

public abstract class BaseClass implements CheckInterface {
public BaseClass() {
}
public void check(String name){
if (name.equals("supervisor"))
System.out.println("Check Pass!!");
else {
System.out.println("No access privilege! Please do sth. else!");
}
}
}

  再做一个测试类:

public class ExcuteClass extends BaseClass {
public ExcuteClass() {
}

public void excute(String name){
System.out.println("Excute here!"+name);
}
}


  好了,下面做一个通知类(Advice):

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import org.apache.log4j.Logger;

public class BeforeAdvisor implements MethodBeforeAdvice {
private static Logger logger=Logger.getLogger(BeforeAdvisor.class);
public void before(Method m, Object[] args, Object target) throws Throwable {
if (target instanceof CheckInterface){
logger.debug("Is Instanceof CheckInterface!!!");
CheckInterface ci=(CheckInterface)target;
ci.check((String)args[0]);
}
}
}

  其中重要的before方法的参数:Object target传入的通知的对象(即测试类的接口),Method m, Object[] args分别是该对象被调用的方法和参数。我们再来作spring bean定义xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<description>Spring Quick Start</description>
<bean id="MyAdvisor" class="com.wysm.netstar.test.springaop.BeforeAdvisor"/>

<bean id="myPointcutAdvisor2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="MyAdvisor" />
</property>
<property name="patterns">
<list>
<value>.*excute.*</value>
</list>
</property>
</bean>

<bean id="checkInterface" class="com.wysm.netstar.test.springaop.ExcuteClass"/>

<bean id="myCheckClass" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.wysm.netstar.test.springaop.CheckInterface</value>
</property>
<property name="target">
<ref local="checkInterface" />
</property>
<property name="interceptorNames">
<value>myPointcutAdvisor2</value>
</property>
</bean>

</beans>

  这个定义文件指明了ExcuteClass为监视对象,它的excute方法被执行的时候,BeforeAdvisor将被调用。

  最后是测试类:

import junit.framework.TestCase;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class SpringTestCase2 extends TestCase {
CheckInterface test=null;

    protected void setUp() throws Exception {
super.setUp();
ApplicationContext ctx=new FileSystemXmlApplicationContext("src/com/wysm/netstar/test/springaop/aoptest.xml");
test = (CheckInterface) ctx.getBean("myCheckClass");
}

    protected void tearDown() throws Exception {
super.tearDown();
}
public void testExcute(){
test.excute("supervisor");
}
}

AOP的进一步实际应用

通过面向特征的编程减少编码时间和重复。

目前编程中最热门的新概念是面向特征的编程(ASpECt-oriented ProgrammingAOP)。AOP曾经主要用于学术和研发机构,如今开始进入主流开发领域。与OOP在面向过程的编程方法基础上的改进一样,AOP是在面向对象编程OOP)方法的基础上进行改进而来的一种创新的软件开发方法。OOP引入了封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。然而,OOP在处理范围扩展到一些无关对象的公共行为方面达不到要求。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如,看一下日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(CROSs-cutting)代码,这也是AOP编码方法产生的原因。

AOP提供一种提取横切代码的方法,这种横切代码横跨各个对象层次,但与它所跨越的对象代码在功能上没有相关性。AOP不是在中嵌入横切代码,而是允许你将横切代码提取到一个单独的模块中,然后在需要的时候动态地应用该代码,这个单独的模块叫做一个"特征代码"("aspect",也译作"标记")。通过在你的对象模型中需要应用横切代码的地方定义特定的位置--切入点(pointcut)--来实现动态的应用横切代码。在运行或编译时,根据你的AOP框架,横切代码被插入指定的切入点。本质上说,AOP允许你在对象中引入新功能,而对象无需了解所引入的功能。这是一个非常有用的概念。


为了进一步理解AOP是如何工作的,请看一个典型的AOP日志例子。代码清单1显示了两个包含日志代码的简单对象:objectA和ObjectB。

通过标准的面向对象的编程,每次你需要时,都要在相应的对象中编写日志代码。在代码清单1的例子中,使用SySTem.out.println()调用来记日志很烦琐。代替System.out.println()调用的另一种方法是使用log4j这样的日志框架,但这会带来额外的开销并给使用它的类带来不必要的杂乱东西。无论是使用System.out.println()调用还是日志框架来实现日志功能,日志代码都与它被嵌入到的类在功能上没有相关性。

通过AOP,你可以动态地将日志代码插入到需要日志功能的类中。这样,对象可以专注于其核心职责。只专注于其核心职责的Java对象往往被称为POJOs(PlAIn Old Java Objects):普通老式Java对象。你可以使用AOP给POJOs增加日志和一些其他类型的公共功能,而无需嵌入不必要的无关功能。

本文将介绍和解释AOP术语,解释不同AOP框架之间的差别,然后逐步说明一个将AOP用于缓存的示例。

AOP术语

AOP 引入了几个新的术语来描述其基本概念。必须切实理解这些术语才可能理解AOP。下面列出了AOP引入的术语及其描述:

1. 建议--是应用到或横切你现有对象模型的代码。建议代码就是修改已有对象的行为或属性的代码。建议通常也被称为引入件(introductions)或混入件(mix-ins)。

2. 切入点-定义在你的模型中要应用建议的位置点。例如,切入点定义在一个类的什么位置应引入代码或哪个方法要在执行前被拦截。切入点也被通称为连接点(joinpoints)。

3. 特征-它将建议和切入点封装到功能单元中,其封装方式与OOP使用类将字段和方法封装到内聚单元(cohesive unit)的方式基本相同。例如,你可能有一个日志特征,它包含了将日志代码应用于对象的所有设置(setter)和获取(getter)方法的建议和切入点。

选择一个框架

在开始使用AOP之前,必须选择一个所用的AOP框架。Java和任何其他主流的面向对象的编程语言都没有对AOP提供内置支持。不过,有一些支持Java的可用的AOP框架,其中大多数都有着相同的核心功能,但在AOP连接到对象模型的方式上有差别。

一些AOP框架使用字节码操作来连接到对象模型,其他一些则使用基于代理的系统进行连接。使用字节码方法的框架在源代码编译为字节码之前修改源代码或在编译后修改字节码。这两种方式都可以有效地得到相同的结果:经过修改的字节码将AOP嵌入到了原始代码。基于代理的框架使用一个代理系统,AOP框架籍此截取所有对具有某特征代码对象的方法调用,然后代理执行对这些框架想要的对象的方法调用。这种代理功能是透明的,而且可以保持字节码总是不变。

各个AOP框架之间的另一个核心区别是特征代码的定义和应用的方式。有些框架中,通过代码来定义和应用特征,而其他一些框架则要求通过XML配置文件来定义和应用特征代码。更好的是,一些框架同时支持用代码和XML配置文件定义和应用特征代码。

因为大多数AOP框架的功能有重叠,究竟使用哪个框架往往取决于某个特定框架的独特功能。本文中的AOP示例应用程序是用一个新的AOP框架dynaop开发的。但是,这个示例应用的基本原理是通用的,可应用于所有的AOP框架。dynaop框架支持用代码定义和应用特征代码,它还提供了一个通过BeanShell脚本框架定义和应用特征代码的独特解决方案。 BeanShell脚本可以使你轻松地编写Java对象脚本,比如示例应用程序所示。

通过dynaop使用AOP

一个简单的缓存特征对方法的结果进行缓存,它说明了AOP的一些基本概念。第一次对方法的调用具有实用的缓存特征代码,它调用该方法并缓存其执行的结果。对这个方法的后续调用将从缓存中返回方法的调用结果。这个基本的缓存特征代码对于运行时间长的方法非常有用,诸如处理大量数据的方法或对某个数据源发出多个查询的方法等。


如代码清单1中的日志例子,在每个需要的类中嵌入缓存功能会导致大量的重复代码。而且,缓存对于大多数对象并不是一个核心功能,而很适合用AOP来实现统一和重复使用。

这个缓存应用程序包括四个文件:


CachingInterceptor.java-包含对方法调用结果进行缓存的AOP代码。这些代码被称为建议。


User.java-一个基本类,其中包含对"name"字段进行设置和获取"getter"和"setter"的方法。 CachingInterceptor AOP代码将被用于这个类。


CacheTest.java-一个说明CachingInterceptor AOP代码使用情况的示例应用程序。


dynaop.bsh-规定在哪里应用CachingInterceptor advice的BeanShell脚本。这些规定被称为切入点(pointcuts)。

以下部分将详细解释每个文件。

CachingInterceptor.java文件。CachingInterceptor类以一种标准、通用的方式将可以应用到任何类的缓存代码进行封装。这个类将方法初次被调用后的结果缓存起来。然后CachingInterceptor类拦截对其结果已被缓存的方法的后续调用,并直接从缓存中返回这些结果,从而为运行时间很长的方法提供快速响应。代码清单2显示了CachingInterceptor类。

CachingInterceptor类包含intercept()、calculateCacheCode()和getFullMethodName()各个方法。intercept()方法实现dynaop.Interceptor接口并包含这个特征的核心建议代码。该方法在具有特征代码的方法被调用之前被调用,然后它负责调用这个具有特征代码的方法。重要的是,intercept()方法是一个代理,用于插入在该方法被代理前执行的功能。另外,intercept()方法控制代理方法是否真正被调用。

CachingInterceptor类的intercept()方法调用calculateCacheCode()方法为将被调用的方法计算一个缓存键值。然后使用这个键值为被调用的方法(在本例中是User类中的getName()方法,在后面部分有定义)查找缓存,查看是否其结果已被缓存。如果结果已经被缓存,则返回被缓存的结果,不再调用User.getName()方法。如果结果没有被缓存,则调用User.getName()方法,并将其结果缓存起来供后续调用使用。这是一个基本的缓存实现,不考虑缓存中的时效数据项和缓存是否超长。


User.java 文件。User类是一个简单的类,只有一个字段以及针对该字段的获取和设置方法。 CachingInterceptor特征代码被用于这个类的getName()方法。代码清单3 显示了User类。

请注意,这个类中的每个方法都有一个System.out.println()调用,指明该方法什么时候被调用。这些调用用来说明示例应用运行时究竟会产生什么结果。 下一步

了解更多AOP信息
aosd.net
eclipse.org/AspectJ

BeanShell

下载
dynaop

本应用程序

阅读更多James Holmes的文章
Java艺术
Struts: 完整的参考手册
 

CacheTest.java 文件。CacheTest类包含说明CachingInterceptor特征代码如何使用的示例应用程序的代码。这个应用程序只是实例化了一个User对象,并多次调用它的getName()方法,显示出连续调用缓存中的方法结果以及对该方法的直接访问。代码清单4显示了CacheTest类。

CacheTest包含一个main()方法,所以它可以作为一个独立的应用程序来运行。

dynaop框架使用一个基于运行时的编织机制将AOP代码插入对象中,这样来直接实例化User对象,使用这个新的操作符,而不是返回一个具有特征代码的对象。为了在示例应用程序中使用CachingInterceptor特征代码,CacheTest类通过dynaop的ProxyFactory类来实例化User对象。一旦你通过ProxyFactory.getInstance() .extend()实例化了这个对象,你就可以像使用其他任何对象一样正常地使用User对象。从这点来说AOP代码的执行是透明的。


dynaop.bsh文件。dynaop.bsh文件是一个BeanShell脚本,用于指定应用CachingInterceptor建议的切入点。以下是dynaop.bsh文件的内容:


// Apply interceptor to all
// getter methods.
interceptor
(
  User.Class,
  GET_METHODS,
  new CachingInterceptor()
);


这个简单脚本指定了CachingInterceptor建议将应用到User类的所有获取方法。GET_METHODS是dynaop用来方便地指定一组切点的几个常量之一。如果必要,你也可以明确地为一些切点指定各个方法。

编译和运行这个应用程序

最后,编译和运行这个应用程序。假设你将这个应用程序的四个文件放在了你安装的dynaop的目录下,例如c:\java\dynaop,下面的命令行将编译这个应用程序:

 

javac -classpath .\bsh-2.0b1.jar;
                .\cglib-asm-1.0.jar;
               .\dynaop-1.0-beta.jar;
               .\jakarta-oro-2.0.7.jar
               *.java


编译完后,执行以下命令运行它:


java -classpath .\;
               .\bsh-2.0b1.jar;
               .\cglib-asm-1.0.jar;
               .\dynaop-1.0-beta.jar;
               .\jakarta-oro-2.0.7.jar
               CacheTest


图1显示了这个示例应用程序的运行结果。这个应用程序先调用User类的setName()方法,然后调用getName()方法。这两个调用都在User类中被启用,对getName()的后续调用在缓存中查找到结果,而不再在User类中被启用。

无提示--不好意思!

-----
图1: 命令行提示 


结论

AOP提供了一个创建软件的功能丰富的新平台,它去除了类的一些不必要职责,并极大地促进了代码的重用。 AOP的日常实际使用包括提取日志代码、提取安全性和缓存代码等,但AOP的应用远不止于此。随着AOP的日益成熟和发展,它还将会有更多的应用。

AOP应用实例--Spring事务处理及其AOP框架的内幕

Spring框架中成功吸引人的一点就是容器事务的管理,提供了一个轻量级的容器事务处理,针对的对象是普通的java类,使用Spring事务管理的话,你可以按照自己的业务把一些相关的方法纳入其事务管理里面,这就避免了程序员在处理事务的过程中繁琐的工作.同时这些也是ejb2.X规范里面吸引人的一点,这在spring里面都很好的提供.虽然在跨容器的事务管理,spring里面并没有提供,但是对于一般的web程序来说,也不需要仅仅为了那些功能而不得不使用ejb.不过,最近jboss的嵌入式的ejb容器也可以做的更小了,也是开源中的选择之一.无论技术是怎样发展的,当前,我们先来研究其中AOP实现的方法.

       事实上,Spring中的事务处理是通过AOP思想来实现的,Spring AOP与Aspect J和JBoss具有很大的不同,首先,使用Spring AOP框架的用户要记住的一点是,Spring AOP针对的是方法层次上的实现,而其他两者对字段也提供了支持.说到Spring AOP的内幕,其实也不难,对于有接口的类,使用的是Java内部类提供的Proxy;而对于那些不实现接口的类,使用的是cglib库,动态创建一个子类来实现.

      在Spring AOP中提供了4种处理切入类型:around,before,after,introduction.顾名思义,

     1)around是针对具体的某个切入点的方法(比如,现在有个OrderBook方法,around的切入类型是就这个方法的内部调用,是通过java的元数据,在运行时通过Method.invoke来调用,具有返回值,当发生意外的时候会终止.记住的一点是,返回值.);

    2)before是在方法调用前调用(在OrderBook方法前调用,但是没有返回值,同时在通常意外情况下,会继续运行下一步方法.记住的一点是没有返回值);

    3)after和before刚好相反,没有什么特别的地方.

    4)introduction是一个更加特殊的,但功能更加强大的切入类型.比如(你现在有Book对象,Computer对象,还有几十个这种业务对象,现在你希望在每个这样的对象中都加入一个记录最后修改的时间.但是你又不希望对每个类都进行修改,因为太麻烦了,同时更重要的一点,破坏了对象的完整性,说不定你以后又不需要这个时间数据了呢...这时怎么办呢?Spring AOP就为你专门实现这种思想提供了一个切入处理,那就是introduction.introduction可以为你动态加入某些方法,这样可以在运行时,强制转换这些对象,进行插入时间数据的动作,更深的内幕就是C++虚函数中的vtable思想).不过这种动态是以性能作为代价的,使用之前要慎重考虑,这里我们谈的是技术,所以就认为他是必需的.

     好,现在我们就拿第四种来进行举例说明Spring AOP的强大之处:

1)假设创建了一个BookService接口及其实现方法(你自己的业务对象):

//$ID:BookService.java Created:2005-11-6 by Kerluse Benn
package com.osiris.springaop;

public interface BookService {
 public String OrderComputerMagazine(String userName,String bookName);
 public String OrderBook(String userName,String bookName);
}

//$ID:BookServiceImpl.java Created:2005-11-6 by Kerluse Benn
package com.osiris.springaop;

public class BookServiceImpl implements BookService{
 public String OrderBook(String name,String bookName) {
  // TODO Add your codes here
  String result=null;
  result="订购"+bookName+"成功";
  return result;
 }

 public String OrderComputerMagazine(String userName, String bookName) {
  // TODO Add your codes here
  String result=null;
  result="订购"+bookName+"成功";
  return result;
 }
}

2)事实上你还有很多这样的对象,现在我们希望在每个对象中添加我们的功能最后修改的时间,功能如下:

//$ID:IAuditable.java Created:2005-11-7 by Kerluse Benn
package com.osiris.springaop.advices.intruduction;

import java.util.Date;

public interface IAuditable {
 void setLastModifiedDate(Date date);
 Date getLastModifiedDate();
}

3)因为我们使用的切入类型是introduction,Spring AOP为我们提供了一个描述这种类型的接口IntroductionInterceptor,所以我们的切入实现处理,也需要实现这个接口:

//$ID:AuditableMixin.java Created:2005-11-7 by Kerluse Benn
package com.osiris.springaop.advices.intruduction;

import java.util.Date;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.IntroductionInterceptor;

public class AuditableMixin implements IAuditable,IntroductionInterceptor{
 private Date lastModifiedDate;
 
 public Object invoke(MethodInvocation m) throws Throwable {
  // TODO Add your codes here
  if(implementsInterface(m.getMethod().getDeclaringClass())){
   return m.getMethod().invoke(this,m.getArguments());
   //invoke introduced mthod,here is IAuditable
  }else{
   return m.proceed(); //delegate other method
  }
 }

 public Date getLastModifiedDate() {
  // TODO Add your codes here
  return lastModifiedDate;
 }

 public void setLastModifiedDate(Date date) {
  // TODO Add your codes here
  lastModifiedDate=date;
 }

 public boolean implementsInterface(Class cls) {
  // TODO Add your codes here
  return cls.isAssignableFrom(IAuditable.class);
 }
 
}
4)ok,现在业务对象BookService类有了,自己希望添加的处理也有了IAuditable,那就剩下使用Spring AOP框架的问题了,配置bean.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
 <!-- Beans -->
 <bean id="BookServiceTarget" class="com.osiris.springaop.BookServiceImpl" singleton="false"/>

 <!-- introduction advice -->
 <bean id="AuditableMixin" class="com.osiris.springaop.advices.intruduction.AuditableMixin" singleton="false"/>

 <!-- Introduction advisor -->
 <bean id="AuditableAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"
  singleton="false">
  <constructor-arg>
   <ref bean="AuditableMixin"/>
  </constructor-arg>
 </bean>
 
 <bean id="BookService" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="target">
   <ref bean="BookServiceTarget"/>
  </property>
  
  <property name="singleton">
   <value>false</value>
  </property>
  
  <!-- force to use cglib -->
  <property name="proxyTargetClass">
   <value>true</value>
  </property>
  
  <!-- introduction methods -->
  <property name="proxyInterfaces">
   <value>com.osiris.springaop.advices.intruduction.IAuditable</value>
  </property>
  
  <property name="interceptorNames">
   <list>
    <value>AuditableAdvisor</value>
   </list>
  </property>
 </bean>
 
</beans>

以上就是配置文件,现在我们假设使用业务对象如下,这里是一个简单测试类:

//$ID:MainApp.java Created:2005-11-6 by Kerluse Benn
package com.osiris.springaop;

import java.util.Date;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import com.osiris.springaop.advices.intruduction.IAuditable;

public class MainApp {
 /**
  * @param args
  * @author Kerluse Benn
  */
 public static void main(String[] args) throws Exception{
  // TODO Add your codes here
  BeanFactory factory=new XmlBeanFactory(new FileSystemResource("bean.xml"));
  BookService bookService=(BookService)factory.getBean("BookService");
  IAuditable auditable=(IAuditable)bookService;
  System.out.print(bookService.OrderBook("Kerluse Benn","Professional C#"));
  auditable.setLastModifiedDate(new Date());
  System.out.println(" 订购时间为"+auditable.getLastModifiedDate());
  Thread.sleep(10000);
  System.out.print(bookService.OrderBook("Kerluse Benn","Expert j2ee one-on-one"));
  auditable.setLastModifiedDate(new Date());
  System.out.println(" 订购时间为"+auditable.getLastModifiedDate());
 }
}

输出结果:
订购Professional C#成功 订购时间为Mon Nov 07 11:35:20 CST 2005
订购Expert j2ee one-on-one成功 订购时间为Mon Nov 07 11:35:30 CST 2005

看见上面黑体字:

IAuditable auditable=(IAuditable)bookService;

由于bookService对象事实上已经实现了IAuditable接口,通过Spring AOP的introduction切入实现,所以在运行时(熟悉C++的vtable模型的可以在大脑里想一下)可以转换,我们就可以随意添加自己的接口方法了.

好了,关于Spring AOP就介绍到这了,其他相关的内容可以参考相应的书籍,这篇文章的目的主要是为了介绍一下AOP思想应用的强大之处.具体的相关应用还包括用户操作验证等等.

18 mai

为什么要区分J2EE容器和J2EE应用系统? --讨论

为什么要区分J2EE容器和J2EE应用系统?

  我们知道,J2EE应用系统只有部署在J2EE容器中才能运行,那么为什么划分为J2EE容器和J2EE应用系统? 通过对J2EE容器运行机制的分析,我们可以发现:实际上J2EE容器分离了一般应用系统的一些通用功能,例如事务机制、安全机制以及对象池或线程池等性能优化机制。

  这些功能机制是每个应用系统几乎都需要的,因此可以从具体应用系统中分离出来,形成一个通用的框架平台,而且,这些功能机制的设计开发有一定难度,同时运行的稳定性和快速性都非常重要,必须经过长时间调试和运行经验积累而成,因此,形成了专门的J2EE容器服务器产品,如Tomcat JBoss、Websphere、WebLogic等。

  从J2EE系统划分为J2EE容器和J2EE应用系统两个方面,我们已经看到一种分散关注的思路(separation of concerns)。

  分散关注

  将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。

  AOP就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中。

  AOP是什么?

  AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面编程。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。

  举例:假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个访问类,专门用于在同一时刻访问这同一个数据对象。

  为了完成上述并发访问同一资源的功能,需要引入锁Lock的概念,也就是说,某个时刻,当有一个访问类访问这个数据对象时,这个数据对象必须上锁Locked,用完后就立即解锁unLocked,再供其它访问类访问。

  使用传统的编程习惯,我们会创建一个抽象类,所有的访问类继承这个抽象父类,如下:

abstract class Worker{

  abstract void locked();
  abstract void accessDataObject();
  abstract void unlocked();

}

  缺点:

  accessDataObject()方法需要有“锁”状态之类的相关代码。Java只提供了单继承,因此具体访问类只能继承这个父类,如果具体访问类还要继承其它父类,比如另外一个如Worker的父类,将无法方便实现。重用被打折扣,具体访问类因为也包含“锁”状态之类的相关代码,只能被重用在相关有“锁”的场合,重用范围很窄。

  仔细研究这个应用的“锁”,它其实有下列特性:

  “锁”功能不是具体访问类的首要或主要功能,访问类主要功能是访问数据对象,例如读取数据或更改动作。
  
  “锁”行为其实是和具体访问类的主要功能可以独立、区分开来的。

  “锁”功能其实是这个系统的一个纵向切面,涉及许多类、许多类的方法。如下图:

  因此,一个新的程序结构应该是关注系统的纵向切面,例如这个应用的“锁”功能,这个新的程序结构就是aspect(方面)在这个应用中,“锁”方面(aspect)应该有以下职责:

  提供一些必备的功能,对被访问对象实现加锁或解锁功能。以保证所有在修改数据对象的操作之前能够调用lock()加锁,在它使用完成后,调用unlock()解锁。

  AOP有必要吗?

  当然,上述应用范例在没有使用AOP情况下,也得到了解决,例如JBoss 3.XXX也提供了上述应用功能,但是没有使用AOP。

  但是,使用AOP可以让我们从一个更高的抽象概念来理解软件系统,AOP也许提供一种有价值的工具。可以这么说:因为使用AOP结构,现在JBoss 4.0的源码要比JBoss 3.X容易理解多了,这对于一个大型复杂系统来说是非常重要的。

  从另外一个方面说,好像不是所有的人都需要关心AOP,它可能是一种架构设计的选择,如果选择J2EE系统,AOP关注的上述通用方面都已经被J2EE容器实现了,J2EE应用系统开发者可能需要更多地关注行业应用方面aspect。

  AOP具体实现

  AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),目前AOP具体实现有以下几个项目:

  AspectJ (TM): 创建于Xerox PARC. 有近十年历史,技术成熟。

  缺点:过于复杂;破坏封装;需要专门的Java编译器。

  动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术。

  基于动态代理API的具体项目有:

  JBoss 4.0 JBoss 4.0服务器
  
  基于字节码的项目有:

  aspectwerkz 
  spring

为什么要区分J2EE容器和J2EE应用系统?

我们知道,J2EE应用系
J2EE应用系统? 通过对J2E
们可以发现:实际上J2EE容
以及对象池或线程池等性能
统只有部署在J2EE容器中才能运
E容器运行机制的分析(见我的
器分离了一般应用系统的一些通
优化机制。
行,那么为什么划分为J2EE容器和
电子教材“EJB实用原理”),我
用功能,例如事务机制、安全机制


  这些功能机制是每个应
一个通用的框架平台,而且
性都非常重要,必须经过长
器产品,如Tomcat JBoss、
用系统几乎都需要的,因此可以
,这些功能机制的设计开发有一
时间调试和运行经验积累而成,
Websphere、WebLogic等。
从具体应用系统中分离出来,形成
定难度,同时运行的稳定性和快速
因此,形成了专门的J2EE容器服务


  从J2EE系统划分为J2EE
(separation of concerns
容器和J2EE应用系统两个方面,
)。
我们已经看到一种分散关注的思路


  分散关注                                                                      

  将通用需求功能从不相关类之中分离
为发生变化,不必修改很多类,只要修改
出来;同时,能够使得很多类共享一个行为,一旦行
这个行为就可以。

  AOP就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中。          

  AOP是什么?                                                                

  AOP是OOP的延续,是Aspect Oriente
际是GoF设计模式的延续,设计模式孜孜
以说也是这种目标的一种实现。
d Programming的缩写,意思是面向方面编程。AOP实
不倦追求的是调用者和被调用者之间的解耦,AOP可


  举例:假设有在一个应
数据封装在数据对象中,称
这同一个数据对象。
用系统中,有一个共享的数据必
为Data Class,同时,将有多个

须被并发同时访问,首先,将这个
访问类,专门用于在同一时刻访问


  为了完成上述并发访问同一资源的功
当有一个访问类访问这个数据对象时,这
unLocked,再供其它访问类访问。
能,需要引入锁Lock的概念,也就是说,某个时刻,
个数据对象必须上锁Locked,用完后就立即解锁


  使用传统的编程习惯,我们会创建一个抽象类,所有的访问类继承这个抽象父类,如下:

  abstract class Worker{                                  

  abstract void locked();                                
  abstract void accessDataObject();            
  abstract void unlocked();                            

  缺点:

  accessDataObject()方法需要有“锁”状态之类的相关代码。      
  Java只提供了单继承,
父类,比如另外一个如Work
因此具体访问类只能继承这个父
er的父类,将无法方便实现。
类,如果具体访问类还要继承其它

  重用被打折扣,具体访
“锁”的场合,重用范围很
问类因为也包含“锁”状态之类
窄。
的相关代码,只能被重用在相关有


  仔细研究这个应用的“锁”,它其实有下列特性:                                  
  “锁”功能不是具体访
取数据或更改动作。
问类的首要或主要功能,访问类

主要功能是访问数据对象,例如读

  “锁”行为其实是和具体访问类的主要功能可以独立、区分开来的。                  
  “锁”功能其实是这个系统的一个纵向切面,涉及许多类、许多类的方法。如下图:    
   [[The No.1 Picture.]]

  因此,一个新的程序结构应该是关注
新的程序结构就是aspect(方面)
系统的纵向切面,例如这个应用的“锁”功能,这个


  在这个应用中,“锁”方面(aspect)应该有以下职责:                      

  提供一些必备的功能,对被访问对象
操作之前能够调用lock()加锁,在它使用
实现加锁或解锁功能。以保证所有在修改数据对象的
完成后,调用unlock()解锁。

  AOP应用范围

  很明显,AOP非常适合
开发J2EE容器服务器,目前JBos
s 4.0正是使用AOP框架进行开发。
  具体功能如下:                                                                
  Authentication 权限                                            
  Caching 缓存                                                          
  Context passing 内容传递                                      
  Error handling 错误处理                                        
  Lazy loading 懒加载                                              
  Debugging  调试                                                    
  logging, tracing, pr
ofiling and monitoring 记录
跟踪 优化 校准
  Performance optimization 性能优化                    
  Persistence  持久化                                              
  Resource pooling 资源池                                      
  Synchronization 同步                                          
  Transactions 事务                                                

  AOP有必要吗?

  当然,上述应用范例在没有使用AOP
上述应用功能,但是没有使用AOP。
情况下,也得到了解决,例如JBoss 3.XXX也提供了


  但是,使用AOP可以让
价值的工具。可以这么说:
多了,这对于一个大型复杂
我们从一个更高的抽象概念来理
因为使用AOP结构,现在JBoss 4
系统来说是非常重要的。
解软件系统,AOP也许提供一种有
.0的源码要比JBoss 3.X容易理解


  从另外一个方面说,好
如果选择J2EE系统,AOP关
可能需要更多地关注行业应
像不是所有的人都需要关心AOP
注的上述通用方面都已经被J2EE
用方面aspect。
,它可能是一种架构设计的选择,
容器实现了,J2EE应用系统开发者


  AOP具体实现

  AOP是一个概念,并没有设定具体语
(如Java),目前AOP具体实现有以下几
言的实现,它能克服那些只有单继承特性语言的缺点
个项目:

  AspectJ (TM): 创建于Xerox PARC. 有近十年历史,成熟  
  缺点:过于复杂;破坏封装;需要专门的Java编译器。                          

  动态AOP:使用JDK的动态代理API或字节码Bytecode处理技术。      

  基于动态代理API的具体项目有:                                              
  JBoss 4.0 JBoss 4.0服务器                                  
  nanning 这是以中国
的?
南宁命名的一个项目,搞不清楚

为什么和中国相关?是中国人发起


  基于字节码的项目有:                                                          
  aspectwerkz                                                        
  spring ?                                      
17 mai

强大的eclipse开发插件-Exadel Studio

Exadel Studio

 

http://www.esadel.com


Exadel Studio一个开源,免费,强大的Web应用程序开发工具提供了与MyEclipse一样的功能。它在Eclipse 3.0.x上进行扩展,可以在Windows和Linux下运行。 Exadel Studio具备了很多的功能具体如下:
*支持许多框架的集成开发包括:JSF,Struts(1.2, 1.1),Spring Framework,Hibernate。
*支持Jsp编辑,预览(Struts, JavaServer Faces ,HTML这几种类型的JSP文件)。
*它还包括许多编辑器:
 Graphical Properties editor,
 Graphical TLD editor,
 Graphical web.xml editor,
 CSS editor,
 JavaScript editor,
 Image viewer,
 XSD editor。
*数据库管理器(查看数据库中的数据,执行SQL语句)。
*支持启动/停止/重启/调试Tomcat, JBoss, JRun 和J2EE 1.4 服务器
*内嵌一个WEB浏览器
*支持超过20几种应用程序服务器的部署(使用ANT脚本),包括WebLogic, WebSphere,Resin
还有许多具体的功能可以到其主页查看
 http://www.exadel.com/products_exadelstudio.htm?ssd=ssd

JSF与Exadel Studio

最近一直在研究应用JSF,对JSF有了一点感性认识:
      JSF可以提供像rup开发工具一样所见即所得开发能力,集成了许多组件,直接拖到页面上就可以用了。目前比较有名的JSF标准实现当属Apache的myFaces(个人认为而已),提供了树、日期控件、panel、menue、fileupload 等大量控件。
      JSF具有如下特点:(以下是引用Java视线论坛上的评价
1、客户端网页组件的事件可以直接绑定绑定为服务器端Javabean的方法。
2、网页组件的值可以直接同服务器端JavaBean属性。
3、自动进行状态保存。
4、安全性较高。后台对控件的状态有检查。比如:将控件的Disabled属性同javaBean的一个布尔形变量绑订,当布尔变量为true时,组件在客户端被禁用。这时通过firefox浏览器中的工具更改组件的Disabled属性为发false,向其中输入数据,然后提交网页 ,发现该组件中输入的数据没有被服务器接受。
 
      JSF的开发工具有:
1、webSpher studio 5.1.2提供了JSF图形化托拽式开发
2、eclipse3.1 使用Exadel Studio 3.0插件(下载地址:http://www.exadel.com/)
12 mai

有可能挑战Java优势的四种技术

Java是一种杰出的产业开发语言,这是因为它带来了伟大的统一和对事实上以前并不存在的重要标准的关注。但是和所有语言一样,Java将来也会褪色。依据我做的超越Java的研究,一个重复出现的主题是有越来越多的人相信Java已不再足够的有效率。以下一组技术可以使你更有效率。他们不是对所有的工程都适合,但当被应用于适合的工程时,他们是优秀的工具。

1、动态语言

动态语言可以比像C++或Java这样的静态语言更加有效率。他们可以让你用更少的语言表达更多的意思。这里,我会关注现在新出现最流行的动态语言Ruby。用Ruby的"Hello, World"和Java的作一个比较:

puts "Hello, world."


这显然既简单又明了。你不需要写一些其他的代码去做这件事。下面是用Java语言的描述:

class HelloWorld
{  
public static void main(String[] args) 
{    
System.out.println("Hello World!")  
}
}


在Java中,类型是静态的。这就代表了编译器要检查所有的类型;你必须建立和编译一个完整的程序。在Ruby中,类型是动态的,所以你不需要去声明他们,你可以马上编写和运行他们。下面是用两种语言描述的Fibonacci 序列:

First, Ruby:x1, x2 = 0, 1                    
//110.times do  puts x2   x1, 
x2 = x2, x1+x2         
//4end


注意到在第一、四行同时声明两个变量,让你可以简洁地表达两种不同的声明形式。另外,注意到10是一个对象,它支持方法,如:times。再另外,在do和end之间是一个代码块。Ruby代码块可以让你把代码块传入方法。。这种技术导致了难以置信的效率和简洁的代码。接下来,看一看用Java实现的代码:

class Fib 
{  
public static void main (String args[]) 
{    
int x1 = 0;    
int x2 = 1;    
int total = 1;    
for (int i=0; i<10; i++) 
{      
System.out.println(total);      
total = x1+x2;      
x1 = x2;     
x2 = total;   
}   
}
}


你需要去声明所有的变量,以及详细地写出来你用for循环实现的迭代。每个变量都是独立的,所以你必须有一个临时变量用于存放total。 相比,动态语言更为简洁。按照一个普通的规则,如果你可以写更少的代码而不牺牲可读性,这些代码将导致更高的效率。(但是你不可以牺牲可读性来达到这一步,我们可以以Perl举例作为结尾。) 更为重要的是,动态语言在Java开发者想要去解决的重要问题上表现得更好,如: 元编程。 Hibernate 使用元编程技术使对象持久化。 Spring使用元编程来为Java对象增加服务,而免除你为他们构建额外支持的烦恼。在Rails框架上,当红的Ruby利用了自己能力来构建某种已存的 最有效率的应用开发框架。 令人惊讶的是,许多Java开发者采用了Ruby。Ant和Tomcat的发明者James Duncan Davidson正在Rails上使用Ruby,以及Java的畅销书作者之一,JSP专家组的 成员David Geary正在写一本关于Rails上的Rub的书y。许多在Java社区里有着聪明思想的人都转向使用像Ruby一样的动态语言。这是因为这种新出现的语言能更好的解决最有兴趣的问题。动态语言将不会完全取代Java,但是他们会适合于解决小的,轻量级的问题。 2、Continuation 服务 Web编程绝对是个的灾难。在Java诞生十年后,我们仍旧不能构建一个使返回按钮正确的框架。Web应用是无国界的,所以Web应用会发展得更好。但是很难去构建无国界的应用,而我们现有的框架不能给与我们足够的帮助。 你使用大多数Java的Web框架时,从根本上说,你构建了许多不相关的使用servlets或JSP技术的应用。然后通过手工保存对象来把他们集成起来,这些对象就是你需要的,用来暂时存储对象的会话。 Continuation是语言的构造器,它可以使你快速存储某个线程的状态,过后执行这个线程。基于Continuation的web框架总体上是通过模拟一个监控状态的应用来使web开发变得更为容易。当你的应用需要从用户那取得数据时,这种框架使用continuation来自动保存应用程序的状态。如果用户按下返回按钮或者通过浏览器的历史纪录回到以前的页面,应用程序可以重新读取一个continuation。 基于continuation最好的框架是用动态语言来开发的。到目前为止,最健壮的框架是Seaside。他是基于一种Smalltalk的Squeak语言的框架。Seaside支持很好的调试功能,你可以实时检查、调试以及在浏览器里改动你的代码。Borges、Iowa和Wee都是基于Ruby且支持continuation的框架。 Java不支持continuations,但是一些在特殊限制下构造的Java框架支持模拟continuations。这些框架具有用其他语言编写的continuations框架的某些特征。 流行的框架是用了一些高级的技术,如:字节码增强、反射以及特殊类的装载器。这些技术用Java部分地实现了continuations。 Cocoon 2在Rhino JavaScript 虚拟机中增加了continuations,用来模拟监控状态的应用。 Spring Webflow使用了状态机来提供对返回按钮良好的支持,以及其他一些continuation服务的特征。 Lakeshore使用了悬挂的线程来模拟continuations。这种方法不像其他方法一样有可扩展性,同时还缺乏对返回按钮完整的支持,但是这些预计在将来 都会具有。 每个月都会有新的框架出现。我认为在未来的三年内,我们都会使用支持基于continuations的方法的web开发框架,这种框架是由一种语言或者其他语言编写的。 3、惯例超越配置

Java开发者经常探索用于改进配置的方法。新的框架越来越多的使用Java 5批注来进行配置。其他的框架是用一种不同的方法。Rails中的Ruby常用惯例来推断需要在其他框架进行配置的联系。

例如:在结束的时候,一个叫BlogController且有一个show方法的Rails控制器,会自动在blog_controller目录里提交一个叫show.rhtml的视图。Rails还使用命名惯例来绑定数据库表里持久化的类。默认情况下,Perosn类会与用英语的复数people与表进行匹配。

新的框架将会支持惯例,而不是配置。

4、元编程

就像前面提到的一样,在Rails编程框架上的Ruby里,存在着许多hype的。我认为这种hype是正确的。在Rails上的Ruby让你比起java,可以在一个更抽象的层次上编写你的程序。有了Rails上的Ruby,你可以创建域对象,这种对象可以发现相关联的数据库表的内容。例如:你可以写这样简单的模型对象:

class Person < ActiveRecord::Baseend


这种类表面看起来相当的受限制。但是一旦你执行它,Rails就会展现它的神奇。这种实现了持久化Rails的活动纪录框架与相关的数据库关联,以及为了表定义扫描系统表,还发现数据库里列项。然后,活动记录为数据库中的每一列增加一个属性,为数据库中id列名在类中增加一个独一无二的标示符。你可以用下面的类去编写代码:

person=Person.newperson.name='Bruce
Tate'person.email=
'bruce.tate@j2life.nospam.com'person.save


数据库的列名和行为都会在运行时后加入Person类。你可以很容易的扩展Person类:

class Person < ActiveRecord::Base 
has_many :carsend


通过Ruby中一个belongs_to的简单方法和:department标示符,我实现了所有我想做的。活动记录隐式调用了Ruby的元编程来添加了所有的方法和变量,这些方法和变量用来管理一个任何一个部门之间的一对多关系。Rails用户使用域语言来管理像继承这样的关系,另外可以用Ruby语言在一个更抽象的层次上工作。Rails无缝扩展了Ruby语言。

Rails会不会是下一代伟大的框架?有可能。要做一个选择的话,Rails应该会是在使用Ruby或是其他动态编程语言的元编程框架潮流中的第一个。或者,你可能看到Rails会作为某些松散对齐技术的中枢,它是以元编程作为基础的。在任何情况下,你都会更有效率。

总结

在《超越Java》这本书中,我表达了Java还不会淘汰意思,但是在最近的十年,我们目睹了在Java领域之外引人注目的创新。这四种技术会在不远的将来起到重要的作用。请密切关注他们。

Struts+Spring+Hibernate上传下载 --之四

Web层实现

  1、Web层的构件和交互流程

  Web层包括主要3个功能:

  ·上传文件。

  ·列出所有已经上传的文件列表,以供点击下载。

  ·下载文件。

  Web层实现构件包括与2个JSP页面,1个ActionForm及一个Action:

  ·file-upload.jsp:上传文件的页面。

  ·file-list.jsp:已经上传文件的列表页面。

  ·FileActionForm:file-upload.jsp页面表单对应的ActionForm。

  ·FileAction:继承org.apache.struts.actions.DispatchAction的Action,这样这个Action就可以通过一个URL参数区分中响应不同的请求。

  Web层的这些构件的交互流程如图 6所示:


图 6 Web层Struts流程图


  其中,在执行文件上传的请求时,FileAction在执行文件上传后,forward到loadAllFile出口中,loadAllFile加载数据库中所有已经上传的记录,然后forward到名为fileListPage的出口中,调用file-list.jsp页面显示已经上传的记录。

  2、FileAction功能

  Struts 1.0的Action有一个弱项:一个Action只能处理一种请求,Struts 1.1中引入了一个DispatchAction,允许通过URL参数指定调用Action中的某个方法,如http://yourwebsite/fileAction.do?method=upload即调用FileAction中的upload方法。通过这种方式,我们就可以将一些相关的请求集中到一个Action当中编写,而没有必要为某个请求操作编写一个Action类。但是参数名是要在struts-config.xml中配置的:

1. <struts-config>
2. <form-beans>
3. <form-bean name="fileActionForm" type="sshfile.web.FileActionForm" />
4. </form-beans>
5. <action-mappings>
6. <action name="fileActionForm" parameter="method" path="/fileAction"
7. type="sshfile.web.FileAction">
8. <forward name="fileListPage" path="/file-list.jsp" />
9. <forward name="loadAllFile" path="/fileAction.do?method=listAllFile" />
10. </action>
11. </action-mappings>
12. </struts-config>


  第6行的parameter="method"指定了承载方法名的参数,第9行中,我们还配置了一个调用FileAction不同方法的Action出口。

  FileAction共有3个请求响应的方法,它们分别是:

  ·upload(…):处理上传文件的请求。

  ·listAllFile(…):处理加载数据库表中所有记录的请求。

  ·download(…):处理下载文件的请求。

  下面我们分别对这3个请求处理方法进行讲解。

  2.1 上传文件

  上传文件的请求处理方法非常简单,简之言之,就是从Spring容器中获取业务层处理类FileService,调用其save(FileActionForm form)方法上传文件,如下所示:

1. public class FileAction
2. extends DispatchAction
3. {
4. //将上传文件保存到数据库中
5. public ActionForward upload(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. {
9. FileActionForm fileForm = (FileActionForm) form;
10. FileService fileService = getFileService();
11. fileService.save(fileForm);
12. return mapping.findForward("loadAllFile");
13. }
14. //从Spring容器中获取FileService对象
15. private FileService getFileService()
16. {
17. ApplicationContext appContext = WebApplicationContextUtils.
18. getWebApplicationContext(this.getServlet().getServletContext());
19. return (FileService) appContext.getBean("fileService");
20. }
21. …
22. }


  由于FileAction其它两个请求处理方法也需要从Spring容器中获取FileService实例,所以我们特别提供了一个getFileService()方法(第15~21行)。重构的一条原则就是:"发现代码中有重复的表达式,将其提取为一个变量;发现类中有重复的代码段,将其提取为一个方法;发现不同类中有相同的方法,将其提取为一个类"。在真实的系统中,往往拥有多个Action和多个Service类,这时一个比较好的设置思路是,提供一个获取所有Service实现对象的工具类,这样就可以将Spring 的Service配置信息屏蔽在一个类中,否则Service的配置名字散落在程序各处,维护性是很差的。

  2.2 列出所有已经上传的文件

  listAllFile方法调用Servie层方法加载T_FILE表中所有记录,并将其保存在Request域中,然后forward到列表页面中:

1. public class FileAction
2. extends DispatchAction
3. {
4. …
5. public ActionForward listAllFile(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. throws ModuleException
9. {
10. FileService fileService = getFileService();
11. List fileList = fileService.getAllFile();
12. request.setAttribute("fileList",fileList);
13. return mapping.findForward("fileListPage");
14. }
15. }


  file-list.jsp页面使用Struts标签展示出保存在Request域中的记录:

1. <%@page contentType="text/html; charset=GBK"%>
2. <%@taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%>
3. <%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
4. <html>
5. <head>
6. <title>file-download</title>
7. </head>
8. <body bgcolor="#ffffff">
9. <ol>
10. <logic:iterate id="item" name="fileList" scope="request">
11. <li>
12. <a href='fileAction.do?method=download&fileId=
13. <bean:write name="item"property="fileId"/>'>
14. <bean:write name="item" property="fileName"/>
15. </a>
16. </li>
17. </logic:iterate>
18. </ol>
19. </body>
20. </html>


  展现页面的每条记录挂接着一个链接地址,形如:fileAction.do?method=download&fileId=xxx,method参数指定了这个请求由FileAction的download方法来响应,fileId指定了记录的主键。

  由于在FileActionForm中,我们定义了fileId的属性,所以在download响应方法中,我们将可以从FileActionForm中取得fileId的值。这里涉及到一个处理多个请求Action所对应的ActionForm的设计问题,由于原来的Action只能对应一个请求,那么原来的ActionForm非常简单,它仅需要将这个请求的参数项作为其属性就可以了,但现在一个Action对应多个请求,每个请求所对应的参数项是不一样的,此时的ActionForm的属性就必须是多请求参数项的并集了。所以,除了文件上传请求所对应的fileContent和remark属性外还包括文件下载的fileId属性:


图 7 FileActionForm


  当然这样会造成属性的冗余,比如在文件上传的请求中,只会用到fileContent和remark属性,而在文件下载的请求时,只会使用到fileId属性。但这种冗余是会带来好处的--它使得一个Action可以处理多个请求。

  2.3 下载文件

  在列表页面中点击一个文件下载,其请求由FileAction的download方法来响应,download方法调用业务层的FileService方法,获取文件数据并写出到response的响应流中。通过合理设置HTTP响应头参数,将响应流在客户端表现为一个下载文件对话框,其代码如下所示:

  代码 10 业务接口实现类之download

1. public class FileAction
2. extends DispatchAction
3. {
4. …
5. public ActionForward download(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. throws ModuleException
9. {
10. FileActionForm fileForm = (FileActionForm) form;
11. FileService fileService = getFileService();
12. String fileName = fileService.getFileName(fileForm.getFileId());
13. try
14. {
15. response.setContentType("application/x-msdownload");
16. response.setHeader("Content-Disposition",
17. "attachment;" + " filename="+
18. new String(fileName.getBytes(), "ISO-8859-1"));
19. fileService.write(response.getOutputStream(), fileForm.getFileId());
20. }
21. catch (Exception e)
22. {
23. throw new ModuleException(e.getMessage());
24. }
25. return null;
26. }
27. }


  第15~18行,设置HTTP响应头,将响应类型设置为application/x-msdownload MIME类型,则响应流在IE中将弹出一个文件下载的对话框,如图 4所示。IE所支持的MIME类型多达26种,您可以通过这个网址查看其他的MIME类型:

http://msdn.microsoft.com/workshop/networking/moniker/overview/appendix_a.asp。

  如果下载文件的文件名含有中文字符,如果不对其进行硬编码,如第18行所示,客户文件下载对话框中出现的文件名将会发生乱码。
第19行代码获得response的输出流,作为FileServie write(OutputStream os,String fileId)的入参,这样文件的内容将写到response的输出流中。

  3、web.xml文件的配置

  Spring容器在何时启动呢?我可以在Web容器初始化来执行启动Spring容器的操作,Spring提供了两种方式启动的方法:

  ·通过org.springframework.web.context .ContextLoaderListener容器监听器,在Web容器初始化时触发初始化Spring容器,在web.xml中通过<listener></listener>对其进行配置。

  ·通过Servlet org.springframework.web.context.ContextLoaderServlet,将其配置为自动启动的Servlet,在Web容器初始化时,通过这个Servlet启动Spring容器。

  在初始化Spring容器之前,必须先初始化log4J的引擎,Spring也提供了容器监听器和自动启动Servlet两种方式对log4J引擎进行初始化:

  ·org.springframework.web.util .Log4jConfigListener

  ·org.springframework.web.util.Log4jConfigServlet

  下面我们来说明如何配置web.xml启动Spring容器:

  代码 11 web.xml中对应Spring的配置内容

1. <web-app>
2. <context-param>
3. <param-name>contextConfigLocation</param-name>
4. <param-value>/WEB-INF/applicationContext.xml</param-value>
5. </context-param>
6. <context-param>
7. <param-name>log4jConfigLocation</param-name>
8. <param-value>/WEB-INF/log4j.properties</param-value>
9. </context-param>
10. <servlet>
11. <servlet-name>log4jInitServlet</servlet-name>
12. <servlet-class>org.springframework.web.util.Log4jConfigServlet</servlet-class>
13. <load-on-startup>1</load-on-startup>
14. </servlet>
15. <servlet>
16. <servlet-name>springInitServlet</servlet-name>
17. <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
18. <load-on-startup>2</load-on-startup>
19. </servlet>
20. …
21. </web-app>


  启动Spring容器时,需要得到两个信息:Spring配置文件的地址和Log4J属性文件,这两上信息分别通过contextConfigLocationWeb和log4jConfigLocation容器参数指定,如果有多个Spring配置文件,则用逗号隔开,如:

/WEB-INF/applicationContext_1.xml, /WEB-INF/applicationContext_1.xm2

  由于在启动ContextLoaderServlet之前,必须事先初始化Log4J的引擎,所以Log4jConfigServlet必须在ContextLoaderServlet之前启动,这通过<load-on-startup>来指定它们启动的先后顺序。

  乱码是开发Web应用程序一个比较老套又常见问题,由于不同Web应用服务器的默认编码是不一样的,为了方便Web应用在不同的Web应用服务器上移植,最好的做法是Web程序自身来处理编码转换的工作。经典的作法是在web.xml中配置一个编码转换过滤器,Spring就提供了一个编码过滤器类CharacterEncodingFilter,下面,我们为应用配置上这个过滤器:

1. <web-app>
2. …
3. <filter>
4. <filter-name>encodingFilter</filter-name>
5. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
6. <init-param>
7. <param-name>encoding</param-name>
8. <param-value>GBK</param-value>
9. </init-param>
10. </filter>
11. <filter-mapping>
12. <filter-name>encodingFilter</filter-name>
13. <url-pattern>/*</url-pattern>
14. </filter-mapping>
15. …
16. </web-app>


  Spring的过滤器类是org.springframework.web.filter.CharacterEncodingFilter,通过encoding参数指定编码转换类型为GBK,<filter-mapping>的配置使该过滤器截获所有的请示。

  Struts的框架也需要在web.xml中配置,想必读者朋友对Struts的配置都很熟悉,故在此不再提及,请参见本文所提供的源码。

  总结

  本文通过一个文件上传下载的Web应用,讲解了如何构建基于SSH的Web应用,通过Struts和FormFile,Spring的LobHandler以及Spring为HibernateBlob处理所提供的用户类BlobByteArrayType ,实现上传和下载文件的功能仅需要廖廖数行的代码即告完成。读者只需对程序作稍许的调整,即可处理Clob字段:

  ·领域对象对应Clob字段的属性声明为String类型;

  ·映射文件对应Clob字段的属性声明为org.springframework.orm.hibernate3.support.ClobStringType类型。

  本文通过SSH对文件上传下载简捷完美的实现得以管中窥豹了解SSH强强联合构建Web应用的强大优势。在行文中,还穿插了一些分层的设计经验,配置技巧和Spring所提供的方便类,相信这些知识对您的开发都有所裨益。

Web层实现

  1、Web层的构件和交互流程

  Web层包括主要3个功能:

  ·上传文件。

  ·列出所有已经上传的文件列表,以供点击下载。

  ·下载文件。

  Web层实现构件包括与2个JSP页面,1个ActionForm及一个Action:

  ·file-upload.jsp:上传文件的页面。

  ·file-list.jsp:已经上传文件的列表页面。

  ·FileActionForm:file-upload.jsp页面表单对应的ActionForm。

  ·FileAction:继承org.apache.struts.actions.DispatchAction的Action,这样这个Action就可以通过一个URL参数区分中响应不同的请求。

  Web层的这些构件的交互流程如图 6所示:


图 6 Web层Struts流程图

  其中,在执行文件上传的请求时,FileAction在执行文件上传后,forward到loadAllFile出口中,loadAllFile加载数据库中所有已经上传的记录,然后forward到名为fileListPage的出口中,调用file-list.jsp页面显示已经上传的记录。

  2、FileAction功能

  Struts 1.0的Action有一个弱项:一个Action只能处理一种请求,Struts 1.1中引入了一个DispatchAction,允许通过URL参数指定调用Action中的某个方法,如http://yourwebsite/fileAction.do?method=upload即调用FileAction中的upload方法。通过这种方式,我们就可以将一些相关的请求集中到一个Action当中编写,而没有必要为某个请求操作编写一个Action类。但是参数名是要在struts-config.xml中配置的:

1. <struts-config>
2. <form-beans>
3. <form-bean name="fileActionForm" type="sshfile.web.FileActionForm" />
4. </form-beans>
5. <action-mappings>
6. <action name="fileActionForm" parameter="method" path="/fileAction"
7. type="sshfile.web.FileAction">
8. <forward name="fileListPage" path="/file-list.jsp" />
9. <forward name="loadAllFile" path="/fileAction.do?method=listAllFile" />
10. </action>
11. </action-mappings>
12. </struts-config>

  第6行的parameter="method"指定了承载方法名的参数,第9行中,我们还配置了一个调用FileAction不同方法的Action出口。

  FileAction共有3个请求响应的方法,它们分别是:

  ·upload(…):处理上传文件的请求。

  ·listAllFile(…):处理加载数据库表中所有记录的请求。

  ·download(…):处理下载文件的请求。

  下面我们分别对这3个请求处理方法进行讲解。

  2.1 上传文件

  上传文件的请求处理方法非常简单,简之言之,就是从Spring容器中获取业务层处理类FileService,调用其save(FileActionForm form)方法上传文件,如下所示:

1. public class FileAction
2. extends DispatchAction
3. {
4. //将上传文件保存到数据库中
5. public ActionForward upload(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. {
9. FileActionForm fileForm = (FileActionForm) form;
10. FileService fileService = getFileService();
11. fileService.save(fileForm);
12. return mapping.findForward("loadAllFile");
13. }
14. //从Spring容器中获取FileService对象
15. private FileService getFileService()
16. {
17. ApplicationContext appContext = WebApplicationContextUtils.
18. getWebApplicationContext(this.getServlet().getServletContext());
19. return (FileService) appContext.getBean("fileService");
20. }
21. …
22. }

  由于FileAction其它两个请求处理方法也需要从Spring容器中获取FileService实例,所以我们特别提供了一个getFileService()方法(第15~21行)。重构的一条原则就是:"发现代码中有重复的表达式,将其提取为一个变量;发现类中有重复的代码段,将其提取为一个方法;发现不同类中有相同的方法,将其提取为一个类"。在真实的系统中,往往拥有多个Action和多个Service类,这时一个比较好的设置思路是,提供一个获取所有Service实现对象的工具类,这样就可以将Spring 的Service配置信息屏蔽在一个类中,否则Service的配置名字散落在程序各处,维护性是很差的。

  2.2 列出所有已经上传的文件

  listAllFile方法调用Servie层方法加载T_FILE表中所有记录,并将其保存在Request域中,然后forward到列表页面中:

1. public class FileAction
2. extends DispatchAction
3. {
4. …
5. public ActionForward listAllFile(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. throws ModuleException
9. {
10. FileService fileService = getFileService();
11. List fileList = fileService.getAllFile();
12. request.setAttribute("fileList",fileList);
13. return mapping.findForward("fileListPage");
14. }
15. }

  file-list.jsp页面使用Struts标签展示出保存在Request域中的记录:

1. <%@page contentType="text/html; charset=GBK"%>
2. <%@taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%>
3. <%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
4. <html>
5. <head>
6. <title>file-download</title>
7. </head>
8. <body bgcolor="#ffffff">
9. <ol>
10. <logic:iterate id="item" name="fileList" scope="request">
11. <li>
12. <a href='fileAction.do?method=download&fileId=
13. <bean:write name="item"property="fileId"/>'>
14. <bean:write name="item" property="fileName"/>
15. </a>
16. </li>
17. </logic:iterate>
18. </ol>
19. </body>
20. </html>

  展现页面的每条记录挂接着一个链接地址,形如:fileAction.do?method=download&fileId=xxx,method参数指定了这个请求由FileAction的download方法来响应,fileId指定了记录的主键。

  由于在FileActionForm中,我们定义了fileId的属性,所以在download响应方法中,我们将可以从FileActionForm中取得fileId的值。这里涉及到一个处理多个请求Action所对应的ActionForm的设计问题,由于原来的Action只能对应一个请求,那么原来的ActionForm非常简单,它仅需要将这个请求的参数项作为其属性就可以了,但现在一个Action对应多个请求,每个请求所对应的参数项是不一样的,此时的ActionForm的属性就必须是多请求参数项的并集了。所以,除了文件上传请求所对应的fileContent和remark属性外还包括文件下载的fileId属性:


图 7 FileActionForm

  当然这样会造成属性的冗余,比如在文件上传的请求中,只会用到fileContent和remark属性,而在文件下载的请求时,只会使用到fileId属性。但这种冗余是会带来好处的--它使得一个Action可以处理多个请求。

  2.3 下载文件

  在列表页面中点击一个文件下载,其请求由FileAction的download方法来响应,download方法调用业务层的FileService方法,获取文件数据并写出到response的响应流中。通过合理设置HTTP响应头参数,将响应流在客户端表现为一个下载文件对话框,其代码如下所示:

  代码 10 业务接口实现类之download

1. public class FileAction
2. extends DispatchAction
3. {
4. …
5. public ActionForward download(ActionMapping mapping, ActionForm form,
6. HttpServletRequest request,
7. HttpServletResponse response)
8. throws ModuleException
9. {
10. FileActionForm fileForm = (FileActionForm) form;
11. FileService fileService = getFileService();
12. String fileName = fileService.getFileName(fileForm.getFileId());
13. try
14. {
15. response.setContentType("application/x-msdownload");
16. response.setHeader("Content-Disposition",
17. "attachment;" + " filename="+
18. new String(fileName.getBytes(), "ISO-8859-1"));
19. fileService.write(response.getOutputStream(), fileForm.getFileId());
20. }
21. catch (Exception e)
22. {
23. throw new ModuleException(e.getMessage());
24. }
25. return null;
26. }
27. }

  第15~18行,设置HTTP响应头,将响应类型设置为application/x-msdownload MIME类型,则响应流在IE中将弹出一个文件下载的对话框,如图 4所示。IE所支持的MIME类型多达26种,您可以通过这个网址查看其他的MIME类型:

http://msdn.microsoft.com/workshop/networking/moniker/overview/appendix_a.asp。

  如果下载文件的文件名含有中文字符,如果不对其进行硬编码,如第18行所示,客户文件下载对话框中出现的文件名将会发生乱码。
第19行代码获得response的输出流,作为FileServie write(OutputStream os,String fileId)的入参,这样文件的内容将写到response的输出流中。

  3、web.xml文件的配置

  Spring容器在何时启动呢?我可以在Web容器初始化来执行启动Spring容器的操作,Spring提供了两种方式启动的方法:

  ·通过org.springframework.web.context .ContextLoaderListener容器监听器,在Web容器初始化时触发初始化Spring容器,在web.xml中通过<listener></listener>对其进行配置。

  ·通过Servlet org.springframework.web.context.ContextLoaderServlet,将其配置为自动启动的Servlet,在Web容器初始化时,通过这个Servlet启动Spring容器。

  在初始化Spring容器之前,必须先初始化log4J的引擎,Spring也提供了容器监听器和自动启动Servlet两种方式对log4J引擎进行初始化:

  ·org.springframework.web.util .Log4jConfigListener

  ·org.springframework.web.util.Log4jConfigServlet

  下面我们来说明如何配置web.xml启动Spring容器:

  代码 11 web.xml中对应Spring的配置内容

1. <web-app>
2. <context-param>
3. <param-name>contextConfigLocation</param-name>
4. <param-value>/WEB-INF/applicationContext.xml</param-value>
5. </context-param>
6. <context-param>
7. <param-name>log4jConfigLocation</param-name>
8. <param-value>/WEB-INF/log4j.properties</param-value>
9. </context-param>
10. <servlet>
11. <servlet-name>log4jInitServlet</servlet-name>
12. <servlet-class>org.springframework.web.util.Log4jConfigServlet</servlet-class>
13. <load-on-startup>1</load-on-startup>
14. </servlet>
15. <servlet>
16. <servlet-name>springInitServlet</servlet-name>
17. <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
18. <load-on-startup>2</load-on-startup>
19. </servlet>
20. …
21. </web-app>

  启动Spring容器时,需要得到两个信息:Spring配置文件的地址和Log4J属性文件,这两上信息分别通过contextConfigLocationWeb和log4jConfigLocation容器参数指定,如果有多个Spring配置文件,则用逗号隔开,如:

/WEB-INF/applicationContext_1.xml, /WEB-INF/applicationContext_1.xm2

  由于在启动ContextLoaderServlet之前,必须事先初始化Log4J的引擎,所以Log4jConfigServlet必须在ContextLoaderServlet之前启动,这通过<load-on-startup>来指定它们启动的先后顺序。

  乱码是开发Web应用程序一个比较老套又常见问题,由于不同Web应用服务器的默认编码是不一样的,为了方便Web应用在不同的Web应用服务器上移植,最好的做法是Web程序自身来处理编码转换的工作。经典的作法是在web.xml中配置一个编码转换过滤器,Spring就提供了一个编码过滤器类CharacterEncodingFilter,下面,我们为应用配置上这个过滤器:

1. <web-app>
2. …
3. <filter>
4. <filter-name>encodingFilter</filter-name>
5. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
6. <init-param>
7. <param-name>encoding</param-name>
8. <param-value>GBK</param-value>
9. </init-param>
10. </filter>
11. <filter-mapping>
12. <filter-name>encodingFilter</filter-name>
13. <url-pattern>/*</url-pattern>
14. </filter-mapping>
15. …
16. </web-app>

  Spring的过滤器类是org.springframework.web.filter.CharacterEncodingFilter,通过encoding参数指定编码转换类型为GBK,<filter-mapping>的配置使该过滤器截获所有的请示。

  Struts的框架也需要在web.xml中配置,想必读者朋友对Struts的配置都很熟悉,故在此不再提及,请参见本文所提供的源码。

  总结

  本文通过一个文件上传下载的Web应用,讲解了如何构建基于SSH的Web应用,通过Struts和FormFile,Spring的LobHandler以及Spring为HibernateBlob处理所提供的用户类BlobByteArrayType ,实现上传和下载文件的功能仅需要廖廖数行的代码即告完成。读者只需对程序作稍许的调整,即可处理Clob字段:

  ·领域对象对应Clob字段的属性声明为String类型;

  ·映射文件对应Clob字段的属性声明为org.springframework.orm.hibernate3.support.ClobStringType类型。

  本文通过SSH对文件上传下载简捷完美的实现得以管中窥豹了解SSH强强联合构建Web应用的强大优势。在行文中,还穿插了一些分层的设计经验,配置技巧和Spring所提供的方便类,相信这些知识对您的开发都有所裨益。

 

Struts+Spring+Hibernate上传下载--之三

业务层

  1、业务层接口

  "面向接口而非面向类编程"是Spring不遗余力所推荐的编程原则,这条原则也已经为大部开发者所接受;此外,JDK的动态代理只对接口有效,否则必须使用CGLIB生成目标类的子类。我们依从于Spring的倡导为业务类定义一个接口:

  代码 7 业务层操作接口

1. public interface FileService
2. {
3. void save(FileActionForm fileForm);//将提交的上传文件保存到数据表中
4. List getAllFile();//得到T_FILE所示记录
5. void write(OutputStream os,String fileId);//将某个文件的文件数据写出到输出流中
6. String getFileName(String fileId);//获取文件名
7. }


  其中save(FileActionForm fileForm)方法,将封装在fileForm中的上传文件保存到数据库中,这里我们使用FileActionForm作为方法入参,FileActionForm是Web层的表单数据对象,它封装了提交表单的数据。将FileActionForm直接作为业务层的接口入参,相当于将Web层传播到业务层中去,即将业务层绑定在特定的Web层实现技术中,按照分层模型学院派的观点,这是一种反模块化的设计,但在"一般"的业务系统并无需提供多种UI界面,系统Web层将来切换到另一种实现技术的可能性也微乎其微,所以笔者觉得没有必要为了这个业务层完全独立于调用层的过高目标而去搞一个额外的隔离层,浪费了原材料不说,还将系统搞得过于复杂,相比于其它原则,"简单"始终是最大的一条原则。

  getAllFile()负责获取T_FILE表所有记录,以便在网页上显示出来。

  而getFileName(String fileId)和write(OutputStream os,String fileId)则用于下载某个特定的文件。具体的调用是将Web层将response.getOutputStream()传给write(OutputStream os,String fileId)接口,业务层直接将文件数据输出到这个响应流中。具体实现请参见错误!未找到引用源。节下载文件部分。

  2、业务层接口实现类

  FileService的实现类为FileServiceImpl,其中save(FileActionForm fileForm)的实现如下所示:

  代码 8 业务接口实现类之save()

1. …
2. public class FileServiceImpl
3. implements FileService
4. {
5. private TfileDAO tfileDAO;
6. public void save(FileActionForm fileForm)
7. {
8. Tfile tfile = new Tfile();
9. try
10. {
11. tfile.setFileContent(fileForm.getFileContent().getFileData());
12. }
13. catch (FileNotFoundException ex)
14. {
15. throw new RuntimeException(ex);
16. }
17. catch (IOException ex)
18. {
19. throw new RuntimeException(ex);
20. }
21. tfile.setFileName(fileForm.getFileContent().getFileName());
22. tfile.setRemark(fileForm.getRemark());
23. tfileDAO.save(tfile);
24. }
25. …
26. }


  在save(FileActionForm fileForm)方法里,完成两个步骤:

  其一,象在水桶间倒水一样,将FileActionForm对象中的数据倒入到Tfile对象中;

  其二,调用TfileDAO保存数据。

  需要特别注意的是代码的第11行,FileActionForm的fileContent属性为org.apache.struts.upload.FormFile类型,FormFile提供了一个方便的方法getFileData(),即可获取文件的二进制数据。通过解读FormFile接口实现类DiskFile的原码,我们可能知道FormFile本身并不缓存文件的数据,只有实际调用getFileData()时,才从磁盘文件输入流中获取数据。由于FormFile使用流读取方式获取数据,本身没有缓存文件的所有数据,所以对于上传超大体积的文件,也是没有问题的;但是,由于数据持久层的Tfile使用byte[]来缓存文件的数据,所以并不适合处理超大体积的文件(如100M),对于超大体积的文件,依然需要使用java.sql.Blob类型以常规流操作的方式来处理。

  此外,通过FileForm的getFileName()方法就可以获得上传文件的文件名,如第21行代码所示。

  write(OutputStream os,String fileId)方法的实现,如代码 9所示:

  代码 9 业务接口实现类之write()

1. …
2. public class FileServiceImpl
3. implements FileService
4. {
5.
6. public void write(OutputStream os, String fileId)
7. {
8. Tfile tfile = tfileDAO.findByFildId(fileId);
9. try
10. {
11. os.write(tfile.getFileContent());
12. os.flush();
13. }
14. catch (IOException ex)
15. {
16. throw new RuntimeException(ex);
17. }
18. }
19. …
20. }


  write(OutputStream os,String fileId)也简单地分为两个操作步骤,首先,根据fileId加载表记录,然后将fileContent写入到输出流中。

  3、Spring事务配置

  下面,我们来看如何在Spring配置文件中为FileService配置声明性的事务

1. <beans>
2. …
3. <bean id="transactionManager"
4. class="org.springframework.orm.hibernate3.HibernateTransactionManager">
5. <property name="sessionFactory" ref="sessionFactory"/>
6. </bean>
7. <!-- 事务处理的AOP配置 //-->
8. <bean id="txProxyTemplate" abstract="true"
9. class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
10. <property name="transactionManager" ref="transactionManager"/>
11. <property name="transactionAttributes">
12. <props>
13. <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
14. <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
15. <prop key="save">PROPAGATION_REQUIRED</prop>
16. <prop key="write">PROPAGATION_REQUIRED,readOnly</prop>
17. </props>
18. </property>
19. </bean>
20. <bean id="fileService" parent="txProxyTemplate">
21. <property name="target">
22. <bean class="sshfile.service.FileServiceImpl">
23. <property name="tfileDAO" ref="tfileDAO"/>
24. </bean>
25. </property>
26. </bean>
27. </beans>


  Spring的事务配置包括两个部分:

  其一,定义事务管理器transactionManager,使用HibernateTransactionManager实现事务管理;

  其二,对各个业务接口进行定义,其实txProxyTemplate和fileService是父子节点的关系,本来可以将txProxyTemplate定义的内容合并到fileService中一起定义,由于我们的系统仅有一个业务接口需要定义,所以将其定义的一部分抽象到父节点txProxyTemplate中意义确实不大,但是对于真实的系统,往往拥有为数众多的业务接口需要定义,将这些业务接口定义内容的共同部分抽取到一个父节点中,然后在子节点中通过parent进行关联,就可以大大简化业务接口的配置了。

  父节点txProxyTemplate注入了事务管理器,此外还定义了业务接口事务管理的方法(允许通过通配符的方式进行匹配声明,如前两个接口方法),有些接口方法仅对数据进行读操作,而另一些接口方法需要涉及到数据的更改。对于前者,可以通过readOnly标识出来,这样有利于操作性能的提高,需要注意的是由于父类节点定义的Bean仅是子节点配置信息的抽象,并不能具体实现化一个Bean对象,所以需要特别标注为abstract="true",如第8行所示。

  fileService作为一个目标类被注入到事务代理器中,而fileService实现类所需要的tfileDAO实例,通过引用3.2节中定义的tfileDAO Bean注入。

Struts+Spring+Hibernate上传下载 --之二

数据持久层

  1、领域对象及映射文件

  您可以使用Hibernate Middlegen、HIbernate Tools、Hibernate Syhchronizer等工具或手工的方式,编写Hibernate的领域对象和映射文件。其中对应T_FILE表的领域对象Tfile.java为:

  代码 1 领域对象Tfile

1. package sshfile.model;
2. public class Tfile
3.{
4. private String fileId;
5. private String fileName;
6. private byte[] fileContent;
7. private String remark;
8. …//getter and setter
9. }


  特别需要注意的是:数据库表为Blob类型的字段在Tfile中的fileContent类型为byte[]。Tfile的Hibernate映射文件Tfile.hbm.xml放在Tfile .java类文件的相同目录下:

  代码 2 领域对象映射文件

1. <?xml version="1.0"?>
2. <!DOCTYPE hibernate-mapping PUBLIC
3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
4. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
5. <hibernate-mapping>
6. <class name="sshfile.model.Tfile" table="T_FILE">
7. <id name="fileId" type="java.lang.String" column="FILE_ID">
8. <generator class="uuid.hex"/>
9. </id>
10. <property name="fileContent"
11. type="org.springframework.orm.hibernate3.support.BlobByteArrayType"
12. column="FILE_CONTENT" lazy="true"/>
13. …//其它一般字段的映射
14. </class>
15. </hibernate-mapping>


  fileContent字段映射为Spring所提供的BlobByteArrayType类型,BlobByteArrayType是用户自定义的数据类型,它实现了Hibernate 的org.hibernate.usertype.UserType接口。BlobByteArrayType使用从sessionFactory获取的Lob操作句柄lobHandler将byte[]的数据保存到Blob数据库字段中。这样,我们就再没有必要通过硬编码的方式,先insert然后再update来完成Blob类型数据的持久化,这个原来难伺候的老爷终于被平民化了。关于lobHandler的配置请见本文后面的内容。

  此外lazy="true"说明地返回整个Tfile对象时,并不返回fileContent这个字段的数据,只有在显式调用tfile.getFileContent()方法时才真正从数据库中获取fileContent的数据。这是Hibernate3引入的新特性,对于包含重量级大数据的表字段,这种抽取方式提高了对大字段操作的灵活性,否则加载Tfile对象的结果集时如果总是返回fileContent,这种批量的数据抽取将可以引起数据库的"洪泛效应"。

  2、DAO编写和配置

  Spring强调面向接口编程,所以我们将所有对Tfile的数据操作的方法定义在TfileDAO接口中,这些接口方法分别是:

  ·findByFildId(String fileId)

  ·save(Tfile tfile)

  ·List findAll()

  TfileDAOHibernate提供了对TfileDAO接口基于Hibernate的实现,如代码 3所示:

  代码 3 基于Hibernate 的fileDAO实现类

1. package sshfile.dao;
2.
3. import sshfile.model.*;
4. import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
5. import java.util.List;
6.
7. public class TfileDAOHibernate
8. extends HibernateDaoSupport implements TfileDAO
9. {
10. public Tfile findByFildId(String fileId)
11. {
12. return (Tfile) getHibernateTemplate().get(Tfile.class, fileId);
13. }
14. public void save(Tfile tfile)
15. {
16. getHibernateTemplate().save(tfile);
17. getHibernateTemplate().flush();
18. }
19. public List findAll()
20. {
21. return getHibernateTemplate().loadAll(Tfile.class);
22. }
23. }


  TfileDAOHibernate通过扩展Spring提供的Hibernate支持类HibernateDaoSupport而建立,HibernateDaoSupport封装了HibernateTemplate,而HibernateTemplate封装了Hibernate所提供几乎所有的的数据操作方法,如execute(HibernateCallback action),load(Class entityClass, Serializable id),save(final Object entity)等等。

  所以我们的DAO只需要简单地调用父类的HibernateTemplate就可以完成几乎所有的数据库操作了。

  由于Spring通过代理Hibernate完成数据层的操作,所以原Hibernate的配置文件hibernate.cfg.xml的信息也转移到Spring的配置文件中:

  代码 4 Spring中有关Hibernate的配置信息

1. <beans>
2. <!-- 数据源的配置 //-->
3. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
4. destroy-method="close">
5. <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
6. <property name="url" value="jdbc:oracle:thin:@localhost:1521:ora9i"/>
7. <property name="username" value="test"/>
8. <property name="password" value="test"/>
9. </bean>
10. <!-- Hibernate会话工厂配置 //-->
11. <bean id="sessionFactory"
12. class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
13. <property name="dataSource" ref="dataSource"/>
14. <property name="mappingDirectoryLocations">
15. <list>
16. <value>classpath:/sshfile/model</value>
17. </list>
18. </property>
19. <property name="hibernateProperties">
20. <props>
21. <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
22. <prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
23. </props>
24. </property>
25. </bean>
26. <!-- Hibernate 模板//-->
27. <bean id="hibernateTemplate"
28. class="org.springframework.orm.hibernate3.HibernateTemplate">
29. <property name="sessionFactory" ref="sessionFactory"/>
30. </bean>
31. <!--DAO配置 //-->
32. <bean id="tfileDAO" class="sshfile.dao.TfileDAOHibernate">
33. <property name="hibernateTemplate" ref="hibernateTemplate" />
34. </bean>
35. …
36. </beans>


  第3~9行定义了一个数据源,其实现类是apache的BasicDataSource,第11~25行定义了Hibernate的会话工厂,会话工厂类用Spring提供的LocalSessionFactoryBean维护,它注入了数据源和资源映射文件,此外还通过一些键值对设置了Hibernate所需的属性。

  其中第16行通过类路径的映射方式,将sshfile.model类包目录下的所有领域对象的映射文件装载进来,在本文的例子里,它将装载进Tfile.hbm.xml映射文件。如果有多个映射文件需要声明,使用类路径映射方式显然比直接单独指定映射文件名的方式要简便。

  第27~30行定义了Spring代理Hibernate数据操作的HibernateTemplate模板,而第32~34行将该模板注入到tfileDAO中。

  需要指定的是Spring 1.2.5提供了两套Hibernate的支持包,其中Hibernate 2相关的封装类位于org.springframework.orm.hibernate2.*包中,而Hibernate 3.0的封装类位于org.springframework.orm.hibernate3.*包中,需要根据您所选用Hibernate版本进行正确选择。

  3、Lob字段处理的配置

  我们前面已经指出Oracle的Lob字段和一般类型的字段在操作上有一个明显的区别--那就是你必须首先通过Oracle的empty_blob()/empty_clob()初始化Lob字段,然后获取该字段的引用,通过这个引用更改其值。所以要完成对Lob字段的操作,Hibernate必须执行两步数据库访问操作,先Insert再Update。

  使用BlobByteArrayType字段类型后,为什么我们就可以象一般的字段类型一样操作Blob字段呢?可以确定的一点是:BlobByteArrayType不可能逾越Blob天生的操作方式,原来是BlobByteArrayType数据类型本身具体数据访问的功能,它通过LobHandler将两次数据访问的动作隐藏起来,使Blob字段的操作在表现上和其他一般字段业类型无异,所以LobHandler即是那个"苦了我一个,幸福十亿人"的那位幕后英雄。

  LobHandler必须注入到Hibernate会话工厂sessionFactory中,因为sessionFactory负责产生与数据库交互的Session。LobHandler的配置如代码 5所示:

  代码 5 Lob字段的处理句柄配置

1. <beans>
2. …
3. <bean id="nativeJdbcExtractor"
4. class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor"
5. lazy-init="true"/>
6. <bean id="lobHandler"
7. class="org.springframework.jdbc.support.lob.OracleLobHandler" lazy-init="true">
8. <property name="nativeJdbcExtractor">
9. <ref local="nativeJdbcExtractor"/>
10. </property>
11. </bean>
12. …
13. </beans>


  首先,必须定义一个能够从连接池中抽取出本地数据库JDBC对象(如OracleConnection,OracleResultSet等)的抽取器:nativeJdbcExtractor,这样才可以执行一些特定数据库的操作。对于那些仅封装了Connection而未包括Statement的简单数据连接池,SimpleNativeJdbcExtractor是效率最高的抽取器实现类,但具体到apache的BasicDataSource连接池,它封装了所有JDBC的对象,这时就需要使用CommonsDbcpNativeJdbcExtractor了。Spring针对几个著名的Web服务器的数据源提供了相应的JDBC抽取器:

  ·WebLogic:WebLogicNativeJdbcExtractor

  ·WebSphere:WebSphereNativeJdbcExtractor

  ·JBoss:JBossNativeJdbcExtractor

  在定义了JDBC抽取器后,再定义lobHandler。Spring 1.2.5提供了两个lobHandler:

  ·DefaultLobHandler:适用于大部分的数据库,如SqlServer,MySQL,对Oracle 10g也适用,但不适用于Oracle 9i(看来Oracle 9i确实是个怪胎,谁叫Oracle 公司自己都说Oracle 9i是一个过渡性的产品呢)。

  ·OracleLobHandler:适用于Oracle 9i和Oracle 10g。

  由于我们的数据库是Oracle9i,所以使用OracleLobHandler。

  在配置完LobHandler后, 还需要将其注入到sessionFactory的Bean中,下面是调用后的sessionFactory Bean的配置:

  代码 6 将lobHandler注入到sessionFactory中的配置

1. <beans>
2. …
3. <bean id="sessionFactory"
4. class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
5. <property name="dataSource" ref="dataSource"/>
6. <!-- 为处理Blob类型字段的句柄声明 //-->
7. <property name="lobHandler" ref="lobHandler"/>
8. …
9. </bean>
10. …
11. </beans>


  如第7所示,通过sessionFactory的lobHandler属性进行注入。

Struts+Spring+Hibernate上传下载 --之一

文件的上传和下载在J2EE编程已经是一个非常古老的话题了,也许您马上就能掰着指头数出好几个著名的大件:如SmartUpload、Apache的FileUpload。但如果您的项目是构建在Struts+Spring+Hibernate(以下称SSH)框架上的,这些大件就显得笨重而沧桑了,SSH提供了一个简捷方便的文件上传下载的方案,我们只需要通过一些配置并辅以少量的代码就可以完好解决这个问题了。

  本文将围绕SSH文件上传下载的主题,向您详细讲述如何开发基于SSH的Web程序。SSH各框架的均为当前最新版本:

  ·Struts 1.2

  ·Spring 1.2.5

  ·Hibernate 3.0

  本文选用的数据库为Oracle 9i,当然你可以在不改动代码的情况下,通过配置文件的调整将其移植到任何具有Blob字段类型的数据库上,如MySQL,SQLServer等。

  总体实现

  上传文件保存到T_FILE表中,T_FILE表结构如下:


图 1 T_FILE表结构

  其中:

  ·FILE_ID:文件ID,32个字符,用Hibernate的uuid.hex算法生成。

  ·FILE_NAME:文件名。

  ·FILE_CONTENT:文件内容,对应Oracle的Blob类型。

  ·REMARK:文件备注。

  文件数据存储在Blob类型的FILE_CONTENT表字段上,在Spring中采用OracleLobHandler来处理Lob字段(包括Clob和Blob),由于在程序中不需要引用到oracle数据驱动程序的具体类且屏蔽了不同数据库处理Lob字段方法上的差别,从而撤除程序在多数据库移植上的樊篱。

  1.首先数据表中的Blob字段在Java领域对象中声明为byte[]类型,而非java.sql.Blob类型。

  2.数据表Blob字段在Hibernate持久化映射文件中的type为org.springframework.orm.hibernate3.support.BlobByteArrayType,即Spring所提供的用户自定义的类型,而非java.sql.Blob。

  3.在Spring中使用org.springframework.jdbc.support.lob.OracleLobHandler处理Oracle数据库的Blob类型字段。

  通过这样的设置和配置,我们就可以象持久化表的一般字段类型一样处理Blob字段了。

  以上是Spring+Hibernate将文件二进制数据持久化到数据库的解决方案,而Struts通过将表单中file类型的组件映射为ActionForm中类型为org.apache.struts.upload. FormFile的属性来获取表单提交的文件数据。

  综上所述,我们可以通过图 2,描绘出SSH处理文件上传的方案:


图 2 SSH处理文件上传技术方案

  文件上传的页面如图 3所示:


图 3 文件上传页面

  文件下载的页面如图 4所示:


图 4 文件下载页面

  该工程的资源结构如图 5所示:


图 5 工程资源结构


  工程的类按SSH的层次结构划分为数据持久层、业务层和Web层;WEB-INF下的applicationContext.xml为Spring的配置文件,struts-config.xml为Struts的配置文件,file-upload.jsp为文件上传页面,file-list.jsp为文件列表页面。

  本文后面的章节将从数据持久层->业务层->Web层的开发顺序,逐层讲解文件上传下载的开发过程。

基于Struts+Spring+Hibernate构建多层应用系统

本文是开发基于spring的web应用的入门文章,前端采用Struts MVC框架,中间层采用spring,后台采用Hibernate。

  本文包含以下内容:

   ·配置Hibernate和事务

   ·装载Spring的applicationContext.xml文件

   ·建立业务层和DAO之间的依赖关系

   ·将Spring应用到Struts中

简介

  这个例子是建立一个简单的web应用,叫MyUsers,完成用户管理操作,包含简单的数据库增,删,查,该即CRUD(新建,访问,更新,删除)操作。这是一个三层的web应用,通过Action(Struts)访问业务层,业务层访问DAO。图一简要说明了该应用的总体结构。图上的数字说明了流程顺序-从web(UserAction)到中间层(UserManager),再到数据访问层(UserDAO),然后将结果返回。

  Spring层的真正强大在于它的声明型事务处理,帮定和对持久层支持(例如Hiberate和iBATIS)

  以下下是完成这个例子的步骤:

  1. 安装Eclipse插件

  2. 数据库建表

  3. 配置Hibernate和Spring

  4. 建立Hibernate DAO接口的实现类

  5. 运行测试类,测试DAO的CRUD操作

  6. 创建一个处理类,声明事务

  7. 创建web层的Action和model

  8. 运行Action的测试类测试CRUD操作

  9. 创建jsp文件通过浏览器进行CRUD操作

  10. 通过浏览器校验jsp

  安装eclipse插件

  1. Hibernate插件http://www.binamics.com/hibernatesync

  2. Spring插件http://springframework.sourceforge.net/spring-ide/eclipse/updatesite/

  3. MyEclipse插件(破解版)

  4. Tomcat插件. tanghan

  5. 其他插件包括xml,jsp,

  数据库建表


create table app_user(id number not null primary,firstname vchar(32),lastname vchar(32));

  新建项目

  新建一个web project,新建后的目录结构同时包含了新建文件夹page用于放jsp文件,和源文件夹test用于放junit测试文件。同时将用到的包,包括struts,hibernate,spring都导入到lib目录下。

  创建持久层O/R mapping

  1. 在src/com.jandar.model下用hibernate插件从数据库导出app_user的.hbm.xml文件改名为User.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
   "-//Hibernate/Hibernate Mapping DTD//EN"
   "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" >
<hibernate-mapping package="com.jandar.model">
<class name="User" table="APP_USER">
 <id
  column="ID"
  name="id"
  type="integer"
 >

  <generator class="assigned" />

 </id>

 <property
   column="LASTNAME"
   length="10"
   name="lastname"
   not-null="false"
   type="string"
 />

 <property
   column="FIRSTNAME"
   length="10"
   name="firstname"
   not-null="true"
   type="string"
 />

</class>
</hibernate-mapping>

  2. 通过hibernate synchronizer->synchronizer file生成User.java文件,User对象对应于数据库中的app_user表

  注:在eclipse下自动生成的对象文件不完全相同,相同的是每个对象文件必须实现Serializable接口,必需又toString和hashCode方法;

import java.io.Serializable;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

public class BaseObject implements Serializable {
 public String toString() {
  return ToStringBuilder.reflectionToString(this,
  ToStringStyle.MULTI_LINE_STYLE);
 }

 public boolean equals(Object o) {
  return EqualsBuilder.reflectionEquals(this, o);
 }

 public int hashCode() {
  return HashCodeBuilder.reflectionHashCode(this);
 }
}

public class User extends BaseObject {
 private Long id;
 private String firstName;
 private String lastName;

 /**
 * @return Returns the id.
 */

 public Long getId() {
  return id;
 }

 /**
  * @param id The id to set.
 */

 public void setId(Long id) {
  this.id = id;
 }

 /**
 * @return Returns the firstName.
 */

 public String getFirstName() {
  return firstName;
 }

 /**
  * @param firstName The firstName to set.
 */

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 /**
 * @return Returns the lastName.
 */

 public String getLastName() {
  return lastName;
 }

 /**
 * @param lastName The lastName to set.
 */

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
}



创建DAO访问对象

  1. 在src/com.jandar.service.dao新建IDAO.java接口,所有的DAO都继承该接口

package com.jandar.services.dao;

public interface IDAO {

}

  2. 在src/com.jandar.service.dao下新建IUserDAO.java接口

public interface IUserDAO extends DAO {
 List getUsers();
 User getUser(Integer userid);
 void saveUser(User user);
 void removeUser(Integer id);
}

  该接口提供了访问对象的方法,

  3. 在src/com.jandar.service.dao.hibernate下新建UserDAOHiberante.java

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.orm.hibernate.support.HibernateDaoSupport;
import com.jandar.model.User;
import com.jandar.service.dao.IUserDAO;

public class UserDaoHibernate extends HibernateDaoSupport implements IUserDAO {

 private Log log=LogFactory.getLog(UserDaoHibernate.class);
 /* (非 Javadoc)
 * @see com.jandar.dao.IUserDAO#getUsers()
 */

 public List getUsers() {
  return getHibernateTemplate().find("from User");
 }

 /* (非 Javadoc)
 * @see com.jandar.dao.IUserDAO#getUser(java.lang.Long)
 */

 public User getUser(Integer id) {
  // TODO 自动生成方法存根
  return (User) getHibernateTemplate().get(User.class,id);
 }

 /* (非 Javadoc)
 * @see com.jandar.dao.IUserDAO#saveUser(com.jandar.model.User)
 */

 public void saveUser(User user) {
  log.debug("xxxxxxx");
  System.out.println("yyyy");
  getHibernateTemplate().saveOrUpdate(user);
  if(log.isDebugEnabled())
  {
   log.debug("userId set to "+user.getId());
  }
 }

 /* (非 Javadoc)
 * @see com.jandar.dao.IUserDAO#removeUser(java.lang.Long)
 */

 public void removeUser(Integer id) {
  Object user=getHibernateTemplate().load(User.class,id);
  getHibernateTemplate().delete(user);
  if(log.isDebugEnabled()){
   log.debug("del user "+id);
  }
 }
}

  在这个类中实现了IUserDAO接口的方法,并且继承了HibernateDAOSupport类。这个类的作用是通过hibernate访问、操作对象,进而实现对数据库的操作。
11 mai

JSF与Struts的异同

Struts和JSF/Tapestry都属于表现层框架,这两种分属不同性质的框架,后者是一种事件驱动型的组件模型,而Struts只是单纯的MVC模式框架,老外总是急吼吼说事件驱动型就比MVC模式框架好,何以见得,我们下面进行详细分析比较一下到底是怎么回事?

  首先事件是指从客户端页面(浏览器)由用户操作触发的事件,Struts使用Action来接受浏览器表单提交的事件,这里使用了Command模式,每个继承Action的子类都必须实现一个方法execute。

  在struts中,实际是一个表单Form对应一个Action类(或DispatchAction),换一句话说:在Struts中实际是一个表单只能对应一个事件,struts这种事件方式称为application event,application event和component event相比是一种粗粒度的事件。

  struts重要的表单对象ActionForm是一种对象,它代表了一种应用,这个对象中至少包含几个字段,这些字段是Jsp页面表单中的input字段,因为一个表单对应一个事件,所以,当我们需要将事件粒度细化到表单中这些字段时,也就是说,一个字段对应一个事件时,单纯使用Struts就不太可能,当然通过结合JavaScript也是可以转弯实现的。

  而这种情况使用JSF就可以方便实现,

<h:inputText id="userId" value="#{login.userId}">
  <f:valueChangeListener type="logindemo.UserLoginChanged" />
</h:inputText>

  #{login.userId}表示从名为login的JavaBean的getUserId获得的结果,这个功能使用struts也可以实现,name="login" property="userId"

  关键是第二行,这里表示如果userId的值改变并且确定提交后,将触发调用类UserLoginChanged的processValueChanged(...)方法。

  JSF可以为组件提供两种事件:Value Changed和 Action. 前者我们已经在上节见识过用处,后者就相当于struts中表单提交Action机制,它的JSF写法如下:

<h:commandButton id="login" commandName="login">
  <f:actionListener type=”logindemo.LoginActionListener” />
</h:commandButton>

  从代码可以看出,这两种事件是通过Listerner这样观察者模式贴在具体组件字段上的,而Struts此类事件是原始的一种表单提交Submit触发机制。如果说前者比较语言化(编程语言习惯做法类似Swing编程);后者是属于WEB化,因为它是来自Html表单,如果你起步是从Perl/PHP开始,反而容易接受Struts这种风格。

基本配置

  Struts和JSF都是一种框架,JSF必须需要两种包JSF核心包、JSTL包(标签库),此外,JSF还将使用到Apache项目的一些commons包,这些Apache包只要部署在你的服务器中既可。

  JSF包下载地址:http://java.sun.com/j2ee/javaserverfaces/download.html选择其中Reference Implementation。

  JSTL包下载在http://jakarta.apache.org/site/downloads/downloads_taglibs-standard.cgi

  所以,从JSF的驱动包组成看,其开源基因也占据很大的比重,JSF是一个SUN伙伴们工业标准和开源之间的一个混血儿。

  上述两个地址下载的jar合并在一起就是JSF所需要的全部驱动包了。与Struts的驱动包一样,这些驱动包必须位于Web项目的WEB-INF/lib,和Struts一样的是也必须在web.xml中有如下配置:

<web-app>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>
</web-app>

  这里和Struts的web.xml配置何其相似,简直一模一样。

  正如Struts的struts-config.xml一样,JSF也有类似的faces-config.xml配置文件:


<faces-config>
  <navigation-rule>
    <from-view-id>/index.jsp</from-view-id>
    <navigation-case>
      <from-outcome>login</from-outcome>
      <to-view-id>/welcome.jsp</to-view-id>
    </navigation-case>
  </navigation-rule>

  <managed-bean>
    <managed-bean-name>user</managed-bean-name>
    <managed-bean-class>com.corejsf.UserBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
</faces-config>

 

  在Struts-config.xml中有ActionForm Action以及Jsp之间的流程关系,在faces-config.xml中,也有这样的流程,我们具体解释一下Navigation:

  在index.jsp中有一个事件:

<h:commandButton label="Login" action="login" />

  action的值必须匹配form-outcome值,上述Navigation配置表示:如果在index.jsp中有一个login事件,那么事件触发后下一个页面将是welcome.jsp

  JSF有一个独立的事件发生和页面导航的流程安排,这个思路比struts要非常清晰。

  managed-bean类似Struts的ActionForm,正如可以在struts-config.xml中定义ActionForm的scope一样,这里也定义了managed-bean的scope为session。

  但是如果你只以为JSF的managed-bean就这点功能就错了,JSF融入了新的Ioc模式/依赖性注射等技术。

Ioc模式

  对于Userbean这样一个managed-bean,其代码如下:

public class UserBean {
  private String name;
  private String password;

  // PROPERTY: name
  public String getName() { return name; }
  public void setName(String newValue) { name = newValue; }

  // PROPERTY: password
  public String getPassword() { return password; }
  public void setPassword(String newValue) { password = newValue; }
}

<managed-bean>
  <managed-bean-name>user</managed-bean-name>
  <managed-bean-class>com.corejsf.UserBean</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>

  <managed-property>
    <property-name>name</property-name>
    <value>me</value>
  </managed-property>

  <managed-property>
    <property-name>password</property-name>
    <value>secret</value>
  </managed-property>
</managed-bean>

  faces-config.xml这段配置其实是将"me"赋值给name,将secret赋值给password,这是采取Ioc模式中的Setter注射方式

Backing Beans

  对于一个web form,我们可以使用一个bean包含其涉及的所有组件,这个bean就称为Backing Bean, Backing Bean的优点是:一个单个类可以封装相关一系列功能的数据和逻辑。

  说白了,就是一个Javabean里包含其他Javabean,互相调用,属于Facade模式或Adapter模式。


  对于一个Backing Beans来说,其中包含了几个managed-bean,managed-bean一定是有scope的,那么这其中的几个managed-beans如何配置它们的scope呢?

<managed-bean>
  ...
  <managed-property>
    <property-name>visit</property-name>
    <value>#{sessionScope.visit}</value>
  </managed-property>

  这里配置了一个Backing Beans中有一个setVisit方法,将这个visit赋值为session中的visit,这样以后在程序中我们只管访问visit对象,从中获取我们希望的数据(如用户登陆注册信息),而visit是保存在session还是application或request只需要配置既可。

UI界面

  JSF和Struts一样,除了JavaBeans类之外,还有页面表现元素,都是是使用标签完成的,Struts也提供了struts-faces.tld标签库向JSF过渡。

  使用Struts标签库编程复杂页面时,一个最大问题是会大量使用logic标签,这个logic如同if语句,一旦写起来,搞的JSP页面象俄罗斯方块一样,但是使用JSF标签就简洁优美:

<jia:navigatorItem name="inbox" label="InBox"
  icon="/images/inbox.gif"
  action="inbox"
  disabled="#{!authenticationBean.inboxAuthorized}"/>

  如果authenticationBean中inboxAuthorized返回是假,那么这一行标签就不用显示,多干净利索!

  先写到这里,我会继续对JSF深入比较下去,如果研究过Jdon框架的人,可能会发现,Jdon框架的jdonframework.xml中service配置和managed-bean一样都使用了依赖注射,看来对Javabean的依赖注射已经迅速地成为一种新技术象征,如果你还不了解Ioc模式,赶紧补课。