tfc2005

BlogJava 首页 新随笔 联系 聚合 管理
  8 Posts :: 0 Stories :: 2 Comments :: 0 Trackbacks

2005年6月13日 #

JDOM and XML 解析,Part 1


JDOM让XML在Java中比以前任何时候都容易使用

以前,可能使用一部分Java的一部分库类来操作XML数据结构。那么,JDOM(Java Document Object Model)的要点又是什么,为什么开发人员需要它?
对Java-optimized XML数据操作的JDOM是开放源代码。虽然它很像World Wide Web联盟(W3C)的DOM,但是它是一个可供选择的对象模型,这个模型不是建立在DOM或者基于DOM的模型之上的。主要的不同是DOM作为language-neutral建立并且用于HTML页面的JavaScript操作,JDOM作为Java-specific建立,因此具有了Java 本身的优点,包括method overloading,collections,reflection,and familiar。对于Java编程人员来说,JDOM 倾向于探索更多的本身的能力和好处。这个很象Java-optimized RMI (remote method invocation) 库探索更多本身的能力,而不是象language-neutral CORBA (Common Object Request Broker Architecture)那样。

在jdom.org上的开放代码Apache-style (commercial-friendly)版本中,可以找到JDOM。它是协作设计、开发的产物,它拥有多达3,000多个志愿人员。这个库同时也被Sun's Java Community Process (JCP)作为Java Specification Request (JSR-102)接受,同时正在一步一步的成为一个正式的Java规范。

下面一系列的文章将针对JDOM提供技术说明。文章提供了关于重要类的信息。下篇文章将给一些关于怎样在的Java程序里面使用JDOM的内容。
 
JDOM 包结构

JDOM 库由六个包组成。第一个org.jdom 包支持包括了一个XML文档,同时包括:Attribute,CDATA,Comment,DocType,Document,Element,EntityRef,Namespace,ProcessingInstruction,以及Text。如果对XML很熟悉,类名将帮助理解类的含义。
下来的org.jdom.input包,它支持类用于建立XML文档。最主要也是重要的类是SAXBuilder。SAXBuilder 通过监听输入简单的针对XML (SAX) 事件的API建立文档。当想从一个文件或者其它流来建立文档的时候,可以使用SAXBuilder。它使用SAX 分析器来读取流,然后依据SAX分析器的返回来建立文档。这个设计的一个好的方面是分析器的工作越快,,SAXBuilder的工作也就越快。另外一个主要的输入类是DOMBuilder。DOMBuilder从DOM树建立。这个类很容易从先前的DOM树中获得,而要一个JDOM版本代替。。
对于设计人员是没有限制的。例如:现在Xerces要在比SAX低水平时操作Xerces Native Interface (XNI),它可以作出判断去做一个XNIBuilder来支持一些分析器的规则,不被SAX暴露。ResultSetBuilder是一种受欢迎的人,他们投稿给JDOM工程。这样让JDBC的更加坚固,并且建立了SQL的XML文档表达模式,包括了许多关于那些是组成部分那些是属性的配置。
org.jdom.outpu包支持类输出XML文档。最重要的类是XMLOutputter。它为了将文档输出到文件,流,以及sockets将其转化为字节流。类XMLOutputter又很多奇特的配置选项来支持原始输出,恰当的输出,或者压缩输出以及其它方式。它是一个相当复杂的类。这也许就是为什么这个不在DOM Level 2存在的原因。
另外的输出包括了类SAXOupputter,它根据文档内容产生了SAX事件。虽然表面上看很神秘,但是这个类在XSLT转换中是非常有用的,因为SAX事件比字节更有效传输文档数据。这里同样有类DOMOutputter,它建立了DOM树文档表示。一个有趣的设计是 JTreeOutputter,它只有很少的一些代码,它建立了Jtree来文档表示。用ResultSetBuilder联合,使用简单的代码,就可以完成SQL查询和队列遍历。
注意,不象DOM,开发人员不会将文档打包。这就产生了一个模式,可以使用类保存数据,许多的类构造了数据,并且许多其它的类放弃了数据。
org.jdom.transform 和 org.jdom.xpath 包的类支持建立XSLT转化和Xpath查找。
最后是org.jdom.adapters包中类支持在DOM内部进行交流。库用户不需要访问这个库的类。每个DOM执行过程都对每步任务有不同的方法名,所以适配器将标准调用转换为parser-specific调用。Java API for XML Processing (JAXP)对于这个问题给出其它的一些解决方法,实际上是对于类的需要,但是一些类仍然保留,因为不是所有的分析器都支持JAXP,或者JAXP不是任何地方都安装了,并且版本正确。

建立一个文档
文档由org.jdom.Documentclass来辅助建立。可以象下面这样建立一个文档:

// This builds: <root/>
Document doc = new Document(new Element("root"));


或者可以由一个文件,流,系统ID,或者URL建立文档:

// This builds a document of whatever's in the given resource
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(url);


在JDOM中将一些调用组合在一起,可以很容易的建立一个简单的文档:

// This builds: <root>This is the root</root>
Document doc = new Document();
Element e = new Element("root");
e.setText("This is the root");
doc.addContent(e);


如果是个有经验的用户,或许可以使用"method chaining,",在有多个方法的时候依次被调用。因为固定的方法返回产生的对象。如下:

Document doc = new Document(
  new Element("root").setText("This is the root"));


作为比较,这里给出如何使用JAXP/DOM建立同样的文档:

// JAXP/DOM
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element root = doc.createElement("root");
Text text = doc.createText("This is the root");
root.appendChild(text);
doc.appendChild(root);


用SAXBuilder建立

早期,SAXBuilder建立了一个可以从任何byte-oriented资源建立文档的机制。默认的SAXBuilder()在后台使用JAXP选择SAX剖析器。如果要改变剖析器,可以设置javax.xml.parsers.SAXParserFactory系统特性指向SAXParser Factory,由的剖析器保证运行。对于Oracle9i Release 2 XML剖析器,可以这样使用:

java -Djavax.xml.parsers.SAXParserFactory=
oracle.xml.jaxp.JXSAXParserFactory YourApp


对于Xerces 剖析器,可以这样来代替:

java -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp
.SAXParserFactoryImpl YourApp


如果没有安装JAXP,SAXBuilder默认为Apache Xerces。一旦建立一个 SAXBuilder 实例,就可以设置一些道具,包括:

setValidation(boolean validate)


这个方法在建立中告诉剖析器针对Document Type Definition (DTD) 是否有效。默认是关闭的。使用DTD 是关于文档的 DocType的一个参考。针对其它DTD是不可能有效的,因为没有剖析器支持这个能力。

setIgnoringElementContentWhitespace(boolean ignoring)


这个方法告诉剖析器是否忽视调用whitespace的元素目录。每个XML 1.0 说明,在元素目录中的whitespace 必须剖析器保存,但是针对一个DTD确认需要确认剖析器知道某些文档的部分没有被声明支持whitespace,则任何在那个区域的whitespace都是"ignorable." 这个默认是关闭的。这个对于部分存储并不是最好的,除非要"round trip"一个文档,并且将输入作为输出。注意,这个标记只有确认打开时是激活的,而且确认使得执行速度减慢,所以最好在确认已经使用的情况下再使用。

setFeature(String name,String value)


这个方法再SAX剖析器的后台做一些设置。这是个无处理的传递调用,所以要非常小心的使用,因为设置错误(例如tweaking namespaces)将会毁坏JDOM。此外,依靠任何parser-specific都有一定的便捷性。这个调用对于计划确认的授权很有用。

setProperty(String name,Object value)


这个方法再SAX剖析器的后台做一些设置。这同样是个无处理的传递调用,对于高级用户同时拥有了高危险性和很高的适用性,特别针对于计划确认。将方法组合,下面的代码在确认打开同时忽视ignorable whitespace的情况下,使用JAXP-selected剖析器读取当地文件。

SAXBuilder builder = new SAXBuilder();
builder.setValidation(true);
builder.setIgnoringElementContentWhitespace(true);
Document doc = builder.build(new File("/tmp/foo.xml"));


通过XMLOutputter建立文档

一个文档可以以多种格式输出,但是最常用的是字节流。在JDOM中,XMLOutputter类提供了这种特性。默认的构造器试图无错误的输出在内存中存储的文档。下面的代码提供了针对文件原始的表示。

// Raw output
XMLOutputter outp = new XMLOutputter();
outp.output(doc,fileStream);


如果担心whitespace,可以清除空白:

// Compressed output
outp.setTextTrim(true);
outp.output(doc,socketStream);


如果要打印出人们习惯的样式,可以添加缩进的空白并且换到新行:

outp.setTextTrim(true);
outp.setIndent("  ");
outp.setNewlines(true);
outp.output(doc,System.out);


当打印一个已经格式化空白的文档,需要确认是可以清理的。否则,必须格式化,这可能很难看。

操作元素树

JDOM让操作元素树很容易。得到根部元素:

Element root = doc.getRootElement();
To get a list of all its child elements:
List allChildren = root.getChildren();


通过名字得到指定元素:

List namedChildren = root.getChildren("name");


根据给定名得到第一个元素:

Element child = root.getChild("name");


getChildren()调用返回的List是一个java.util.List,一个所有Java程序员都知道的List接口操作。列表的活动性很有趣。任何List的改变将会在后台的文档中显示出来。

// Remove the fourth child
allChildren.remove(3);
// Remove children named "jack"
allChildren.removeAll(root.getChildren("jack"));
// Add a new child,at the tail or at the head
allChildren.add(new Element("jane"));
allChildren.add(0,new Element("jill"));


使用List意味着不需要添加众多的方法就可以进行多个元素的操作。为了方便,无论是在最后添加元素还是删除已命名的元素,元素本身都拥有了相应的方法,则不需要在操作之前获得List:

root.removeChildren("jill");
root.addContent(new Element("jenny"));


JDOM的一个优点是它可以很容易在文档内部或者在文档之间移动元素。代码都是一样的:
 
Element movable = new Element("movable");
parent1.addContent(movable);    // place
parent1.removeContent(movable); // remove
parent2.addContent(movable);    // add


使用DOM,移动元素是不容易的,因为在DOM中,元素是和建立工具绑定的。因而,DOM元素在文档之间移动必须是有关系的"imported"。
关于JDOM有一件必须注意的就是移动元素之前先要添加,所以你不能建立一个循环树。有个detach()方法可以分离/添加一条线:

parent3.addContent(movable.detach());


如果在添加一个元素到另外一个父接点之前忘记了分离它,库将会抛出一个异常。库也会检查元素的名字和内容,确认它们不包括不适当的字符,就像空白。它同样还有其它的规则,例如只有一个根元素,相容的名字空间声明,注释和CDATA部分里面没有被禁止的字符序列,等等。这种特性使得在进程中尽可能早的去检查"well-formedness"错误变成可能。

操作元素属性
元素属性:

<table width="100%" border="0"> ..。</table>


对于元素,可以用命名属性值任何名字:

String val = table.getAttributeValue("width");


对于执行特殊的操作,例如类型变化,也可以作为对象得到属性:
 
Attribute border = table.getAttribute("border");
int size = border.getIntValue();


使用setAttribute()设置或者改变属性:

table.setAttribute("vspace","0");


使用removeAttribute()删除一个属性:
 
table.removeAttribute("vspace");


关于文本内容的处理

元素文本内容就像:

<description>
  A cool demo
</description>


在JDOM中,文本直接可以被调用使用:

String desc = description.getText();


只要注意,因为XML 1.0规范需要空白被保存,返回"\n A cool demo\n"。当然,作为实际编程人员经常的不想面对空白的格式,所以当忽视了周围的空白的时有了简单的方法:

String betterDesc = description.getTextTrim();


如果需要空白在图片外,需要getTextNormalize() 方法规格化空白。对于文本内容是很便利的:

<description>
  Sometimes you have text content with formatting
  space within the string.
</description>


通过 setText()方法改变文本内容:

description.setText("A new description");


文本里面的任何特殊字符都被相应的字符解释,并且空格在输出中有了恰当的意义。例如:

element.setText("<xml/> content");


内部存储以字符保存文字串。tore will keep that literal string as characters。不需要盲目的对内容分解。在输出时:

<xml/> content<elt>


这些行为保持了早期setText()调用的语义。如果需要XML在一个元素内被支持,必须添加适当的JDOM子元素对象。
JDOM同样可以处理 CDATA 部分。一个CDATA部分需要的一个text文本不需要被分析。它包括了简单一些HTML和XML内容,不含有< and >以及空格。建立一个CDATA 部分,只要用CDATA对象包裹字符串:

element.addContent(new CDATA("<xml/> content"));


混和内容的行为

一些元素包括了很多的内容,例如whitespace,comments,text,child elements,等等:

<table>

  <!-- Some comment -->
  Some text
  <tr>Some child element</tr>
</table>


当一个元素包括了文本和子元素,被称为混和内容。处理混和内容本来是很困难的,但是通过JDOM变得简单。标准的应用-找回文本内容和遍历元素-很简单:

String text = table.getTextTrim();  // "Some text"
Element tr = table.getChild("tr");  // A straight reference


对于大多数高级用户,都需要注释,空白,处理说明和实体参考,未处理的混和文本作为List是可行的:

List mixedCo = table.getContent();
Iterator itr = mixedCo.iterator();
while (itr.hasNext()) {
  Object o = i.next();
  if (o instanceof Comment) {
    ...
  }
  // Types include Comment,Element,CDATA,DocType,
  // ProcessingInstruction,EntityRef,and Text
}


作为子元素列表,改变原始列表影响了后台文档:

// Remove the Comment。 It's "1" because "0" is a whitespace block.
mixedCo.remove(1);


更多关于DocType,ProcessingInstruction,和EntityRef classes的细节可以在jdom.org上的API文档中找到。

 

ORACLE XML 工具


XML Developer Kit (XDK)是免费的XML工具库,它是有Oracle提供给开发人员的。它包括了XML剖析器和一个XSLT翻译引擎,能和JDOM使用。在OracleXML主页上,你可以找到更多的信息关于这些工具,oracle.com/xml。
下载剖析器,寻找名为"XDK for Java."的XML Developer Kit。在左边的专栏点击"Software"来找到下载连接。一旦你打开分类,文件xalparserv2.jar就包括了剖析器。
使用剖析器的默认来配置JDOM和其它软件,你需要设置JAXP javax.xml.parsers.SAXParserFactory 系统为oracle.xml.jax.JXSAXParserFactory。这将告诉JAXP选择了Oracle剖析器。简单的方法是在命令行输入:

[CODE]java -Djavax.xml.parsers.SAXParserFactory=
oracle.xml.jaxp.JXSAXParserFactory


同样可以设定:

System.setProperty("jaxax.xml.parsers.SAXParserFactory","oracle.xml.jaxp.JXSAXParserFactory");


除了XDK之外,Oracle提供了原先的XML Oracle9i Database Release 2仓库。Oracle9i XML Database (XDB) 是应用很多,本身的XML存储。它充分的吸收了W3C XML 在Oracle9i Database中的数据模式并且为XML遍历和查询提供了新的标准接入方法。关于XDB,得到了数据库相关的优点以及XML技术的优点。

posted @ 2005-06-17 13:10 佟福春 阅读(381) | 评论 (0)编辑 收藏

jdom轻松整合java和xml

 概述:

jdom是用java语言读、写、操作XML的新api函数。Jason Hunter Brett McLaughlin公开发布了它的测试版本。在直觉、简单和高效的前提下,这些api函数被最大限度的优化。在接下来的篇幅里,Hunter   McLaughlin介绍怎么用jdom去读写一个已经存在的XML文档。

Jdom是一个开源的api,它以直接易懂的方式向java程序员描述XML文档和文档的内容。就象名字揭示的那样,jdom是为java优化的。为使用XML文档提供一个低消耗的方法。Jdom的使用者可以不必掌握太多的XML的知识就可以完成想要的操作。

Jdom可以和已有的XML技术如Simple API for XML (SAX)和 Document Object Model (DOM)相互协作.然而,它并不是简单的从这些api中提取出一些。Jdom从这些已存在的技术中吸收了好的方面,自己建立了一套新的类和接口,用一个jdom用户的话来说就是:“这些接口是我从一开始阅读org.w3c.dom就期待的”;jdom可以读入SAX或是DOM的内容,也可以输出SAX或DOM可以接收的格式。这个能力可以使jdom很好的和已有的用SAX或DOM建立的系统之间整合。

Jdom的原则

第一条并且是最重要的一条就是jdom的api函数被设计成对java程序员来说是简单易懂的。其他的XML解析函数被设计成语言通用的(支持相同的api函数在java,c++,甚至是javascript中)。Jdom利用了java的优秀的特征,如:方法重载、回收机制,和后台处理等。

为了能够简单易用,这些函数不得不以程序员喜欢的形式来描绘XML文档。例如:程序员想的到的一个元素的文本内容是什么样子的呢?

<element>This is my text content</element>

在一些api中,元素的文本内容仅被当作是一个元素的孩子节点。从技术角度来说,这个设计需要下面的代码才能访问到元素的内容:

String content = element.getFirstChild().getValue();

Jdom用一种更简单易用的方法来取得元素的内容:

String text = element.getText();

Jdom尽可能的减少程序员的工作量。依据拇指规则,jdom应该用20%或是更少的努力来完成80%或是更多的java/xml方面的问题。这并不是说jdom只是支持80%的XML规范(实际上我们希望jdom100%的支持XML规范)。这个拇指规则是说有些东西可以加进去,但是没有必要。这些api函数应该保持简洁。

Jdom的第二条原则是说jdom应该是快速的和轻量级的。调入和执行文档应该快速,内存的消耗应该尽量小。Jdom的设计明显是遵循这个原则。例如,就算在开始的时候,不太协调的操作已经比DOM快,但是比SAX显的粗糙。尽管这样,jdom还是有许多SAX没有的优点。

你需要jdom吗?

那么,你需要jdom吗?这真是一个很好的问题。已经有了存在的标准,为什么还要去发明一个新的呢?答案是jdom解决了现有的标准解决不了的问题。

DOM完全在内存中描述一个元素树。它是一个大的api,被设计操作几乎所有可能的XML任务。它也必须有相同的api去支持不同的语言。因为这些限制,对那些习惯使用java的特征,如方法重载、简单的set,get方法的java程序员来说,就很不习惯。DOM还需要大量的内存和较高的主频,这使它很难和许多轻量级的web应用一起工作。

 SAX没有在内存中建立一个元素树,它用事情发展的方式来描述。例如:它报告每个读到的开始标记和结束标记。这种处理方式使它成为一个轻量级的快速读取的api。然而,这种事件处理方式对服务器端的java程序员来说不够直观。SAX也不支持修改XML文档和随机读取。

Jdom试图组合DOM和SAX的优点。它被设计成一个可以在小内存上快速执行轻量级api 。jdom也支持随机读取整个文档,但是令人惊奇的是它并不需要把整个文档读到内存中。这个api支持未来的当需要时才读入信息的次轻量级操作。还有,jdom通过标准的构造器和set方法支持XML文档的修改。

posted @ 2005-06-17 10:46 佟福春 阅读(207) | 评论 (0)编辑 收藏

     摘要:     String title = JiveGlobals.getJiveProperty("websiteconf.name");   //网页标题  进到JiveGlobals看一看  public static String getJiveProperty(String name) {   &n...  阅读全文
posted @ 2005-06-17 10:43 佟福春 阅读(292) | 评论 (1)编辑 收藏

public class SimpleTest
{
     public static void main(String[] args)
     {
          System.out.println("Hello world");
     }
}

1、JAVA语言是大小写敏感的。
2、程序是从MAIN()开始执行。MAIN()必须是public 的
JAVA中的数据类型

整    型

Type

Storage Requirement

Range (Inclusive)

Int

4 bytes

–2,147,483,648 to 2,147,483, 647 (just over 2 billion)

Short

2 bytes

–32,768 to 32,767

Long

8 bytes

–9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Byte

1 byte

–128 to 127



实    型

Type

Storage Requirement

Range

float

4 bytes

approximately ±3.40282347E+38F (6–7 significant decimal digits)

double

8 bytes

approximately ±1.79769313486231570E+308 (15 significant decimal digits)



char型

In Java, the char type describes a code unit in the UTF-16 encoding.

boolean型  true false


变量
1、 声名:变量类型:变量名;
例:double salary;
         int    age;

2、初始化
变量必须初始化后才能使用。

注:变量声名可以在任何地方。

常量:

在java中用final表示一个常量
例:

public class Constants
{
   public static void main(String[] args)
   {
      final double CM_PER_INCH = 2.54;
      double paperWidth = 8.5;
      double paperHeight = 11;
      System.out.println("Paper size in centimeters: "
         + paperWidth * CM_PER_INCH + " by " + paperHeight * CM_PER_INCH);
   }
}
posted @ 2005-06-16 14:26 佟福春 阅读(241) | 评论 (0)编辑 收藏

    myEnv.registeUserInit(authToken);

 public void registeUserInit(Authorization authToken){
      try{
          this.forumFactory = ForumFactory.getInstance(authToken);
           this.pageUser=this.forumFactory.getUserManager().getUser(authToken.getUserID());
        }catch(Exception ex){
        }
  }

通过myEnv的registeUserInit()方法将UserEnvFront代表当前用户个人信息的对象pageUser进行赋值,但这不是最主要的,最主要的是该方法中的this.forumFactory = ForumFactory.getInstance(authToken);这句话非常非常关键。让我们潜入ForumFactory的静态方法getInstance()。


 public static ForumFactory getInstance(Authorization authorization) {
        //If no valid authorization passed in, return null.
        if (authorization == null) {
            return null;
        }
        if (factory == null) {
            synchronized(initLock) {
                if (factory == null) {
                    String classNameProp =
                        JiveGlobals.getJiveProperty("ForumFactory.className");
                    if (classNameProp != null) {
                        className = classNameProp;
                    }
                    try {
                        //Load the class and create an instance.
                        Class c = Class.forName(className);
                        factory = (ForumFactory)c.newInstance();
                    }
                    catch (Exception e) {
                        System.err.println("Failed to load ForumFactory class "
                            + className + ". Jive cannot function normally.");
                        e.printStackTrace();
                        return null;
                    }
                }
            }

        }

        //Now, create a forum factory proxy.
        return new ForumFactoryProxy(authorization, factory,
                factory.getPermissions(authorization));
    }

这个方法接受一个Authorization参数,当传入的Authorization参数为空时方法将直接返回null,我认为这是避免当Jive没有产生任何关于用户的认证信息却调用了此方法,得到工厂,从而Jive无法通过权限对用户的访问进行控制。大家睁大眼睛了,精彩的地方来了。这里采用了设计模式中的“单例”模式。这里的变量factory在类中为静态变量private static ForumFactory factory = null;但jive第一次运行的时候,这个变量毫无疑问为null,当用户执行到ForumFactory.getInstance(authToken)这句话的时候,程序判断factory为空,此时程序将类中的类型为Object的实例initLock作为同步的锁对象,保证当同步块中的代码没有执行完的时候没有其他用户能够执行此代码块,严格保证factory的唯一性,在同步区块中程序从系统的属性中读取到论坛将用何种工厂方法类进行数据持久化管理所需的相关对象的创建。如果你没有做任何修改该类因该是com.jivesoftware.forum.database.DbForumFactory。


在程序的最后生成的工厂方法又被new ForumFactoryProxy(authorization, factory,factory.getPermissions(authorization))包装(这根据了设计模式中的“代理”模式)。

Proxy是比较有用途的一种模式,而且变种较多,应用场合覆盖从小结构到整个系统的大结构,Proxy是代理的意思,我们也许有代理服务器等概念,代理概念可以解释为:在出发点到目的地之间有一道中间层,意为代理.

设计模式中定义: 为其他对象提供一种代理以控制对这个对象的访问.

为什么要使用Proxy?
1.授权机制 不同级别的用户对同一对象拥有不同的访问权利,如Jive论坛系统中,就使用Proxy进行授权机制控制,访问论坛有两种人:注册用户和游客(未注册用户),Jive中就通过类似ForumProxy这样的代理来控制这两种用户对论坛的访问权限.

2.某个客户端不能直接操作到某个对象,但又必须和那个对象有所互动.
举例两个具体情况:
(1)如果那个对象是一个是很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,打开文档必须很迅速,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片.

(2)如果那个对象在Internet的某个远端服务器上,直接操作这个对象因为网络速度原因可能比较慢,那我们可以先用Proxy来代替那个对象.


总之原则是,对于开销很大的对象,只有在使用它时才创建,这个原则可以为我们节省很多宝贵的Java内存. 所以,有些人认为Java耗费资源内存,我以为这和程序编制思路也有一定的关系.

如何使用Proxy?
以Jive论坛系统为例,访问论坛系统的用户有多种类型:注册普通用户 论坛管理者 系统管理者 游客,注册普通用户才能发言;论坛管理者可以管理他被授权的论坛;系统管理者可以管理所有事务等,这些权限划分和管理是使用Proxy完成的.

Forum是Jive的核心接口,在Forum中陈列了有关论坛操作的主要行为,如论坛名称 论坛描述的获取和修改,帖子发表删除编辑等.

在ForumPermissions中定义了各种级别权限的用户:

public class ForumPermissions implements Cacheable { 
/**
* Permission to read object.
*/
public static final int READ = 0;

/**
* Permission to administer the entire sytem.
*/
public static final int SYSTEM_ADMIN = 1;

/**
* Permission to administer a particular forum.
*/
public static final int FORUM_ADMIN = 2;

/**
* Permission to administer a particular user.
*/
public static final int USER_ADMIN = 3;

/**
* Permission to administer a particular group.
*/
public static final int GROUP_ADMIN = 4;

/**
* Permission to moderate threads.
*/
public static final int MODERATE_THREADS = 5;

/**
* Permission to create a new thread.
*/
public static final int CREATE_THREAD = 6;

/**
* Permission to create a new message.
*/
public static final int CREATE_MESSAGE = 7;

/**
* Permission to moderate messages.
*/
public static final int MODERATE_MESSAGES = 8;

.....

public boolean isSystemOrForumAdmin() {
  return (values[FORUM_ADMIN] || values[SYSTEM_ADMIN]);
}

.....

}

因此,Forum中各种操作权限是和ForumPermissions定义的用户级别有关系的,作为接口Forum的实现:ForumProxy正是将这种对应关系联系起来.比如,修改Forum的名称,只有论坛管理者或系统管理者可以修改,代码如下:

public class ForumProxy implements Forum {

private ForumPermissions permissions;
private Forum forum;
this.authorization = authorization;

public ForumProxy(Forum forum, Authorization authorization,
ForumPermissions permissions)
{
this.forum = forum;
this.authorization = authorization;
this.permissions = permissions;
}

.....

public void setName(String name) throws UnauthorizedException,
ForumAlreadyExistsException
{
  //只有是系统或论坛管理者才可以修改名称
  if (permissions.isSystemOrForumAdmin()) {
    forum.setName(name);
  }
  else {
    throw new UnauthorizedException();
  }
}

...

}


而DbForum才是接口Forum的真正实现,以修改论坛名称为例:

public class DbForum implements Forum, Cacheable {
...

public void setName(String name) throws ForumAlreadyExistsException {

  ....

  this.name = name;
  //这里真正将新名称保存到数据库中
  saveToDb();

  ....
}



...

}



凡是涉及到对论坛名称修改这一事件,其他程序都首先得和ForumProxy打交道,由ForumProxy决定是否有权限做某一样事情,ForumProxy是个名副其实的"网关","安全代理系统".

在平时应用中,无可避免总要涉及到系统的授权或安全体系,不管你有无意识的使用Proxy,实际你已经在使用Proxy了.

我们继续结合Jive谈入深一点,下面要涉及到工厂模式了,如果你不了解工厂模式,请看我的另外一篇文章:设计模式之Factory

我们已经知道,使用Forum需要通过ForumProxy,Jive中创建一个Forum是使用Factory模式,有一个总的抽象类ForumFactory,在这个抽象类中,调用ForumFactory是通过getInstance()方法实现,这里使用了Singleton(也是设计模式之一,由于介绍文章很多,我就不写了,看这里),getInstance()返回的是ForumFactoryProxy.

为什么不返回ForumFactory,而返回ForumFactory的实现ForumFactoryProxy?
原因是明显的,需要通过代理确定是否有权限创建forum.

在ForumFactoryProxy中我们看到代码如下:

public class ForumFactoryProxy extends ForumFactory { 
  protected ForumFactory factory;
  protected Authorization authorization;
  protected ForumPermissions permissions;

  public ForumFactoryProxy(Authorization authorization, ForumFactory factory,
  ForumPermissions permissions)
  {
    this.factory = factory;
    this.authorization = authorization;
    this.permissions = permissions;
  }

  public Forum createForum(String name, String description)
      throws UnauthorizedException, ForumAlreadyExistsException
  {
    //只有系统管理者才可以创建forum
    if (permissions.get(ForumPermissions.SYSTEM_ADMIN)) {
      Forum newForum = factory.createForum(name, description);
      return new ForumProxy(newForum, authorization, permissions);
    }
    else {
      throw new UnauthorizedException();
  }
}



方法createForum返回的也是ForumProxy, Proxy就象一道墙,其他程序只能和Proxy交互操作.

注意到这里有两个Proxy:ForumProxy和ForumFactoryProxy. 代表两个不同的职责:使用Forum和创建Forum;
至于为什么将使用对象和创建对象分开,这也是为什么使用Factory模式的原因所在:是为了"封装" "分派";换句话说,尽可能功能单一化,方便维护修改.

Jive论坛系统中其他如帖子的创建和使用,都是按照Forum这个思路而来的.

以上我们讨论了如何使用Proxy进行授权机制的访问,Proxy还可以对用户隐藏另外一种称为copy-on-write的优化方式.拷贝一个庞大而复杂的对象是一个开销很大的操作,如果拷贝过程中,没有对原来的对象有所修改,那么这样的拷贝开销就没有必要.用代理延迟这一拷贝过程.

比如:我们有一个很大的Collection,具体如hashtable,有很多客户端会并发同时访问它.其中一个特别的客户端要进行连续的数据获取,此时要求其他客户端不能再向hashtable中增加或删除 东东.

最直接的解决方案是:使用collection的lock,让这特别的客户端获得这个lock,进行连续的数据获取,然后再释放lock.

public void foFetches(Hashtable ht){
  synchronized(ht){
    //具体的连续数据获取动作..
  }

}



但是这一办法可能锁住Collection会很长时间,这段时间,其他客户端就不能访问该Collection了.

第二个解决方案是clone这个Collection,然后让连续的数据获取针对clone出来的那个Collection操作.这个方案前提是,这个Collection是可clone的,而且必须有提供深度clone的方法.Hashtable就提供了对自己的clone方法,但不是Key和value对象的clone,关于Clone含义可以参考专门文章.

public void foFetches(Hashtable ht){


  Hashttable newht=(Hashtable)ht.clone();

}


问题又来了,由于是针对clone出来的对象操作,如果原来的母体被其他客户端操作修改了, 那么对clone出来的对象操作就没有意义了.

最后解决方案:我们可以等其他客户端修改完成后再进行clone,也就是说,这个特别的客户端先通过调用一个叫clone的方法来进行一系列数据获取操作.但实际上没有真正的进行对象拷贝,直至有其他客户端修改了这个对象Collection.

使用Proxy实现这个方案.这就是copy-on-write操作.

Proxy应用范围很广,现在流行的分布计算方式RMI和Corba等都是Proxy模式的应用.

更多Proxy应用,见http://www.research.umbc.edu/~tarr/cs491/lectures/Proxy.pdf

Sun公司的 Explore the Dynamic Proxy API Dynamic Proxy Classes

posted @ 2005-06-15 13:28 佟福春 阅读(333) | 评论 (1)编辑 收藏

           authToken = AuthorizationFactory.getAnonymousAuthorization();

if语句内部通过工厂AuthorizationFactory的静态方法getAnonymousAuthorization()获得匿名用户所具备的权限,并将isGuest的值设为true,表明用户为匿名用户。

AuthorizationFactory类是一个抽象类,用来创建Authorization对象。这是一个抽象工厂,可以通过不同的子类来创建不同的Authorization对象。这个工厂的实现方法是:

在AuthorizationFactory中使用一个private static变量factory,用来引用具体的抽象工厂的实例:
private static AuthorizationFactory factory = null;

用一个private static的String,来指明具体的抽象工厂的子类类名:
private static String className ="com.coolservlets.forum.database.DbAuthorizationFactory";

然后是用一个private static的loadAuthorizationFactory方法来给这个factory变量赋值,生成具体的抽象工厂类:

   private static void loadAuthorizationFactory() {
        if (factory == null) {
            synchronized(className) {
                if (factory == null) {
                    String classNameProp = PropertyManager.getProperty(
                        "AuthorizationFactory.className"
                    );
                    if (classNameProp != null) {
                        className = classNameProp;
                    }
                    try {
                        Class c = Class.forName(className);
                        factory = (AuthorizationFactory)c.newInstance();
                    }
                    catch (Exception e) {
                        System.err.println("Exception loading class: " + e);
                        e.printStackTrace();
                    }
                }
            }
        }
}

 



在static的getAuthorization方法返回一个Authorization的过程中,先初始化工厂类factory变量,然后用factory的createAuthorization方法来创建:

   public static Authorization getAuthorization(String username,
            String password) throws UnauthorizedException
    {
        loadAuthorizationFactory();
        return factory.createAuthorization(username, password);
}

 



不同的子类有不同的createAuthorization方法的实现。比如在DbAuthorizationFactory这个AuthorizationFactory的数据库实现子类中,createAuthorization方法是这样实现的:

   public Authorization createAuthorization(String username, String password)
            throws UnauthorizedException
    {
        if (username == null || password == null) {
            throw new UnauthorizedException();
        }
        password = StringUtils.hash(password);
        int userID = 0;
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(AUTHORIZE);
            pstmt.setString(1, username);
            pstmt.setString(2, password);

            ResultSet rs = pstmt.executeQuery();
            if (!rs.next()) {
                throw new UnauthorizedException();
            }
            userID = rs.getInt(1);
        }
        catch( SQLException sqle ) {
            System.err.println("Exception in DbAuthorizationFactory:" + sqle);
            sqle.printStackTrace();
            throw new UnauthorizedException();
        }
        finally {
            try {  pstmt.close(); }
            catch (Exception e) { e.printStackTrace(); }
            try {  con.close();   }
            catch (Exception e) { e.printStackTrace(); }
        }
        return new DbAuthorization(userID);
    }


在这个类中,可以看到抽象类和具体的子类之间的关系,它们是如何协作的,又是如何划分抽象方法和非抽象方法的,这都是值得注意的地方。一般的,抽象方法需要子类来实现,而抽象类中的非抽象方法应该所有子类所能够共享的,或者可是说,是定义在抽象方法之上的较高层的方法。这确实是一个抽象工厂的好例子!虽然实现的方法已经和GOF中给出的实现相差较远了,但思想没变,这儿的实现,也确实是要巧妙的些。

还有就是静态方法的使用,使得这个类看起来有些Singleton的意味。这使得对于AbstractFactory的创建变得简单。

在AuthorizationFactory中定义的其它方法,涉及到具体的如何创建Authorization,都是作为abstract方法出现,具体实现留给子类来完成。

这样,在需要生成一个Authorization的时候,只需要调用AuthorizationFactory的静态方法getAuthorization就可以了,由子类实现了具体的细节。

其它的,如同上面讲到的,在创建Forum的时候用的ForumFactory,具有同上面一样的实现,这就是模式之所以称为模式的所在了。

posted @ 2005-06-15 13:13 佟福春 阅读(236) | 评论 (0)编辑 收藏

    Authorization authToken = SkinUtils.getUserAuthorization(request, response);
调用类SkinUtils中的静态方法getUserAuthorization(request, response)以返回代表用户权限的对象,那么让我们来看看getUserAuthorization(request, response):

getUserAuthorization(request, response):

public static Authorization getUserAuthorization

           (HttpServletRequest request, HttpServletResponse response)

   {

       HttpSession session = request.getSession();

       // Check 1: check for the Jive authentication token in the user's session.

       Authorization authToken = (Authorization)session.getAttribute(JIVE_AUTH_TOKEN);

       if (authToken != null) {

           return authToken;

       }

       // Check 2: check the jive cookie for username and password

       Cookie cookie = getCookie(request, JIVE_AUTOLOGIN_COOKIE);

       if (cookie != null) {

           try {

               // at this point, we found a cookie so grab the username and

               // password from it, create an authorization token and store

               // that in the session

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

               String username = values[0];

               String password = values[1];

               // Try to validate the user based on the info from the cookie.

               // Catch any exceptions

               authToken = AuthorizationFactory.getAuthorization(username,password);

           }

           catch (Exception e) {}

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

           if (authToken != null) {

               session.setAttribute(JIVE_AUTH_TOKEN, authToken);

           }

           // return the authorization token

           return authToken;

       }

       return null;

   }


getUserAuthorization(request, response)中,首先从request中取得session,检查目前用户的session中是否存在JIVE_AUTH_TOKEN”,如果存在的话直接返回Authorization的对象authToken。不存在的话也有可能注册用户刚刚打开浏览器进入论坛(此时的注册用户以前曾经选择将用户信息保存在cookie中),还没有初始化;也有可能是匿名用户登陆,所以接下来程序分别对这两种情况进行处理。先处理用户为注册用户刚刚登陆,程序通过方法getCookie()取得用户客户端存放的cookie,并用decodePasswordCookie()对其进行一些解码的处理,处理完成后取得用户的用户名和密码,将其交给AuthorizationFactory的静态方法getAuthorization()以判别用户是否存在,并产生相应的权限对象,在接下来的代码中通过

if (authToken != null) {

                session.setAttribute(JIVE_AUTH_TOKEN, authToken);

}

判断此时authToken是否为null,不是的话将authToken放入session中,便于程序以后直接从session中取得。那么接下来的处理匿名用户登陆就更简单了,由于匿名用户登陆后Authorization的对象authToken一路为空,并且方法的三次判断都和它不沾边,所以直接返回null


posted @ 2005-06-15 13:04 佟福春 阅读(307) | 评论 (0)编辑 收藏

大多数人认为,接口的意义在于顶替多重继承。众所周知Java没有c++那样多重继承的机制,但是却能够实作多个接口。其实这样做是很牵强的,接口和继承是完全不同的东西,接口没有能力代替多重继承,也没有这个义务。接口的作用,一言以蔽之,就是标志类的类别(type of class)。把不同类型的类归于不同的接口,可以更好的管理他们。OO的精髓,我以为,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如c++、java、c#等),就是因为设计模式所研究的,实际上就是如何合理的去抽象。(cowboy的名言是“抽象就是抽去像的部分”,看似调侃,实乃至理)。

  设计模式中最基础的是工厂模式(Factory),在我最近的一个很简单的应用中,我想尽量的让我的程序能够在多个数据库间移植,当然,这涉及很多问题,单是如何兼容不同DBMS的SQL就让人头痛。我们不妨先把问题简单化,只考虑如何连接不同的数据库。

  假设我有很多个类,分别是Mysql.java、SQLServer.java、Oracle.java、DB2.java,他们分别连接不同的数据库,统一返回一个Connection对象,并且都有一个close方法,用于关闭连接。只需要针对你的DBMS,选择不同的类,就可以用了,但是我的用户他会使用什么数据库?我不知道,我希望的是尽量少的修改代码,就能满足他的需要。我可以抽象如下接口:

package org.bromon.test;
public interface DB
{
  java.sql.Connection openDB(String url,String user,String password);
  void close();


  这个接口只定义两个方法,没有任何有实际意义的代码,具体的代码由实作这个接口的类来给出,比如Mysql.java:

Package org.bromon.test;
import java.sql.*;
public class Mysql implements DB
{
  private String url=”jdbc:mysql:localhost:3306/test”;
  private String user=”root”;
  private String password=””;
  private Connection conn;
  public Connection openDB(url,user,password)
  {
    //连接数据库的代码
  }

  public void close()
  {
    //关闭数据库
  }


  类似的当然还有Oracle.java等等,接口DB给这些类归了个类,在应用程序中我们这样定义对象:

  org.bromon.test.DB myDB;

  使用myDB来操作数据库,就可以不用管实际上我所使用的是哪个类,这就是所谓的“开-闭”原则。但是问题在于接口是不能实例化的,myDB=new DB(),这样的代码是绝对错误的,我们只能myDB=new Mysql()或者myDB=new Oracle()。麻烦了,我还是需要指定具体实例化的是哪个类,用了接口跟没用一样。所以我们需要一个工厂:

package org.bromon.test;
public class DBFactory
{
  public static DB Connection getConn()
  {
    Return(new Mysql());
  }


  所以实例化的代码变成:myDB=DBFactory.getConn();

  这就是23种模式中最基础的普通工厂(Factory),工厂类负责具体实例化哪个类,而其他的程序逻辑都是针对DB这个接口进行操作,这就是“针对接口编程”。责任都被推卸给工厂类了,当然你也可以继续定义工厂接口,继续把责任上抛,这就演变成抽象工厂(Abstract Factory)。

  整个过程中接口不负责任何具体操作,其他的程序要连接数据库的话,只需要构造一个DB对象就OK,而不管工厂类如何变化。这就是接口的意义----抽象。

  继承的概念不用多说,很好理解。为什么要继承呢?因为你想重用代码?这绝对不是理由,继承的意义也在于抽象,而不是代码重用。如果对象A有一个run()方法,对象B也想有这个方法,所以有人就Class B extends A。这是不经大脑的做法。如果在B中实例化一个A,调用A的Run()方法,是不是可以达到同样的目的?如下:

Class B
{
  A a=new A();
  a.run();


  这就是利用类的聚合来重用代码,是委派模式的雏形,是GoF一贯倡导的做法。

  那么继承的意义何在?其实这是历史原因造成的,最开始的OO语言只有继承,没有接口,所以只能以继承来实现抽象,请一定注意,继承的本意在于抽象,而非代码重用(虽然继承也有这个作用),这是很多Java烂书最严重的错误之一,它们所造成的阴影,我至今还没有完全摆脱,坏书害人啊,尤其是入门类的,流毒太大。什么时候应该使用继承?只在抽象类中使用,其他情况下尽量不使用。抽象类也是不能实例化的,它仅仅提供一个模版而已,这就很能说明问题。

  软件开发的万恶之源,一是重复代码而不是重用代码,二是烂用继承,尤以c++程序员为甚。Java中取缔多重继承,目的就是制止烂用继承,实是非常明智的做法,不过很多人都不理解。Java能够更好的体现设计,这是让我入迷的原因之一。

posted @ 2005-06-13 10:32 佟福春 阅读(240) | 评论 (0)编辑 收藏