每日一得

不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速开发
最近关心的内容:SSH,seam,flex,敏捷,TDD
本站的官方站点是:颠覆软件

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  220 随笔 :: 9 文章 :: 421 评论 :: 0 Trackbacks

#

Command模式是最让我疑惑的一个模式,我在阅读了很多代码后,才感觉隐约掌握其大概原理,我认为理解设计模式最主要是掌握起原理构造,这样才 对自己实际编程有指导作用.Command模式实际上不是个很具体,规定很多的模式,正是这个灵活性,让人有些confuse.

Command定义
n 将来自客户端的请求传入一个对象,无需了解这个请求激活的 动作或有关接受这个请求的处理细节。

这是一种两台机器之间通讯联系性质的模式,类似传统过程语 言的 CallBack功能。

优点:
解耦了发送者和接受者之间联系。 发送者调用一个操作,接受者接受请求执行相应的动作,因为使用Command模式解耦,发送者无需知道接受者任何接口。

不少Command模式的代码都是针对图形界面的,它实际就是菜单命令,我们在一个下拉菜单选择一个命令时,然后会执行一些动作.

将 这些命令封装成在一个类中,然后用户(调用者)再对这个类进行操作,这就是Command模式,换句话说,本来用户(调用者)是直接调用这些命令的,如菜 单上打开文档(调用者),就直接指向打开文档的代码,使用Command模式,就是在这两者之间增加一个中间者,将这种直接关系拗断,同时两者之间都隔 离,基本没有关系了.

显然这样做的好处是符合封装的特性,降低耦合度,Command是将对行为进行封装的典型模式,Factory是将创建进行封装的模式,
从Command模式,我也发现设计模式一个"通病":好象喜欢将简单的问题复杂化, 喜欢在不同类中增加第三者,当然这样做有利于代码的健壮性 可维护性 还有复用性.

如何使用?
具体的Command模式代码各式各样,因为如何封装命令,不同系统,有不同的做法.下面事例是将命令封装在一个Collection的List中,任何 对象一旦加入List中,实际上装入了一个封闭的黑盒中,对象的特性消失了,只有取出时,才有可能模糊的分辨出:

典型的Command模式需要有一个接口.接口中有一个统一的方法,这就是"将命令/请求封装为对象":
public interface Command {
  public abstract void execute ( );
}

具体不同命令/请求代码是实现接口Command,下面有三个具体命令
public class Engineer implements Command {

  public void execute( ) {
    //do Engineer's command
  }
}

public class Programmer implements Command {

  public void execute( ) {
    //do programmer's command
  }
}

public class Politician implements Command {

  public void execute( ) {
    //do Politician's command
  }
}


按照通常做法,我们就可以直接调用这三个Command,但是使用Command模式,我们要将他们封装起来,扔到黑盒子List里去:

public class producer{
  public static List produceRequests() {
    List queue = new ArrayList();
    queue.add( new DomesticEngineer() );
    queue.add( new Politician() );
    queue.add( new Programmer() );
    return queue;
  }

}

这三个命令进入List中后,已经失去了其外表特征,以后再取出,也可能无法分辨出谁是Engineer 谁是Programmer了,看下面客户端如何调用Command模式:

public class TestCommand {
  public static void main(String[] args) {
    
    List queue = Producer.produceRequests();
    for (Iterator it = queue.iterator(); it.hasNext(); )
      
 //客户端直接调用execute方法,无需知道被调用者的其它更多类的方法名。
    ((Command)it.next()).execute();
  

  }
}

由此可见,调用者基本只和接口打交道,不合具体实现交互,这也体现了一个原则,面向接口编程,这样,以后增加第四个具体命令时,就不必修改调用者TestCommand中的代码了.

理解了上面的代码的核心原理,在使用中,就应该各人有自己方法了,特别是在如何分离调用者和具体命令上,有很多实现方法,上面的代码是使用"从List过一遍"的做法.这种做法只是为了演示.

使用Command模式的一个好理由还因为它能实现Undo功能.每个具体命令都可以记住它刚刚执行的动作,并且在需要时恢复.

Command模式在界面设计中应用广泛.Java的Swing中菜单命令都是使用Command模式,由于Java在界面设计的性能上还有欠缺,因此界面设计具体代码我们就不讨论,网络上有很多这样的示例.

参考:
http://www.patterndepot.com/put/8/command.pdf

http://www.javaworld.com/javaworld/javatips/jw-javatip68.html

posted @ 2006-08-31 21:46 Alex 阅读(280) | 评论 (0)编辑 收藏


Your Ad Here

小结

本章主要介绍了一个基于 J2EE Web 技术进行设计开发的论坛系统,通过这个系统的剖析,能够了解和掌握 GOF 设计模式,学会 Java 实战中一些处理技巧和技术。

使用 GOF 设计模式的主要优点:使得复杂系统的架构变得更加清晰而且有条理,而这一点正是许多程序员在开发实用系统中所缺乏的,可能导致的结果是大大降低 Java 系统可维护性以及可拓展性,重新回到了传统编程语言的陷阱中。

因此, GOF 设计模式对于 Java 设计编程的重要性是无论怎么强调也不过分,它能够帮助程序员更加深入地理解 Java 完全面向对象特性,从而以真正的面向对象设计概念进行实用系统的设计和开发。

Jive 系统是一个完全的 Web 系统,整个系统的最大特点是自我定制实现,它为了提高数据库的访问性能,使用了自己开发的数据库连接池;为了提高系统的数据处理系统,它使用了缓存机制;为了实现用户安全管理机制,它使用 Proxy 模式实现了角色权限的定位和检查等。这些模块功能在很多系统中都是需要的,但是如果想从 Jive 系统提炼出这些模块功能以达到重用,又是非常困难的。

因此,开发者需要一种具有一定高度的框架技术。在这个框架技术中,所有这些通用技术都能够自动实现,无需再自行设计和开发,能够将更多精力投入到与业务有关的特定功能开发中。 J2EE EJB 技术实际就是这种框架技术。

学习和研究 Jive 论坛系统也非常有助于程序员学习和理解 EJB J2EE 完整的框架技术,因为它们的目的都是一样,只不过实现的途径不一样而已。

posted @ 2006-08-31 12:29 Alex 阅读(429) | 评论 (0)编辑 收藏

7  Jive 安装调试运行

Jive 默认编码方式是 ISO8859_1 。下面以安装在英文 Linux 操作系统上为例:

1 )安装数据库。在 database 目录中选择对应数据库,如果数据库是 MySQL ,则使用 jive_mysql.sql ,然后通过数据库的管理工具将 jive_mysql.sql 导入相应数据库中。 Jive 所需要的数据库都已经准备完成。

2 )由于 Jive Servlet/JSP 系统,必须在 Web 容器支持下才能运行。因此,必须安装 Web 服务器软件,例如 Tomcat 等。

最后需要将 Jive 部署到 Tomcat 中。有两种办法,其中直接复制的办法比较简单,修改 server.xml 办法可以见其他章节介绍。

Jive application 目录下所有文件复制到 Tomcat 应用目录 webapps 的指定目录下,如 Tomcat/webapps/jive 下。

3 )在运行设置 Jive 之前,还需要设定 Jive 的初始值,在上述应用目录的 WEB-INF/classes/ 下,创建或编辑 jive_init.properties ,加入行:

jiveHome=C:\\javasource\\jive\\WEB-INF\\jiveHome

这是指定 jivHome 的目录, jiveHome 主要放置 Jive 的配置文件和搜索等内部工作文件,一般是建立在 WEB-INF 目录下,这样, jiveHome 的内容就不会被外界通过浏览器直接访问到,要注意是绝对路径。

4 )可以安装设置 Jive 系统。通过浏览输入网址: http://localhost:8080/jive/admin/setup/ Jive 的安装导航程序会自动进行安装检查。由于 Jive 编码是 ISO8859_1 ,如果涉及需要设置 Java 编码方式的提问,都设置为英文。

管理员密码最好更改一下,默认用户名和密码是 admin admin

全部设置完成后,进入 http://localhost:8080/jive/ 可以浏览。

进入 http://localhost:8080/jive/admin 可以实现论坛管理,可以设置全局过滤器,一般设置如下过滤器:

·          TextStyle 文本格式。

·          Newline 新一行。

·          Profanity 过渡亵渎或不恰当词语。

·          URLConverter URLConverter

·          ImageFilter ImageFilter 支持上传图片。        

·          CodeHighlighter CodeHighlighter

另外,缓存设置也很重要,可根据访问量、缓存击中率以及实际内存大小调整,可以提高论坛的性能。

图片上传路径的设置需要通过手工修改 jiveHome 下的 jive_config.xml 。打开 jive_config.xml 会发现,所有在管理配置中配置的信息都保存在这里,找到下列配置:

<upload>

        <dir>/home/bqlr/jive/upload/</dir>

        <relurl>upload/</relurl>

</upload>

其中, dir 是上传图片存放的绝对路径; relurl 是网址的相对路径,比如 upload/ ,那就表示输入下列网址:“ http://localhost:8080/jive/upload/xxxx.jpg ”,就可以看到上传的图片。因为上传图片有自动缩小功能,因此需要 Linux 安装时是带图形 X86 的完全安装。用户如为中文名,将影响图片文件名。

以上是 Jive 论坛的安装

posted @ 2006-08-31 12:29 Alex 阅读(769) | 评论 (0)编辑 收藏

6  Jive 图形处理

Jive 提供了强大的论坛功能,但是有些功能离实际需求还是有一定的距离,例如论坛是用于信息交流讨论的场所。而信息不只是文字,有时还包括图片或一些 PDF 等类型的文件,那么如何在 Jive 中实现这样的功能呢?

6.1  图片上传处理

HTML 中,使用表单 Form 主要是用来向服务器提交数据,格式如下:

FORM ACTION="URL"

METHOD="GET|POST"

ENCTYPE="…" TARGET="..."

. . .

/FORM

enctype 指定了表单提交给服务器时的内容形式( Content-Type ),默认值是 "application/x-www-form-urlencoded" ,这时,表单信息一般采用 URL 编码制。

但是,当向服务器传送图片或文件这样包含非 ASCII 字符或二进制数的数据时,根据 RFC1867 规定,就必须使用“ multipart/form-data ”内容类型。

其实无论是默认表单信息,还是图片文件,这些内容都是装载在 HTTP 协议的正文内容部分,都可以看成 HTTP 协议携带的对象,只是两种正文内容形式不一样。前者是 String 字符串类型,而后者则是一个通用的数据对象类型( Object )。在以后章节中将专门讨论 HTTP 协议装载数据对象的底层细节。

使用“ multipart/form-data ”上传文件的格式写法如下:

FORM ACTION="URL" METHOD="GET|POST" ENCTYPE=" multipart/form-data "

  <INPUT TYPE FILE NAME file1>

/FORM

文件通过 HTTP 协议传送到服务器端后,需要在服务器端对该文件进行专门的接受。 HttpServletRequest 没有提供直接获取文件数据的方法,因此需要开发专门的服务器文件处理组件。

目前有两种上传文件处理组件:一种是基于完全 JSP 结构的,使用 JSP 来处理上传的文件,以 SmartUpload http://www.jspsmart.com )最常用;还有一种是完全的 JavaBeans 组件: Cos 文件上传组件包( http://www.servlets.com/cos/index.html ), Cos 可以使用在 JSP 中,也可以使用在 Servlet Servlet Filter 中。

由于在实际应用中,文件上传功能往往和其他正常表单参数一起混合使用,而不是独立使用的。因此,可以设定一个 Servlet 专门用来处理这类混合表单的请求,在将文件接受处理后,自动导向到处理表单正常参数的 JSP/Servlet 去处理。

表单调用示例如下:

<form action="<%=request.getContextPath()%>/multipartformserv"

     method="post" enctype="multipart/form-data">

   <input type="hidden" name="FORWARDNAME" value="login.jsp" > 

   <input type="file" name="file1" >

   <input type="hidden" name="maxwidth" value="120" >

   <input type="hidden" name="maxheight" value="60" >

   <input type="text" name="username" >

   <input type="text" name="password" >

</form>

在这个表单中,既有文件提交,也有 username 这样正常的参数需要提交,提交的 Servlet 名为 multipartformserv 。由 multipartformserv 来处理上传的文件,然后再自动转交到 FORWARDNAME 的值 login.jsp 进行 username 等正常参数的处理。

在表单中,如果设定 maxwidth maxiheight ,那么表示如果上传的图片超过这个尺寸,服务器将缩小图片到这个尺寸。

编制一个专门用来统一处理 Jive 系统中所有文件处理的 Servlet ,这样有利于简化系统。编制 MultipartFormServ 如下:

public class MultipartFormServ extends HttpServlet {

  static final private String CONTENT_TYPE = "text/html";

  // 文件上传处理

  private static MultipartFormHandle mf = MultipartFormHandle.getInstance();

  // 文件上传后存放的临时目录

  private String dirName;

  private ServletContext context;

  public void init() throws ServletException {

    // web.xml 中读取上传目录参数

    dirName = mf.getUploaddir();

    if (dirName == null) {

      throw new ServletException("Please supply uploadDir parameter");

    }

  }

 

  // 处理带有文件内容的请求

  public void doPost(HttpServletRequest request, HttpServletResponse response)

         throws ServletException, IOException {

    try {

       mf.init(dirName,request); // 调用文件上传处理器处理

       // 得到 FORWARDNAME 参数值

       String forward=mf.getForwardProgram();

      if (forward.equals(""))

       {

         errorMessage("no forward program", response);

         return;

      }

      String param=mf.getForwardProgramParam();

      mf.clear();

       // 引导分流到 forward 参数值进行其他文本参数处理

      getServletConfig().getServletContext().

           getRequestDispatcher("/"+forward+"?"+param).forward(request, response);

 

    }catch (Exception Ex) {

          throw new ServletException(Ex.getMessage());

    }

  }

3-8  上传文件处理框架

}

MultipartFormHandler 主要调用 Cos 组件处理上传文件,对上传文件图形大小进行处理,然后将图形搬迁到指定的目录;同时也将请求信号中的正常参数提取出来,以作为转发使用。

这样一个图形或文件上传系统已经形成了一个框架结构,可以重复使用在任何需要图片或文件上传处理的系统中,如图 3-8 所示。

3-8 中上传文件处理额 Servlet 相当于请求信号 Request 的一个过滤器,既然正常的 Request 处理机制不能从 Request 提取携带的文件,那么,使用一个过滤器先将文件提取出来,剩余的再通过正常 Request 处理机制去处理。

6.2  服务器端图形处理

Java 最初是以 Applet 等客户端图形处理为技术起点的,而本节讨论的是如何在 Servlet/JSP 中实现图形处理。

Jive 中,图片可以用来显示用户的头像,用户在上传自己头像图片时,该图片的大小可能不一,但是由于版面原因,显示的头像图片又有大小限制,那么就需要在用户上传图片时对图片大小做一个检查。如果超过规定大小,就进行一定的缩放处理。

缩放处理有两种方式:是在 HTML 显示时,使用 image 语法的 width height 来 限制大小,但是这样做只是解决了表面问题,无法解决大字节图片传送到客户端带来的性能影响,这个图片因为是用户发言的头像,将会在每个帖子里面显示。如果 头像都是巨大图片,对帖子显示速度的影响是很大,因此必须在服务器端进行缩小后,再传送到客户端,这样提高了论坛系统性能。

服务器端的图形处理需要使用到 Java 的图形处理技术,而且图形处理是在服务器端的 Web 容器中进行的。和以往 Java 在客户端进行图形处理稍微有所不同,相同的是都要使用计算机的底层图形支持资源。

J2SE 1.4 提供新的增强的图形处理功能, JDK1.4 中最新的 javax.imageio.ImageIO 对图片进行读写操作,而使用 java.awt.geom.AffineTransform 对图片进行尺寸缩放处理。

import java.io.File;

import java.awt.image.BufferedImage;

import java.awt.Image;

import java.awt.image.AffineTransformOp;

import javax.imageio.ImageIO;

import java.awt.geom.AffineTransform;

 

public class UploadImg{

     /**

     * 参数设置

     * @param fromdir 图片的原始目录

     * @param todir 处理后的图片存放目录

     * @param imgfile 原始图片

     * @param sysimgfile 处理后的图片文件名前缀

     */

           public void init(String fromdir,String todir,String imgfile,String sysimgfile)

           {

                  this.fromdir=fromdir;

                  this.todir=todir;

                  this.imgfile=imgfile;

                  this.sysimgfile=sysimgfile;

             }

    …

    public boolean CreateThumbnail() throws Exception

    {

        //ext 是图片的格式 gif JPG png

        String ext=""

        double Ratio=0.0;

        File oldFile = new File(fromdir,imgfile);

        if (!F.isFile())  // 检查是否存在此图片文件

             throw new Exception(F+" is not image file error in CreateThumbnail!");

 

         // 首先判断上传的图片是 gif 还是 JPG ImageIO ,只能将 gif 转换为 png

         if (isJpg(imgfile)){

            ext="jpg";

        }else{

           ext="png";

        }

        File newFile = new File(todir,sysimgfile+"."+ext);

 

        BufferedImage Bi = ImageIO.read(oldFile);  // 读取原始图片

        if ((Bi.getHeight()>120) || (Bi.getWidth()>120)){

            if (Bi.getHeight()>Bi.getWidth())

              Ratio = 120.0/Bi.getHeight();

            else

              Ratio = 120.0/Bi.getWidth();

       }

       // 进行图片转换

       AffineTransformOp op =

          new AffineTransformOp(AffineTransform.getScaleInstance(Ratio, Ratio), null);

       Image itemp = op.filter(Bi, null);

 

       try { // 写入转换后的图片

           ImageIO.write((BufferedImage) itemp, ext, newFile);

       }catch (Exception ex) {

            throw new Exception(ex.getMessage());

       }

       return (true);

   }

}

该类中由于使用到了 Java AWT ,虽然没有实际显示,但 Linux 系统下需要 X11 Windows 的支持(安装 Linux 时需安装 XFree86 Linux 完全安装方式包括安装 XFree86 )。

该缩放功能是在图片上传到服务器后再进行的处理,可以对 JPG 进行缩小放大;对上传是 GIF 的图片,缩放后变成 PNG 图片格式文件。

posted @ 2006-08-31 12:28 Alex 阅读(432) | 评论 (0)编辑 收藏

     摘要: 5  Jive 的其他组件技术 Jive 是一个比较丰富的知识宝藏,从中可以学习到很多新的实战技巧和具体功能实现方式。前面基本介绍了 Jive 中的一些主要架构技术,通过这些技术可以基本上掌握 Jive 论坛系统。 Jive 中还有很多非常实用的组件技术和工具库,分...  阅读全文
posted @ 2006-08-31 12:27 Alex 阅读(653) | 评论 (0)编辑 收藏

4  Jive 的缓存机制

Jive 论坛的一个主要特点就是其性能速度快,因此很多巨大访问量的网站都采用了 Jive 论坛。这些都是由于 Jive 采取了高速缓存机制。

缓存( Cache )机制是提高系统运行性能必不可少的技术。缓存机制从原理上讲比较简单,就是在原始数据第一次读取后保存在内存中,下次读取时,就直接从内存中读取。原始数据有可能保存在持久化介质或网络上。缓存机制也是代理模式的一种实现。

4.1  缓存原理和实现

Jive Cache 总体来说实现得不是非常精简和有效。它是针对每个具体数据对象逐个实现缓冲,这种“穷尽”的办法不是实践所推荐的用法。通过使用动态代理模式,可以根据具体方法的不同来实现缓存是值得推荐的做法。 Jive 的缓存实现得比较简单,可以用来学习和研究缓存机制。

Jive 中的 Cache 实现了缓存机制的大部分行为,它是将对象用惟一的关键字 Key 作标识保存在 HashMap Hashtable 中。当然,必须知道这些对象的大小,这个前提条件的设定可以保证缓存增长时不会超过规定的最大值。

如果缓存增长得太大,一些不经常被访问的对象将首先从缓存中删除。如果设置了对象的最大生命周期时间,即使这个对象被反复频繁访问,也将从缓存中删除。这个特性可以适用于一些周期性需要刷新的数据,如来自数据库的数据。

Cach 中除了 getObject() 方法的性能依据缓存大小,其他方法的性能都是比较快的。一个 HashMap 用来实现快速寻找,两个 LinkedList 中一个以一定的访问顺序来保存对象,叫 accessed LinkedList ;另外一个以它们加入缓存的顺序保存这些对象,这种保存对象只是保存对象的引用,叫 age LinkedList 。注意,这里的 LinkedList 不是 JDK 中的 LinkedList ,而是 Jive 自己定义的 LinkedList

当对象被加入缓存时,首先被 CacheObject 封装。封装有以下信息:对象大小(以字节计算),一个指向 accessed LinkedList 的引用,一个指向 age LinkedList 的引用。

当从缓存中获取一个对象如 ObjectA 时,首先, HashMap 寻找到指向封装 ObjectA 等信息的 CacheObject 对象。然后,这个对象将被移动到 accessed LinkedList 的前面,还有其他一些动作如缓存清理、删除、过期失效等都是在这个动作中一起触发实现的。

public class Cache implements Cacheable {

    /**

     * 因为 System.currentTimeMillis() 执行非常耗费性能,因此如果 get 操作都执行

* 这条语句将会形成性能瓶颈, 通过一个全局时间戳来实现每秒更新

* 当然,这意味着在缓存过期时间计算上有一到几秒的误差

     */

    protected static long currentTime = CacheTimer.currentTime;

    //CacheObject 对象

    protected HashMap cachedObjectsHash;

    //accessed LinkedList 最经常访问的排列在最前面

    protected LinkedList lastAccessedList;

    // 以缓存加入顺序排列,最后加入排在最前面;越早加入的排在最后面

    protected LinkedList ageList;

    // 缓存最大限制 默认是 128k 可根据内存设定,越大性能越高

    protected int maxSize =  128 * 1024;

    // 当前缓存的大小

    protected int size = 0;

    // 最大生命周期时间,默认是没有

    protected long maxLifetime = -1;

    // 缓存的击中率,用于评测缓存效率

    protected long cacheHits, cacheMisses = 0L;

 

    public Cache() {

        // 构造 HashMap. 默认 capacity 11

        // 如果实际大小超过 11 HashMap 将自动扩充,但是每次扩充都

// 是性能开销,因此期初要设置大一点

        cachedObjectsHash = new HashMap(103);

        lastAccessedList = new LinkedList();

        ageList = new LinkedList();

    }

    public Cache(int maxSize) {

        this();

        this.maxSize = maxSize;

    }

    public Cache(long maxLifetime) {

        this();

        this.maxLifetime = maxLifetime;

    }

    public Cache(int maxSize, long maxLifetime) {

        this();

        this.maxSize = maxSize;

        this.maxLifetime = maxLifetime;

    }

    public int getSize() {        return size;    }

    public int getMaxSize() {        return maxSize;    }

 

    public void setMaxSize(int maxSize) {

        this.maxSize = maxSize;

        // 有可能缓存大小超过最大值,需要激活删除清理动作

        cullCache();

    }

    public synchronized int getNumElements() {

        return cachedObjectsHash.size();

    }

 

    /**

     * 增加一个 Cacheable 对象

* 因为 HashMap 不是线程安全的,所以操作方法要使用同步

* 如果使用 Hashtable 就不必同步

     */

    public synchronized void add(Object key, Cacheable object) {

        // 删除已经存在的 key

        remove(key);

        int objectSize = object.getSize();

        // 如果被缓存对象的大小超过最大值,就放弃

        if (objectSize > maxSize * .90) {            return;        }

        size += objectSize;

        // 创建一个 CacheObject 对象

        CacheObject cacheObject = new CacheObject(object, objectSize);

        cachedObjectsHash.put(key, cacheObject);  // 保存这个 CacheObject

        // 加入 accessed LinkedList Jive 自己的 LinkedList 在加入时可以返回值

        LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);

        // 保存引用

        cacheObject.lastAccessedListNode = lastAccessedNode;

        // 加入到 age LinkedList

        LinkedListNode ageNode = ageList.addFirst(key);

        // 这里直接调用 System.currentTimeMillis(); 用法值得讨论

        ageNode.timestamp = System.currentTimeMillis();

        // 保存引用

        cacheObject.ageListNode = ageNode;

        // 做一些清理工作

        cullCache();

    }

    /**

     * 从缓存中获得一个被缓存的对象,这个方法在下面两种情况返回空

     *    <li> 该对象引用从来没有被加入缓存中

     *    <li> 对象引用因为过期被清除 </ul>

     */

    public synchronized Cacheable get(Object key) {

        // 清除过期缓存

        deleteExpiredEntries();

        // Key 从缓存中获取一个对象引用

        CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);

        if (cacheObject == null) {

            // 不存在,增加未命中率

            cacheMisses++;

            return null;

        }

        // 存在,增加命中率

        cacheHits++;

        // accessed LinkedList 中将对象从当前位置删除

        // 重新插入在第一个

        cacheObject.lastAccessedListNode.remove();

        lastAccessedList.addFirst(cacheObject.lastAccessedListNode);

        return cacheObject.object;

    }

    …

}

Cache 中,关键字 Key 是一个对象,为了再次提高性能,可以进一步将 Key 确定为一个 long 类型的整数。

4.2  缓存使用

建立 LongCache 只是为了提高原来的 Cache 性能,本身无多大意义,可以将 LongCache 看成与 Cache 一样的类。

LongCache 的关键字 Key Forum ForumThread 以及 ForumMessage long 类型的 ID ,值 Value Forum ForumThread 以及 ForumMessage 等的对象。这些基本是通过 DatabaseCacheManager 实现完成,在主要类 DbForumFactory 的初始化构造时,同时构造了 DatabaseCacheManager 的实例 cacheManager

前面过滤器功能分析中, Message 对象获得方法的第一句如下:

protected ForumMessage getMessage(long messageID, long threadID, long forumID) throws

      ForumMessageNotFoundException {

    DbForumMessage message = cacheManager.messageCache.get(messageID);

    …

}

其中, cacheManager DatabaseCacheManager 的实例, DatabaseCacheManager 是一个缓存 Facade 类。在其中包含了 5 种类型的缓存,都是针对 Jive 5 个主要对象, DatabaseCacheManager 主要代码如下:

public class DatabaseCacheManager {

    public UserCache userCache;                          // 用户资料缓存

    public GroupCache groupCache;                       // 组资料缓存

    public ForumCache forumCache;                       //Forum 论坛缓存

    public ForumThreadCache threadCache;                //Thread 主题缓存

    public ForumMessageCache messageCache;          //Message 缓存

    public UserPermissionsCache userPermsCache;     // 用户权限缓存

 

    public DatabaseCacheManager(DbForumFactory factory) {

        …

        forumCache =

            new ForumCache(new LongCache(forumCacheSize, 6*HOUR), factory);

        threadCache =

            new ForumThreadCache(

                  new LongCache(threadCacheSize, 6*HOUR), factory);

        messageCache = new ForumMessageCache(

                  new LongCache(messageCacheSize, 6*HOUR), factory);

        userCache = new UserCache(

                  new LongCache(userCacheSize, 6*HOUR), factory);

        groupCache = new GroupCache(

                  new LongCache(groupCacheSize, 6*HOUR), factory);

        userPermsCache = new UserPermissionsCache(

                new UserPermsCache(userPermCacheSize, 24*HOUR), factory

        );

    }

    …

}

从以上代码看出, ForumCache 等对象生成都是以 LongCache 为基础构建的,以 ForumCache 为例,代码如下:

public class ForumCache extends DatabaseCache {

    // Cache 构建 ID 缓存

    protected Cache forumIDCache = new Cache(128*1024, 6*JiveGlobals.HOUR);

    // LongCache 构建整个对象缓存

    public ForumCache(LongCache cache, DbForumFactory forumFactory) {

        super(cache, forumFactory);

    }

 

    public DbForum get(long forumID) throws ForumNotFoundException {

        …

        DbForum forum = (DbForum)cache.get(forumID);

        if (forum == null) {    // 如果缓存没有从数据库中获取

            forum = new DbForum(forumID, factory);

            cache.add(forumID, forum);

        }

        return forum;

    }

 

public Forum get(String name) throws ForumNotFoundException {

         // name key ,从 forumIDCache 中获取 ID

 CacheableLong forumIDLong = (CacheableLong)forumIDCache.get(name);

        if (forumIDLong == null) { // 如果缓存没有 从数据库获得

            long forumID = factory.getForumID(name);

            forumIDLong = new CacheableLong(forumID); // 生成一个缓存对象

            forumIDCache.add(name, forumIDLong);

        }

        return get(forumIDLong.getLong());

    }

    …

}

由此可以看到, LongCache 封装了 Cache 的核心功能,而 ForumCache 等类则是在 LongCache 核心外又包装了与应用系统相关的操作,这有点类似装饰( Decorator )模式。

从中也可以看到 Cache LongCache 两种缓存的用法。

使用 Cache 时的关键字 Key 是任何字段。如上面代码中的 String name ,如果用户大量帖子主题查询中, Key query + blockID ,见 DbForum 中的 getThreadBlock 方法;而值 Value 则是 Long 类型的 ID ,如 ForumID ThreadID 等。

LongCache 的关键字 Key Long 类型的 ID ,如 ForumID ThreadID 等;而值 Value 则是 Forum ForumThread ForumMessage 等主要具体对象。

在实际使用中,大多数是根据 ID 获得对象。但有时并不是这样,因此根据应用区分了两种 Cache ,这其实类似数据库的数据表,除了主关键字外还有其他关键字。

4.3  小结

缓存中对象是原对象的映射,如何确保缓存中对象和原对象的一致性?即当原对象发生变化时,缓存中的对象也必须立即更新。这是缓存机制需要解决的另外一个基本技术问题。

Jive 中是在原对象发生变化时,立即进行清除缓存中对象,如 ForumMessage 对象的创建。在 DbForumThread AddMessage 方法中有下列语句:

factory.cacheManager.threadCache.remove(this.id);

factory.cacheManager.forumCache.remove(this.forumID);

即当有新的帖子加入时,将 ForumThreadCache ForumCache 相关缓冲全部清除。这样,当有相关对象读取时,将直接从数据库中读取,这是一种非常简单的缓存更新方式。

在复杂的系统,例如有一台以上的服务器运行着 Jive 系统。如果一个用户登陆一台服务器后,通过这台服务器增加新帖。那么按照上述原理,只能更新本服务器 JVM 中的缓存数据,而其他服务器则无从得知这种改变,这就需要一种分布式的缓存机制。

3-7  Jive 主要对象的访问

到目前可以发现 整个 Jive 系统其实是围绕 Forum ForumThread ForumMessage 等这些主要对象展开的读取、修改或创建等操作。由于这些对象原先持久化保存在数据库中,为了提高性能和加强安全性, Jive 在这些对象外面分别实现两层包装,如图 3-7 所示。

客户端如果需要访问这些对象,首先要经过它们的代理对象。进行访问权限的检查,然后再从缓存中获取该对象。只有缓存不存在时,才会从数据库中获取。

这套机制是大多数应用系统都面临的必须解决的基本功能,因此完全可以做成一个通用的可重复使用的框架。这样在具体应用时,不必每个应用系统都架设开发这样的机制。其实 EJB 就是这样一套框架,实体 Bean 都由缓存机制支持,而通过设定 ejb-jar.xml 可以实现访问权限控制,这些工作都直接由 EJB 容器实现了,不必在代码中自己来实现。剩余的工作是调整 EJB 容器的参数,使之适合应用系统的具体要求,这些将在以后章节中讨论。

Jive 中,图 3-7 的机制是通过不同方式实现的。基本上是一配二模式:一个对象有一个缓冲对象和一个代理对象,这样做的一个缺点是导致对象太多,系统变得复杂。这点在阅读 Jive 源码时可能已经发现。

如果建立一个对象工厂,工厂内部封装了图 3-7 机制实现过程,客户端可以根据不同的工厂输入参数获得具体不同的对象。这样也许代码结构要更加抽象和紧凑, Java 的动态代理 API 也许是实现这个工厂的主要技术基础。有兴趣者可以进一步研究提炼。

posted @ 2006-08-31 12:27 Alex 阅读(436) | 评论 (0)编辑 收藏

3  Jive 安全管理机制

Jive 中除了前面介绍的有关设计模式实现组件外,还有其他有一定特点的组件功能,分析研究这些组件功能可以更加完整透彻地理解 Jive 论坛系统。

Jive 安全管理机制基本是由下列部分组成:

·          安全验证机制。主要是验证用户名和密码组合是否与数据库中注册时的数据一致,以确认该用户身份为注册用户。这是对所有的 JSP 访问都进行拦截访问。

·          访问权限控制( ACL )。对不同的数据不同用户拥有不同的访问权限,例如,一个帖子普通用户可以浏览,但是不能更该;但是管理员却可以编辑删除。这部分功能是通过代理模式实现,为每个关键数据都建立一个代理类用来实现访问权限检查,这在前面讨论过。

·          用户资料管理系统。主要是管理用户的资料数据,进行用户组和用户关系的建立等。

安全验证机制

Jive 的安全验证机制是按照比较通用的思路设计的。类似前面“简单的用户注册管理系统”中的介绍, Jive 也是在所有的 JSP 页面中 include 一个安全检验功能的 global.jsp 。由于 global.jsp 是在每个 JSP 一开始必须执行的功能,因此通过拦截 global.jsp 拦截发往各个 JSP 页面的请求( request )。如果这个请求是合法的,将被允许通过;如果不是,将注明请求者身份是 Anonymous (匿名者)。

global.jsp 代码如下:

boolean isGuest = false;

Authorization authToken = SkinUtils.getUserAuthorization(request, response);

if (authToken == null) {// 未被验证通过

    authToken = AuthorizationFactory.getAnonymousAuthorization();

    isGuest=true;

}

Jive 中,以 Authorization 对象作为验证通过的标志,它的接口代码如下:

public interface Authorization {

    public long getUserID();   

    public boolean isAnonymous();

}

具体实现是 DbAuthorization ,代码如下:

public final class DbAuthorization implements Authorization, Serializable {

    private long userID;

    protected DbAuthorization(long userID) {

        this.userID = userID;

    }

    public long getUserID() {

        return userID;

    }

    public boolean isAnonymous() {

        return userID == -1;

    }

}

此类只是一个 userID ,因此只是一个象征性的标志。

SkinUtils 是一个为 JSP 服务的类,它的 getUserAuthorization 代码如下:

public static Authorization getUserAuthorization

        (HttpServletRequest request, HttpServletResponse response)

  {

    HttpSession session = request.getSession();

    // HttpSession 中获取 Authorization 实例

    Authorization authToken =

(Authorization)session.getAttribute(JIVE_AUTH_TOKEN);

    if (authToken != null) {     return authToken;  }

 

    // 如果 HttpSession 中没有,检查用户浏览器 cookie

    Cookie cookie = getCookie(request, JIVE_AUTOLOGIN_COOKIE);

    if (cookie != null) {

        try {

           String[] values = decodePasswordCookie(cookie.getValue());

           String username = values[0];

           String password = values[1];

           // cookie 中获得用户名和密码后,进行安全验证

           authToken = AuthorizationFactory.getAuthorization(username,password);

        }catch (Exception e) {}

        // put that token in the user's session:

        if (authToken != null) {// 如果通过验证,保存 authToken http Session

           session.setAttribute(JIVE_AUTH_TOKEN, authToken);

        }

       // return the authorization token

        return authToken;

    }

    return null;

}

用户验证预先通过两个步骤。首先检查 HttpSession 中是否保存了该用户的验证信息,如果用户第一次验证通过,反复访问,这道关口检查就可以通过。

如果 HttpSession 中没有验证信息,那么从该用户的浏览器 cookie 中寻找用户名和密码。如果该用户激活了 cookie 保存这些登录信息,那么应该可以找到用户名和密码,这样就省却了用户再次从键盘输入用户名和密码,将用户名和密码通过下列语句进行数据库验证:

authToken = AuthorizationFactory.getAuthorization(username,password);

这一举是验证关键。 AuthorizationFactory 是一个抽象类,定义了 Jive 安全验证机制所需的所有方法, AuthorizationFactory 的实现类似前面讨论的 ForumFactory 实现,是使用工厂模式加动态类反射机制完成的,代码如下:

public abstract class AuthorizationFactory {

   // 定义一个数据库具体实现

    private static String className =

        " com.Yasna.forum.database.DbAuthorizationFactory";

 

    private static AuthorizationFactory factory = null;

    // 验证方法 如果没有 UnauthorizedException 抛出,表示验证通过

    public static Authorization getAuthorization(String username,

            String password) throws UnauthorizedException

    {

        loadAuthorizationFactory();

        return factory.createAuthorization(username, password);

    }

    // 匿名者处理方法

    public static Authorization getAnonymousAuthorization() {

        loadAuthorizationFactory();

        return factory.createAnonymousAuthorization();

    }

    // 需要具体实现的抽象方法

    protected abstract Authorization createAuthorization(String username,

            String password) throws UnauthorizedException;

    protected abstract Authorization createAnonymousAuthorization();

    // 动态配置 AuthorizationFactory 的具体实现,可以在配置文件中定义一个

    // 基于 LDAP 的实现。类似 ForumFactory getInstance 方法

    private static void loadAuthorizationFactory() {

        …

    }

}

AuthorizationFactory 看上去很复杂,实际只有一个核心方法 getAuthorization 。实现用户名和密码的验证。如果无法通过验证,有两个信息实现显示:一个是抛出 UnauthorizedException ,另外一个是返回空的 Authorization 对象。

那么,子类 DbAuthorizationFactory 毫无疑问就是查询数据库,将输入的用户名和密码与数据库保存的用户名和密码进行校验。

Jive 的安全验证机制比较简单易懂,值得在实践中学习借鉴。但是注意到这套安全验证机制只是 Web 层的“手工”验证,资源访问权限( ACL )也是自己“手工”来实现的。如果使用 EJB 技术,因为 EJB 容器本身有一定的资源访问控制体系,因此在 Web 层验证通过后,需要将这些登录信息传递到 EJB 层。当然如果直接使用 Web 容器的安全验证机制,那么 Web 层与 EJB 层之间的登录信息传递将由容器实现,这样就更加简单方便。

Jive 这种的安全验证并不是使用 Web 容器的安全验证机制,如何使用 Web 容器的安全验证机制将在以后章节介绍。尽管如此, Jive 这套安全验证机制对付小型系统的应用也是足够的。


用户资料管理

Jive 中,用户 User 对象的操作访问类似于论坛 Forum 对象的访问,与 User 对象有关的操作都封装在一个类中操作,这是外观( Facade )模式的应用。

Jive 中,用户资料管理属于大系统中的一个子系统,在这个子系统中,用户子系统和其他系统又有一定的关系,涉及的类不少,通过建立一个 UserManager 类来统一对外接口,使得整个子系统条目结构清晰。

UserManager 中无外乎用户数据的管理,如用户的创建、修改、查询和删除。 DbUserManager UserManager 的一个数据库实现,可是看看 DbUserManager 中除了删除功能是直接通过 SQL 语句进行数据库删除操作外,其他都委托给 User 的具体实现 DbUser 实现的。这种实现非常类似于 EJB Session Bean 和实体 Bean 之间的关系。以创建用户资料为例,代码如下:

public User createUser(String username, String password, String email)

            throws UserAlreadyExistsException

 {

        User newUser = null;

        try {

            // username 查询改用户是否存在

            User existingUser = getUser(username);

            // 如果没有抛出 UserNotFoundException 异常,表示该用户存在

            //The user already exists since now exception, so:

            throw new UserAlreadyExistsException();

        } catch (UserNotFoundException unfe) {

            // 该用户不存在,创建一个新用户

            newUser = new DbUser(username, password, email, factory);

        }

        return newUser;

}

DbUser 的构造方法实际是用户资料的新增创建:

protected DbUser(String username, String password, String email,

            DbForumFactory factory)

{

        this.id = SequenceManager.nextID(JiveGlobals.USER);  // 获得自增 ID

        this.username = username;

        // Compute hash of password.

        this.passwordHash = StringUtils.hash(password);  // 获得加密的密码

        this.email = email;

        this.factory = factory;

        long now = System.currentTimeMillis();

        creationDate = new java.util.Date(now);

        modifiedDate = new java.util.Date(now);

        properties = new Hashtable();

        insertIntoDb();              // 数据库插入数据

}

Jive 中,数据修改的保存是由 DbUser saveToDb 方法实现的,而 saveToDb 方法调用是在每个 setXXXX 方法中。即每当外界调用 DbUser setXXXX ,则表示需要改变某些字段属性值,在这个方法中直接进行数据库存储,这也类似 EJB CMP 实体 Bean 的数据字段修改保存。

Jive 中组 Group 与用户 User 处理几乎差不多,只是在 Group 中整合了权限方面的信息,这种做法是有一定的局限性,不是很值得借鉴,要想设计一个动态扩展灵活的权限系统,必须在用户或组与权限之间引入角色概念,也就是比较先进的基于角色的权限系统( RBAC Roled-Based Access Control ,相关网址: http://csrc.nist.gov/rbac/ )。

RBAC 中,用户组只是用户的一个集合,应该是通过角色和权限发生联系。所以 RBAC 认为,如果给用户组赋予权限,那么用户组也接近角色的概念。

posted @ 2006-08-31 12:26 Alex 阅读(691) | 评论 (0)编辑 收藏

     摘要: 2  Jive 与设计模式 Jive 论坛系统使用大量设计模式巧妙地实现了一系列功能。因为设计模式的通用性和可理解性,将帮助更多人很快地理解 Jive 论坛源码,从而可以依据一种“协定”来动态地扩展它。那么使用设计模式还有哪些好处? ...  阅读全文
posted @ 2006-08-31 12:26 Alex 阅读(757) | 评论 (0)编辑 收藏

1  Jive 功能需求

Jive 功能需求分析类似于一个新系统的需求分析。只有了解 Jive 系统实现了哪些论坛功能,才能进一步研究和学习它是怎样巧妙、优雅地实现这些功能的。

论坛系统是网络交流的一种主要互动功能系统,如图 3-1 所示。通过论坛系统,用户可以共同就某个话题不断进行讨论,通过发贴功能发布新的话题,通过回贴功能回复别人的话题。 Jive 论坛系统可以允许管理员动态地创建新的论坛、编辑论坛的内容、设置论坛过滤信息以及管理注册用户等。

3-1  Jive 用例图

Jive 论坛系统中,用户角色和权限是紧密联系在一起的。主要分两大角色:普通用户和管理员,具体的表现形式是通过权限组合来体现的。管理方面的权限有:

·          SYSTEM_ADMIN ,系统管理员,可以管理整个系统。

·          FORUM_ADMIN ,论坛管理员,可以管理某个特定的论坛。

·          USER_ADMIN GROUP_ADMIN ,用户和组管理员,可以管理一些特定用户和用户 组。

论坛的读写权限包括:读权限,创建一个新主题,创建一个新的帖子等。

Jive 中没有明确定义普通用户和管理员角色,而是直接通过以上权限组合和具体用户直接建立联系,并将这种直接联系保存到数据库中。

在权限不是很复杂的情况下,这种没有引入角 色的做法比较简单直接。但由于用户和权限直接挂钩,而用户和权限都可能在不断地动态变化,那么它们之间由于联系太直接和紧密,对各自变化形成了限制。所 以,对于复杂的权限系统,引入了基于角色的权限系统,这将在以后章节中进一步讨论。

Jive 论坛业务对象主要分为 Forum ForumThread ForumMessage ,它们之间的关系如图 3-2 所示。

每个论坛 Forum 包含一系列 ForumThread (主题),而每个主题都是由很多内容帖子 ForumMessage 组成的,这是一个聚集关系。这 3 种对象中每一个对象都涉及到对象数据的创建、编辑、查询和删除,这些对象数据分别保存在数据库中。这 3 个对象对于不同的角色可操作访问权限是不一样的,只有系统管理员和论坛管理员可以对 Forum 相关数据实行操作,普通用户可以创建或编辑 ForumThread ForumMessage

Jive 论坛为了实现不同用户对不同基本对象的不同操作权限,通过设定一个统一的入口,在这个入口将检查客户端每次对数据的操作权限,如图 3-3 所示。

  

3-2  基本对象关系图                             3-3  入口示意图

客户端每次对数据库的操作,都要经过 ForumFactory 入口进入。在 ForumFactory 中会动态生成一个访问控制代理 ForumFactoryProxy ,通过 ForumFactoryProxy 检查客户端访问方法是否符合整体权限访问控制要求。

下面将从 ForumFactory 作为 Jive 论坛系统分析入手,结合设计模式逐步分解论坛功能的具体实现。

posted @ 2006-08-31 12:25 Alex 阅读(2096) | 评论 (1)编辑 收藏

JDBMonitor是一个开源项目。使用它开发者可以很轻松为系统增加数据库执行日志功能。它使用十分方便,您所需要做的唯一事情就是在您系统的JDBC连接字符串前增加类似于 "listenerconfig=/config.xml:url=" 的字符即可,不用写任何代码。

使用 JDBMonitor,您可以把数据库执行情况记录通过各种方式记录下来,比如打印到控制台、输出到文件或者通过socket传送给远程客户端。JDBMonitor是可扩展的,您可以扩展它来将执行情况通过其他方式记录下来,您所需要做的就是写一个实现IDBListener接口的类即可。

JDBMonitor遵守 GNU Lesser General Public Licence (LGPL)协议。此协议包含在发行包中。

入门

几乎所有大型数据库应用都包含有自己的SQL执行日志功能,此功能不仅能帮助开发人员调试,而且可以为DBA(数据库管理员)提供系统的运行信息。

(1)很难将业务逻辑同日志代码分离

(2)降低了代码的可读性。

(3)降低了系统的运行速度。在记录日志的时候,程序会暂停运行等待直到记录完成,而I/O操作是相当耗时的。

(4)很难记录运行耗时、语句参数等其他信息

(5)很难为我们无法修改代码的系统(例如没有源代码的系统)或者很难增加记录日志功能代码的系统(比如系统使用了ORMapping)增加日志功能。

JDBMonitor 则不同:

(1)您最多只需要修改一行代码。您需要修改的代码就是这一行:Class.forName("com.cownew.JDBMonitor.jdbc.DBDriver") ,然后再修改一下 JDBC连接字符串,只要从 “jdbc:db2://10.74.198.247:50000/app”修改成” listenerconfig=config.xml:url= jdbc:db2://10.74.198.247:50000/app”就可以了。在您使用WebLogic ,Tomcat或其他服务器的数据源功能的时候,连修改代码这一步都是无需的。

(2)JDBMonitor另起一个线程来记录SQL,所以它不会对程序运行速度有任何影响。

(3)它是高度可扩展的,所以您可以扩展它来把执行情况通过其他方式记录。比如,您可以写一个扩展类,来通过电子邮件将日志发送出去。

取得 JDBMonitor

JDBMonitor的最新稳定版本可以在JDBMonitor的网站上取得:

http://www.cownew.com/JDBMonitor

使用 JDBMonitor

1 将 jdbmonitor.jar放到您系统的类路径下。

2 让系统加载 JDBMonitor的JDBC驱动。

这一步将会依您系统加载JDBC驱动的方式的不同而不同。

(1)如果您通过代码的形式加载JDBC驱动,例如:

   Class.forName(“com.microsoft.jdbc.sqlserver.SQLServerDriver”);
   Connection cn = DriverManager.getConnection(……);

在这种情况下 ,您必须修改 “Class.forName”这一句来加载JDBMonitor的JDBC驱动(“com.cownew.JDBMonitor.jdbc.DBDriver”),而非以前的数据库JDBC驱动。

例如:

Class.forName(“com.cownew.JDBMonitor.jdbc.DBDriver”);
   Connection cn = DriverManager.getConnection(……);

(2)如果您在配置文件中指定JDBC驱动,比如,数据源配置文件或者其他类似的文件。

请修改原来的  JDBC驱动类为 “com.cownew.JDBMonitor.jdbc.DBDriver” 。

3 让 JDBMonitor加载能够加载原来的JDBC驱动

JDBMonitor的工作原理就是截获JDBC驱动的SQL语句调用、记录SQL语句,然后将SQL语句重新转发给原来的JDBC驱动,所以JDBMonitor必须首先向DriverManager注册JDBC驱动。

原来的JDBC驱动定义在配置文件的“JdbcDrivers” 段中。
<JdbcDrivers>
    <JdbcDriver class=" com.mysql.jdbc.Driver"/>
  </JdbcDrivers>

4 在原来的JDBC连接字符串前增加 JDBMonitor所需的信息。

您所需要做的就是将” listenerconfig=<configfilepath>:url=” 增加到原来的JDBC连接字符串前。“<configfilepath>”代表配置文件的路径,下面集中路径都是合法的:

/com/jdbmonitor/config.xml
com/jdbmonitor/config.xml
c:/ jdbmonitor /config.xml

JDBMoinitor使用getClass().getResourceAsStream加载类似于“/com/jdbmonitor/config.xml” and “com/jdbmonitor/config.xml” 的类路径文件,使用 FileInputStream加载类似于 “c:/ jdbmonitor /config.xml”的配置文件。

5 指定您要使用监听器:

您可以把数据库执行情况记录通过各种方式记录下来,比如打印到控制台、输出到文件或者通过socket传送给远程客户端。

我们已经开发了如下常用的监听器:FileDBListener、ConsoleDBListener、 SocketDBListene、DataBaseDBListener。当然您也可以开发满足您要求的监听器。
监听器定义在配置文件的 “Listeners”段中:

<Listeners>
    <!--ConsoleDBListener no arguments-->
    <Listener class="com.cownew.JDBMonitor.listenerImpl.ConsoleDBListener" arg=""/>
   
    <!--the arguments of FileDBListener is the file to log the SQL statement -->
    <Listener class="com.cownew.JDBMonitor.listenerImpl.FileDBListener" arg="c:/aaa.txt"/>
   
    <!--the arguments of SocketDBListener is the bound socket port of the listener server -->
    <Listener class="com.cownew.JDBMonitor.listenerImpl.SocketDBListener" arg="9527"/>
  </Listeners>

搞定!启动您的系统。耶!SQL语句被记录下来了,我们可以在控制台、文件甚至远程监视器中看到日志了。

举例

mvnforum的例子:

您可以从http://www.mvnForum.com得到mvnforum。我演示用的版本是1.0。

(1)打开webapp\WEB-INF\classes\ mvncore.xml,重新配置:

修改之前:

<driver_class_name>com.mysql.jdbc.Driver</driver_class_name>
<database_url>listenerconfig=c:/log/jdbmonitor/config.xml:url= jdbc:mysql://localhost/mvnforum?useUnicode=true&amp;characterEncoding=utf-8</database_url>

修改之后:
<driver_class_name> com.cownew.JDBMonitor.jdbc.DBDriver </driver_class_name>
        <database_url>jdbc:mysql://localhost/mvnforum?useUnicode=true&amp;characterEncoding=utf-8</database_url>

(2)创建文件 c:/log/jdbmonitor/config.xml。我只想将SQL语句记录到文本文件中,所以我做如下配置:
<config>
  <Listeners>
    <!--the arguments of FileDBListener is the file to log the SQL statement -->
    <Listener class="com.cownew.JDBMonitor.listenerImpl.FileDBListener" arg="c:/log.txt"/>
  </Listeners>
  <JdbcDrivers>
    <JdbcDriver class="com.mysql.jdbc.Driver"/>
  </JdbcDrivers>
</config>
(3) 将 jdbmonitor.jar放到webapp\WEB-INF\lib下。
(4) 搞定!

Jive的例子:

您可以从http://www.jivesoftware.com得到Jive。我演示用的版本是 Jive 2.0 beta版。

(1)打开http://localhost:8080/jive/admin/

“jdbc” 填为:com.cownew.JDBMonitor.jdbc.DBDriver

“server” 填为:c:/log/jdbmonitor/config.xml:url=jdbc:mysql://locahost/jive
(2)将 jdbmonitor.jar放到WEB-INF\lib下
(3) 象mvnforum中一样创建同样的 c:/log/jdbmonitor/config.xml 文件.
(4) 搞定!

代码方式的例子:

尽管直接在代码中指定系统所用的JDBC驱动类名和JDBC连接字符串是不推荐的,但是仍然有系统是这么做的。

比如:

              Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
              Connection conn = null;
              PreparedStatement ps = null;
              try
              {
                     conn = DriverManager
                                   .getConnection("jdbc:odbc:MQIS");
                     for (int i = 0; i < 1000; i++)
                     {                   
                            ps = conn.prepareStatement("update T_Material set fid=fid");
                            ps.execute();
                            ps.close();
                     }
              } finally
              {
                     ....         
              }

(1)修改一下代码为:
               Class.forName("com.cownew.JDBMonitor.jdbc.DBDriver");
              Connection conn = null;
              PreparedStatement ps = null;
              try
              {
                     conn = DriverManager.getConnection("listenerconfig= c:/log/jdbmonitor/config.xml:url=jdbc:odbc:MQIS");
                     for (int i = 0; i < 1000; i++)
                     {
                            ps = conn.prepareStatement("update T_Material set fid=fid");
                            ps.execute();
                            ps.close();
                     }
              } finally
              {
                     ....         
              }

(2)创建c:/log/jdbmonitor/config.xml文件。我想记录SQL语句到文本文件中同时输出到控制台,这样可以辅助我进行调试,所以我配置如下:
<config>
  <Listeners>
    <!--the arguments of FileDBListener is the file to log the SQL statement -->
<Listener class="com.cownew.JDBMonitor.listenerImpl.FileDBListener" arg="c:/log.txt"/>

<!--ConsoleDBListener no arguments-->
<Listener class="com.cownew.JDBMonitor.listenerImpl.ConsoleDBListener" arg=""/>
  </Listeners>
  <JdbcDrivers>
    <JdbcDriver class="com.mysql.jdbc.Driver"/>
  </JdbcDrivers>
</config>
(3) 将 jdbmonitor.jar放到类路径下。
(4) 搞定!

监听器

我们已经开发了如下常用的监听器:FileDBListener、ConsoleDBListener、 SocketDBListener、DataBaseDBListener。

1、ConsoleDBListener 控制台监听器

ConsoleDBListener会将SQL语句打印到控制台中。

这个监听器很容易配置:

<Listener class="com.cownew.JDBMonitor.listenerImpl.ConsoleDBListener" arg=""/>

2、FileDBListener 文件监听器

FileDBListener 会将SQL语句保存到文本文件中。

如下配置:

<Listener class="com.cownew.JDBMonitor.listenerImpl.FileDBListener" arg="c:/aaa.txt"/>

arg="c:/aaa.txt"表示日志将保存到文件c:/aaa.txt中。

3、SocketDBListener Socket监听器

SocketDBListener是一个socket服务器,客户端连接到它上边以后就可以接收到它发出的SQL语句。

如下配置:

<Listener class="com.cownew.JDBMonitor.listenerImpl.SocketDBListener" arg="9527"/>

arg="9527"表示服务器将在9527端口监听。

我们已经开发了如下两种客户端:SocketConsoleClient(Socket控制台客户端) 和 SocketSwingClient(Socket Swing客户端)。

SocketConsoleClient工作在控制台中:

SocketSwingClient是一个Swing GUI客户端:

您可以运行"java -classpath jdbmonitor.jar com.cownew.JDBMonitor.listenerImpl.sckListenerClient.SocketConsoleClient" 来启动SocketConsoleClient,运行"java -classpath jdbmonitor.jar com.cownew.JDBMonitor.listenerImpl.sckListenerClient.SocketSwingClient"启动SocketSwingClient

您可以编写符合您自己要求的客户端,具体细节请参考com.cownew.JDBMonitor.listenerImpl.sckListenerClient.ListenerClientcom.cownew.JDBMonitor.listenerImpl.sckListenerClient.IDBSocketClientListener.

4、DataBaseDBListener

DataBaseDBListener将会把SQL语句记录到数据库中:

如下配置:

<Listener class="com.cownew.JDBMonitor.listenerImpl.DataBaseDBListener"
arg="dburl=jdbc:odbc:MQIS;user=;password=;logtable=T_Log_SQLLog"/>

"dburl=jdbc:odbc:MQIS;user=;password=;"表示目标数据库的JDBC连接字符串。"logtable=T_Log_SQLLog" 表示SQL记录将被保存到哪个表中,默认的是T_Log_SQLLog

如果目标数据库用的JDBC驱动与被监控的数据库不同,请将它加入配置文件的 "JdbcDrivers" 部分,例如:

<config>
<Active>true</Active>
<Listeners>

<Listener class="com.cownew.JDBMonitor.listenerImpl.ConsoleDBListener" arg=""/>

<Listener class="com.cownew.JDBMonitor.listenerImpl.DataBaseDBListener"
arg="dburl=jdbc:odbc:MQIS;user=;password=;logtable=T_Log_SQLLog"/>
</Listeners>
<JdbcDrivers>
<JdbcDriver class="com.microsoft.jdbc.sqlserver.SQLServerDriver"/>
<JdbcDriver class="sun.jdbc.odbc.JdbcOdbcDriver"/>
</JdbcDrivers>
</config>

"T_Log_SQLLog"的结构是:

"T_Log_SQLLog"的建库脚本在com/cownew/JDBMonitor/listenerImpl/dataBaseListener,(db2.sql,mssqlserver.sql,oracle.sql)。

DataBaseDBListener是跨数据库的,你可以把记录SQL到任何关系数据库中。

FAQ:

1 如果我暂时不想记录SQL语句执行怎么办?难道我要重新修改成原来的样子?

答:无须如此。您只要修改config.xml,增加<Active>false</Active>到文件中即可。

如下:

<config>
  <Active> false </Active>
  <Listeners>
......
</config>

如何扩展JDBMonitor?

我们已经开发了如下常用的监听器:FileDBListener、ConsoleDBListener、 SocketDBListener、DataBaseDBListener。当然您也可以开发满足您要求的监听器。所有的监听器必须实现接口:com.cownew.JDBMonitor.commo. IDBListener。IDBListener有两个方法需要实现:

public void init(String arg);
public void logSql(SQLInfo info);

JDBMonitor会将配置文件中监听器定义中“arg”的值传递给 “init”方法、将代表SQL语句执行信息的SQLInfo传递给“logSql”方法。

更多信息请参考API文档。

posted @ 2006-08-31 10:59 Alex 阅读(1584) | 评论 (0)编辑 收藏

     摘要: UML 类图介绍 一、 UML 简介 UML ( Unified Modeling ...  阅读全文
posted @ 2006-08-29 22:30 Alex 阅读(1121) | 评论 (0)编辑 收藏

这一段时间,参加了部门组织的 RUP 教学项目,由一位“外援”架构师为我们指导教练。最近一直在忙于业务建模,今天刚刚将自己负责部分的系统用例识别了一遍。其间一直有一个问题,缠绕着包括我在内的很多同事,那就是用例之间的关系——包含、扩展、泛化——到底该如何使用。

    翻阅了同事去年参加 RUP 培训时带来的材料,终于能基本分清三者之间的关系。

 

用例是从系统外部可见的行为,是系统为某一个或几个参与者( Actor )提供的一段完整的服务。从原则上来讲,用例之间都是独立、并列的,它们之间并不存在着包含从属关系。但是为了体现一些用例之间的业务关系,提高可维护性和一致性,用例之间可以抽象出包含 (include) 、扩展 (extend) 和泛化 (generalization) 这几种关系。

在分开介绍它们之前,先说下它们的共性:都是从现有的用例中抽取出公共的那部分信息,作为一个单独的用例,然后通后过不同的方法来重用这个公共的用例,以减少模型维护的工作量。

 

1 、包含 (include)

  包含关系:使用包含 Inclusion 用例来封装一组跨越多个用例的相似动作(行为片断),以便多个基( Base )用例复用。基用例控制与包含用例的关系,以及被包含用例的事件流是否会插入到基用例的事件流中。基用例可以依赖包含用例执行的结果,但是双方都不能访问对方的属性。


 

包 含关系对典型的应用就是复用,也就是定义中说的情景。但是有时当某用例的事件流过于复杂时,为了简化用例的描述,我们也可以把某一段事件流抽象成为一个被 包含的用例;相反,用例划分太细时,也可以抽象出一个基用例,来包含这些细颗粒的用例。这种情况类似于在过程设计语言中,将程序的某一段算法封装成一个子 过程,然后再从主程序中调用这一子过程。 

    例如:业务中,总是存在着维护某某信息的功能,如果将它作为一个用例,那新建、编辑以及修改都要在用例详述中描述,过于复杂;如果分成新建用例、编辑用例和删除用例,则划分太细。这时包含关系可以用来理清关系。

 

 

 

2 扩展 (extend)

扩展关系:将基用例中一段相对独立并且可选的动作,用扩展( Extension )用例加以封装,再让它从基用例中声明的扩展点( Extension Point )上进行扩展,从而使基用例行为更简练和目标更集中。

扩展用例为基用例添加新的行为。扩展用例可以访问基用例的属性,因此它能根据基用例中扩展点的当前状态来判断是否执行自己。但是扩展用例对基用例不可见。

对于一个扩展用例,可以在基用例上有几个扩展点。

 

例如,系统中允许用户对查询的结果进行导出、打印。对于查询而言,能不能导出、打印查询都是一样的,导出、打印是不可见的。导入、打印和查询相对独立,而且为查询添加了新行为。因此可以采用扩展关系来描述:

 

 

用例详述里面大致可以这样来写:

执行查询

  基本流:

1. 员工选择查询功能

        员工期望查询业务数据时,选择查询链接,从而启动本用例的执行。

2. 系统转入查询页面,并显示备选的查询选项

       

3. 员工填写查询条件并提交

               

4. 系统验证查询条件的合法性

                验证条件的格式以及简单逻辑,如大小、前后、范围

5. 系统将符合条件的信息返回

        系统将查询结果以分页列表的形式显示在页面上

6. 员工退出查询功能

        员工点击退出链接,返回到上一级页面

    扩展点:导出、打印扩展点定义在步骤 5

 

导出

    该用例是在“导出、打印”扩展点上扩展了执行查询用例

    基本流:

        1 .如果员工要求导出,选择导出按钮

        。。。。。。

 

由上例可以看出 : 扩展用例的事件流往往可以也可抽象为基用例的备选流。但是在基用例本身已经是一个很复杂的情况下,选用扩展关系将备选流抽象成为单独的用例可以使基用例行为更简练和目标更集中(当然上面的例子中基用例可能简单了些)。

 

 

4 泛化 (generalization)

泛化关系:子用例和父用例相似,但表现出更特别的行为;子用例将继承父用例的所有结构、行为和关系。子用例可以使用父用例的一段行为,也可以重载它。父用例通常是抽象的。在实际应用中很少使用泛化关系,子用例中的特殊行为都可以作为父用例中的备选流存在。

 

例如,业务中可能存在许多需要部门领导审批的事情,但是领导审批的流程是很相似的,这时可以做成泛化关系表示:

 

用例详述里面大致可以这样来写:

审批

  基本流:

7. 领导选择要审批的记录

        领导期望审批记录时,选择待审批记录链接,从而启动本用例的执行。

8. 系统转入审批页面,并显示记录的详细信息

       

9. 领导填写审批意见

                领导根据记录的合理性来填写个人审批意见

10.              提交审批结果

       

 

工资调整审批

    该用例是审批用例的子用例

    基本流:

        1 .领导选择要审批的记录

 

2. 系统转入审批页面,并显示记录的详细信息

 

3. 领导填写审批意见

 

4. 提交审批结果

                如果调整幅度较大,则要提交上级审批

 

 

上面分析了用例之间的三种关系。其中最容易让人迷惑的就是包含关系和扩展关系得区别。如果你现在对两者还有迷惑,请再仔细的对比一下上面两者的描述:)。


posted @ 2006-08-29 22:23 Alex 阅读(519) | 评论 (1)编辑 收藏

Java 数据库连接(JDBC)由一组用 Java 编程语言编写的类和接口组成。JDBC 为工具/数据库开发人员提供了一个标准的 API,使他们能够用纯Java API 来编写数据库应用程序。然而各个开发商的接口并不完全相同,所以开发环境的变化会带来一定的配置变化。本文主要集合了不同数据库的连接方式。
一、连接各种数据库方式速查表
    下面罗列了各种数据库使用JDBC连接的方式,可以作为一个手册使用。

    1
Oracle8/8i/9i数据库(thin模式) 
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); 
String url="jdbc:oracle:thin:@localhost:1521:orcl"; //orcl
为数据库的SID 
String user="test"; 
String password="test"; 
Connection conn= DriverManager.getConnection(url,user,password); 

    2
DB2数据库 
Class.forName("com.ibm.db2.jdbc.app.DB2Driver ").newInstance(); 
String url="jdbc:db2://localhost:5000/sample"; //sample
为你的数据库名 
String user="admin"; 
String password=""; 
Connection conn= DriverManager.getConnection(url,user,password); 

    3
Sql Server7.0/2000数据库 
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance(); 
String url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb"; 
//mydb
为数据库 
String user="sa"; 
String password=""; 
Connection conn= DriverManager.getConnection(url,user,password); 

    4
Sybase数据库 
Class.forName("com.sybase.jdbc.SybDriver").newInstance(); 
String url =" jdbc:sybase:Tds:localhost:5007/myDB";//myDB
为你的数据库名 
Properties sysProps = System.getProperties(); 
SysProps.put("user","userid"); 
SysProps.put("password","user_password"); 
Connection conn= DriverManager.getConnection(url, SysProps); 

    5
Informix数据库 
Class.forName("com.informix.jdbc.IfxDriver").newInstance(); 
String url = 
"jdbc:informix-sqli://123.45.67.89:1533/myDB:INFORMIXSERVER=myserver; 
user=testuser;password=testpassword"; //myDB
为数据库名 
Connection conn= DriverManager.getConnection(url); 

    6
MySQL数据库 
Class.forName("org.gjt.mm.mysql.Driver").newInstance(); 
String url ="jdbc:mysql://localhost/myDB?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1" 
//myDB
为数据库名 
Connection conn= DriverManager.getConnection(url); 

    7
PostgreSQL数据库 
Class.forName("org.postgresql.Driver").newInstance(); 
String url ="jdbc:postgresql://localhost/myDB" //myDB
为数据库名 
String user="myuser"; 
String password="mypassword"; 
Connection conn= DriverManager.getConnection(url,user,password); 


    8、access
数据库直连用ODBC的
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver") ;
String url="jdbc:odbc:Driver={MicroSoft Access Driver (*.mdb)};DBQ="+application.getRealPath("/Data/ReportDemo.mdb");
Connection conn = DriverManager.getConnection(url,"","");
 Statement stmtNew=conn.createStatement() ;

二、JDBC连接MySql方式
    下面是使用JDBC连接MySql的一个小的教程     

    1、查找驱动程序
    MySQL目前提供的java驱动程序为Connection/J,可以从MySQL官方网站下载,并找到mysql-connector-java-3.0.15-ga-bin.jar文件,此驱动程序为纯java驱动程序,不需做其他配置。

    2、动态指定classpath
    如果需要执行时动态指定classpath,就在执行时采用-cp方式。否则将上面的.jar文件加入到classpath环境变量中。

    3、加载驱动程序
try{
    Class.forName(com.mysql.jdbc.Driver);
    System.out.println(Success loading Mysql Driver!);
}catch(Exception e)
{
    System.out.println(Error loading Mysql Driver!);
    e.printStackTrace();
}

    4、设置连接的url
    jdbc:mysql://localhost/databasename[?pa=va][&pa=va]

三、以下列出了在使用JDBC来连接Oracle数据库时可以使用的一些技巧,这些技巧能够使我们更好地发挥系统的性能和实现更多的功能(系转载)。

  1、在客户端软件开发中使用Thin驱动程序

  在开发Java软件方面,Oracle的数据库提供了四种类型的驱动程序,二种用于应用软件、applets、 servlets等客户端软件,另外二种用于数据库中的Java存储过程等服务器端软件。在客户机端软件的开发中,我们可以选择OCI驱动程序或Thin 驱动程序。OCI驱动程序利用Java本地化接口(JNI),通过Oracle客户端软件与数据库进行通讯。Thin驱动程序是纯Java驱动程序,它直 接与数据库进行通讯。为了获得最高的性能,Oracle建议在客户端软件的开发中使用OCI驱动程序,这似乎是正确的。但我建议使用Thin驱动程序,因 为通过多次测试发现,在通常情况下,Thin驱动程序的性能都超过了OCI驱动程序。

  2、关闭自动提交功能,提高系统性能

  在第一次建立与数据库的连接时,在缺省情况下,连接是在自动提交模式下的。为了获得更好的性能,可以通过调用带布尔值false参数的Connection类的setAutoCommit()方法关闭自动提交功能,如下所示:

  conn.setAutoCommit(false);

  值得注意的是,一旦关闭了自动提交功能,我们就需要通过调用Connection类的commit()和rollback()方法来人工的方式对事务进行管理。

  3、在动态SQL或有时间限制的命令中使用Statement对象

  在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用 Statement对象。无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象 时,每次执行一个SQL命令时,都会对它进行解析和编译。这可能会使你认为,使用PreparedStatement对象比使用Statement对象的 速度更快。然而,我进行的测试表明,在客户端软件中,情况并非如此。因此,在有时间限制的SQL操作中,除非成批地处理SQL命令,我们应当考虑使用 Statement对象。

  此外,使用Statement对象也使得编写动态SQL命令更加简单,因为我们可以将字符串连接在一起,建立一个有效的SQL命令。因此,我认为,Statement对象可以使动态SQL命令的创建和执行变得更加简单。

  4、利用helper函数对动态SQL命令进行格式化

  在创建使用Statement对象执行的动态SQL命令时,我们需要处理一些格式化方面的问题。例如,如果我们 想创建一个将名字O'Reilly插入表中的SQL命令,则必须使用二个相连的“''”号替换O'Reilly中的“'”号。完成这些工作的最好的方法是 创建一个完成替换操作的helper方法,然后在连接字符串心服用公式表达一个SQL命令时,使用创建的helper方法。与此类似的是,我们可以让 helper方法接受一个Date型的值,然后让它输出基于Oracle的to_date()函数的字符串表达式。

  5、利用PreparedStatement对象提高数据库的总体效率

  在使用PreparedStatement对象执行SQL命令时,命令被数据库进行解析和编译,然后被放到命令 缓冲区。然后,每当执行同一个PreparedStatement对象时,它就会被再解析一次,但不会被再次编译。在缓冲区中可以发现预编译的命令,并且 可以重新使用。在有大量用户的企业级应用软件中,经常会重复执行相同的SQL命令,使用PreparedStatement对象带来的编译次数的减少能够 提高数据库的总体性能。如果不是在客户端创建、预备、执行PreparedStatement任务需要的时间长于Statement任务,我会建议在除动 态SQL命令之外的所有情况下使用PreparedStatement对象。

  6、在成批处理重复的插入或更新操作中使用PreparedStatement对象

  如果成批地处理插入和更新操作,就能够显著地减少它们所需要的时间。Oracle提供的Statement和 CallableStatement并不真正地支持批处理,只有PreparedStatement对象才真正地支持批处理。我们可以使用 addBatch()和executeBatch()方法选择标准的JDBC批处理,或者通过利用PreparedStatement对象的 setExecuteBatch()方法和标准的executeUpdate()方法选择速度更快的Oracle专有的方法。要使用Oracle专有的批 处理机制,可以以如下所示的方式调用setExecuteBatch():
PreparedStatement pstmt3D null;
try {
((OraclePreparedStatement)
pstmt).setExecuteBatch(30);
...
pstmt.executeUpdate();
}

  调用setExecuteBatch()时指定的值是一个上限,当达到该值时,就会自动地引发SQL命令执行, 标准的executeUpdate()方法就会被作为批处理送到数据库中。我们可以通过调用PreparedStatement类的sendBatch ()方法随时传输批处理任务。

  7、使用Oracle locator方法插入、更新大对象(LOB)

  Oracle的PreparedStatement类不完全支持BLOB和CLOB等大对象的处理,尤其是 Thin驱动程序不支持利用PreparedStatement对象的setObject()和setBinaryStream()方法设置BLOB的 值,也不支持利用setCharacterStream()方法设置CLOB的值。只有locator本身中的方法才能够从数据库中获取LOB类型的值。 可以使用PreparedStatement对象插入或更新LOB,但需要使用locator才能获取LOB的值。由于存在这二个问题,因此,我建议使用 locator的方法来插入、更新或获取LOB的值。

  8、使用SQL92语法调用存储过程

  在调用存储过程时,我们可以使用SQL92或Oracle PL/SQL,由于使用Oracle PL/SQL并没有什么实际的好处,而且会给以后维护你的应用程序的开发人员带来麻烦,因此,我建议在调用存储过程时使用SQL92。

  9、使用Object SQL将对象模式转移到数据库中

  既然可以将Oracle的数据库作为一种面向对象的数据库来使用,就可以考虑将应用程序中的面向对象模式转到数 据库中。目前的方法是创建Java bean作为伪装的数据库对象,将它们的属性映射到关系表中,然后在这些bean中添加方法。尽管这样作在Java中没有什么问题,但由于操作都是在数据 库之外进行的,因此其他访问数据库的应用软件无法利用对象模式。如果利用Oracle的面向对象的技术,可以通过创建一个新的数据库对象类型在数据库中模 仿其数据和操作,然后使用JPublisher等工具生成自己的Java bean类。如果使用这种方式,不但Java应用程序可以使用应用软件的对象模式,其他需要共享你的应用中的数据和操作的应用软件也可以使用应用软件中的 对象模式。

  10、利用SQL完成数据库内的操作

  我要向大家介绍的最重要的经验是充分利用SQL的面向集合的方法来解决数据库处理需求,而不是使用Java等过程化的编程语言。

  如果编程人员要在一个表中查找许多行,结果中的每个行都会查找其他表中的数据,最后,编程人员创建了独立的 UPDATE命令来成批地更新第一个表中的数据。与此类似的任务可以通过在set子句中使用多列子查询而在一个UPDATE命令中完成。当能够在单一的 SQL命令中完成任务,何必要让数据在网上流来流去的?我建议用户认真学习如何最大限度地发挥SQL的功能。

posted @ 2006-08-29 22:01 Alex 阅读(376) | 评论 (0)编辑 收藏

一、 引子

在大学的数据结构这门课上,树是最重要的章节之一。还记得树是怎么定义的吗?树 (Tree) n(n≥0) 个结点的有限集 T T 为空时称为空树,否则它满足如下两个条件:

(1)    有且仅有一个特定的称为根 (Root) 的结点;

(2)   其余的结点可分为 m(m≥0) 个互不相交的子集 Tl T2 Tm ,其中每个子集本身又是一棵树,并称其为根的子树 (SubTree)

上面给出的递归定义刻画了树的固有特性:一棵非空树是由若干棵子树构成的,而子树又可由若干棵更小的子树构成。而这里的子树可以是叶子也可以是分支。

今天要学习的组合模式就是和树型结构以及递归有关系。

 

二、 定义与结构

组合 (Composite) 模式的其它翻译名称也很多,比如合成模式、树模式等等。在《设计模式》一书中给出的定义是:将对象以树形结构组织起来,以达成 部分-整体 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。

从定义中可以得到使用组合模式的环境为: 在设计中想表示对象的 部分 整体 层次结构;希望用户忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。

看下组合模式的组成。

1)         抽象构件角色 Component :它为组合中的对象声明接口,也可以为共有接口实现缺省行为。

2)       树叶构件角色 Leaf :在组合中表示叶节点对象 —— 没有子节点,实现抽象构件角色声明的接口。

3)       树枝构件角色 Composite :在组合中表示分支节点对象 —— 有子节点,实现抽象构件角色声明的接口;存储子部件。

下图为组合模式的类图表示。

 

如图所示:一个 Composite 实例可以像一个简单的 Leaf 实例一样,可以把它传递给任何使用 Component 的方法或者对象,并且它表现的就像是一个 Leaf 一样。

可以看出来,使用组合模式使得这个设计结构非常灵活,在下面的例子中会得到进一步的印证。

      

三、 安全性与透明性

组合模式中必须提供对子对象的管理方法,不然无法完成对子对象的添加删除等等操作,也就失去了灵活性和扩展性。但是管理方法是在 Component 中就声明还是在 Composite 中声明呢?

一种方式是在 Component 里面声明所有的用来管理子类对象的方法,以达到 Component 接口的最大化(如下图所示)。目的就是为了使客户看来在接口层次上树叶和分支没有区别 —— 透明性。但树叶是不存在子类的,因此 Component 声明的一些方法对于树叶来说是不适用的。这样也就带来了一些安全性问题。

 

另一种方式就是只在 Composite 里面声明所有的用来管理子类对象的方法(如下图所示)。这样就避免了上一种方式的安全性问题,但是由于叶子和分支有不同的接口,所以又失去了透明性。

    


    《设计模式》一书认为:在这一模式中,相对于安全性,我们比较强调透明性。对于第一种方式中叶子节点内不需要的方法可以使用空处理或者异常报告的方式来解决。

 

四、 举例

这里以 JUnit 中的组合模式的应用为例(JUnit)。

JUnit 是一个单元测试框架,按照此框架下的规范来编写测试代码,就可以使单元测试自动化。为了达到“自动化”的目的, JUnit 中定义了两个概念: TestCase TestSuite TestCase 是对一个类或者 jsp 等等编写的测试类;而 TestSuite 是一个不同 TestCase 的集合,当然这个集合里面也可以包含 TestSuite 元素,这样运行一个 TestSuite 会将其包含的 TestCase 全部运行。

然而在真实运行测试程序的时候,是不需要关心这个类是 TestCase 还是 TestSuite ,我们只关心测试运行结果如何。这就是为什么 JUnit 使用组合模式的原因。

JUnit 为了采用组合模式将 TestCase TestSuite 统一起来,创建了一个 Test 接口来扮演抽象构件角色,这样原来的 TestCase 扮演组合模式中树叶构件角色,而 TestSuite 扮演组合模式中的树枝构件角色。下面将这三个类的有关代码分析如下:

 

//Test 接口 —— 抽象构件角色

public interface Test {

       /**

        * Counts the number of test cases that will be run by this test.

        */

       public abstract int countTestCases();

       /**

        * Runs a test and collects its result in a TestResult instance.

        */

       public abstract void run(TestResult result);

}

 

//TestSuite 类的部分有关源码 ——Composite 角色,它实现了接口 Test

public class TestSuite implements Test {

// 用了较老的 Vector 来保存添加的 test

       private Vector fTests= new Vector(10);

       private String fName;

       …… 

/**

        * Adds a test to the suite.

        */

       public void addTest(Test test) {          

// 注意这里的参数是 Test 类型的。这就意味着 TestCase TestSuite 以及以后实现 Test 接口的任何类都可以被添加进来

              fTests.addElement(test);

       }

       ……

       /**

        * Counts the number of test cases that will be run by this test.

        */

       public int countTestCases() {

              int count= 0;

              for (Enumeration e= tests(); e.hasMoreElements(); ) {

                     Test test= (Test)e.nextElement();

                     count= count + test.countTestCases();

              }

              return count;

       }

       /**

        * Runs the tests and collects their result in a TestResult.

        */

       public void run(TestResult result) {

              for (Enumeration e= tests(); e.hasMoreElements(); ) {

                    if (result.shouldStop() )

                           break;

                     Test test= (Test)e.nextElement();
                           //关键在这个方法上面

                     runTest(test, result);

              }

       }
            //这个方法里面就是递归的调用了,至于你的Test到底是什么类型的只有在运行的时候得知
            public void runTest(Test test, TestResult result) {
                   test.run(result);
            }

……

}

 

//TestCase 的部分有关源码 ——Leaf 角色,你编写的测试类就是继承自它

public abstract class TestCase extends Assert implements Test {

       ……

       /**

        * Counts the number of test cases executed by run(TestResult result).

        */

       public int countTestCases() {

              return 1;

       }

/**

        * Runs the test case and collects the results in TestResult.

        */

       public void run(TestResult result) {

              result.run(this);

       }

……

}

       可以看出这是一个偏重安全性的组合模式。因此在使用TestCase和TestSuite时,不能使用Test来代替。

 

五、 优缺点

从上面的举例中可以看到,组合模式有以下优点:

1)         使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关心自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。

2)       更容易在组合体内加入对象部件 . 客户端不必因为加入了新的对象部件而更改代码。这一点符合开闭原则的要求,对系统的二次开发和功能扩展很有利!

当然组合模式也少不了缺点:组合模式不容易限制组合中的构件。

 

六、 总结

组合模式是一个应用非常广泛的设计模式,在前面已经介绍过的解释器模式、享元模式中都是用到了组合模式。它本身比较简单但是很有内涵,掌握了它对你的开发设计有很大的帮助。

这里写下了我学习组合模式的总结,希望能给你带来帮助,也希望您能给与指正。
posted @ 2006-08-29 21:44 Alex 阅读(451) | 评论 (0)编辑 收藏

一、 引子

这是一个很简单的模式,却被非常广泛的使用。之所以简单是因为在这个模式中仅仅使用到了继承关系。

继承关系由于自身的缺陷,被专家们扣上了 罪恶 的帽子。 使用委派关系代替继承关系 尽量使用接口实现而不是抽象类继承 等等专家警告,让我们这些菜鸟对继承 另眼相看

其实,继承还是有很多自身的优点所在。只是被大家滥用的似乎缺点更加明显了。合理的利用继承关系,还是能对你的系统设计起到很好的作用的。而模板方法模式就是其中的一个使用范例。

 

二、 定义与结构

GOF 给模板方法( Template Method )模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。这里的算法的结构,可以理解为你根据需求设计出来的业务流程。特定的步骤就是指那些可能在内容上存在变数的环节。

可以看出来,模板方法模式也是为了巧妙解决变化对系统带来的影响而设计的。使用模板方法使系统扩展性增强,最小化了变化对系统的影响。这一点,在下面的举例中可以很明显的看出来。

来看下这个简单模式的结构吧:

1)         AbstractClass (抽象类):定义了一到多个的抽象方法,以供具体的子类来实现它们;而且还要实现一个模板方法,来定义一个算法的骨架。该模板方法不仅调用前面的抽象方法,也可以调用其他的操作,只要能完成自身的使命。

2)         ConcreteClass (具体类):实现父类中的抽象方法以完成算法中与特定子类相关的步骤。

 

下面是模板方法模式的结构图。直接把《设计模式》上的图拿过来用下:

 

三、 举例

还是在我刚刚分析完源码的 JUnit 中找个例子吧。 JUnit 中的 TestCase 以及它的子类就是一个模板方法模式的例子。在 TestCase 这个抽象类中将整个测试的流程设置好了,比如先执行 Setup 方法初始化测试前提,在运行测试方法,然后再 TearDown 来取消测试设置。但是你将在 Setup TearDown 里面作些什么呢?鬼才知道呢!!因此,而这些步骤的具体实现都延迟到子类中去,也就是你实现的测试类中。

来看下相关的源代码吧。

这是 TestCase 中,执行测试的模板方法。你可以看到,里面正像前面定义中所说的那样,它制定了“算法”的框架——先执行 setUp 方法来做下初始化,然后执行测试方法,最后执行 tearDown 释放你得到的资源。

public void runBare() throws Throwable {

       setUp();

       try {

              runTest();

       }

       finally {

              tearDown();

       }

}

这 就是上面使用的两个方法。与定义中不同的是,这两个方法并没有被实现为抽象方法,而是两个空的无为方法(被称为钩子方法)。这是因为在测试中,我们并不是 必须要让测试程序使用这两个方法来初始化和释放资源的。如果是抽象方法,则子类们必须给它一个实现,不管用到用不到。这显然是不合理的。使用钩子方法,则 你在需要的时候,可以在子类中重写这些方法。

protected void setUp() throws Exception {

}

protected void tearDown() throws Exception {

}

 

四、 适用情况

根据上面对定义的分析,以及例子的说明,可以看出模板方法适用于以下情况:

1)         一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。

2)         各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。其实这可以说是一种好的编码习惯了。

3)         控制子类扩展。模板方法只在特定点调用操作,这样就只允许在这些点进行扩展。比如上面 runBare ()方法就只在 runTest 前面适用 setUp 方法。如果你不愿子类来修改你的模板方法定义的框架,你可以采用两种方式来做:一是在 API 中不体现出你的模板方法;二、将你的模板方法置为 final 就可以了。

       可以看出,使用模板方法模式可以将代码的公共行为提取出来,达到复用的目的。而且,在模板方法模式中,是由父类的模板方法来控制子类中的具体实现。这样你在实现子类的时候,根本不需要对业务流程有太多的了解。

 

五、 总结

匆匆忙忙写完了。希望大家指正!
posted @ 2006-08-29 21:39 Alex 阅读(249) | 评论 (0)编辑 收藏

仅列出标题
共15页: First 上一页 4 5 6 7 8 9 10 11 12 下一页 Last