posts - 48,  comments - 14,  trackbacks - 0
 

原文地址:http://www.elecn.com/blog/user1/qsuo/archives/2006/200682782810.html

批量删除_desktop.ini的命令

  这几天来,中了不少病毒,重装系统两次,留下了无数个尸体,_desktop.ini文件,网上查到说是一种叫欢乐时光的病毒,现在使用DOS命令批量删除_desktop.ini,如下:

  del d:\_desktop.ini /f/s/q/a

  强制删除d盘下所有目录内(包括d盘本身)的_desktop.ini文件并且不提示是否删除

  /f 强制删除只读文件

  /q 指定静音状态。不提示您确认删除。

  /s 从当前目录及其所有子目录中删除指定文件。显示正在被删除的文件名。

  /a的意思是按照属性来删除了

  这个命令的作用是在杀掉viking病毒之后清理系统内残留的_desktop.ini文件用的

posted @ 2006-09-05 13:55 逍遥草 阅读(679) | 评论 (0)编辑 收藏
Struts+Spring+Hibernate 的示例(一)

先说说《Wiring Your Web Application with Open Source Java by Mark Eagle》这个示例,网上有这个的中英文文章。在后续的说明中我会将文章的部分内容引用进来。我使用的开发工具是 Eclipse3.1 + MyEclipse4.0M2。


针对一个简单或者复杂的 Web 应用程序,我们需要考虑诸如是怎样建立用户接口?在哪里处理业务逻辑?怎样持久化的数据?而针对这三个层次,每个层次我们都要仔细考虑:各个层该使用什么技术? 怎样的设计能松散耦合还能灵活改变? 怎样替换某个层而不影响整体构架?应用程序如何做各种级别的业务处理(比如事务处理)?等等。

对于这个示例我们采用当前流行的三种框架来做到 Web 应用程序的松散耦合:表示层我们用 Struts;业务层我们用 Spring;而持久层则用 Hibernate。 

Struts 负责管理用户的请求,做出响应;提供控制器,委派调用业务逻辑;处理异常;UI 验证等。
Spring 负责处理应用程序的业务逻辑和业务校验;管理事务;提供与其它层相互作用的接口;管理业务层级别的对象的依赖等。
Hibernate 负责存储、更新、删除数据库记录等。

这篇文章举例说明如何使用这三个框架整合开发,并揭示一个请求是如何贯穿于各个层的。(从用户的加入一个Order到数据库,显示;进而更新、删除)。 

1. 首先创建一组对象,这些对象有的需要持久化,有的提供业务逻辑,有的是显示接口的设计。Hibernate 允许你将数据库中的信息存放入对象,可以在连接断开的情况下把这些数据显示到UI层。而那些对象也可以返回给持续层,从而在数据库里更新。

使用 myeclipse 的 Web  Project 新建一个项目 SSHTest:

创建 Order 类:

利用 eclipse 生成 Getters 和 Setters。

同样创建 OrderLineItem 类。

com.ivan.ssh.bo.Order 包含一个订单的主要信息:订单号,订单总价,订单客户名。
com.ivan.ssh.bo.OrderLineItem 包含订单的详细信息:订单单项号,单价,描述。一个订单对应于多个订单项。

Order 与 OrderLineItem 的关系是一对多的关系,这里为它们建立双向一对多的关系。
在类中我们需要分别为 Order 和 OrderLineItem 添加属性 orderLineItems(java.util.Set 类型) 和 order(Order 类型)及其 getter 和 setter 方法。

2. 配置持久层,Hibernate 通过 XML 文件来映射 (OR) 对象,以下两个 xml 文件分别映射了Order 和 OrderItem 对象。
Order.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
 "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd";>

<hibernate-mapping>
    <class
        name="com.meagle.bo.Order"
        table="Orders"
        dynamic-update="false"
        dynamic-insert="false">

        <id
            name="id"
            column="Order_ID"
            type="int"
            unsaved-value="0">
            <generator class="native">
            </generator>
        </id>

        <set
            name="orderLineItems"
            table="OrderLineItem"
            lazy="true"
            inverse="true"
            cascade="save-update"
            sort="unsorted">
              <key column="Order_ID"/>
              <one-to-many class="com.meagle.bo.OrderLineItem"/>
        </set>

        <property
            name="userName"
            type="string"
            update="true"
            insert="true"
            column="UserName"
            not-null="true"
            unique="false"/>
        <property
            name="total"
            type="double"
            update="true"
            insert="true"
            column="Total"
            not-null="false"
            unique="false"/>
    </class>
</hibernate-mapping>

OrderLineItem.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
 "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd";>

<hibernate-mapping>
    <class
        name="com.meagle.bo.OrderLineItem"
        table="OrderLineItem"
        dynamic-update="false"
        dynamic-insert="false">

        <id
            name="id"
            column="OrderLineItem_ID"
            type="int"
            unsaved-value="0">
            <generator class="native">
            </generator>
        </id>

        <many-to-one
            name="order"
            class="com.meagle.bo.Order"
            cascade="none"
            outer-join="auto"
            update="true"
            insert="true"
            column="Order_ID"/>

        <property
            name="description"
            type="string"
            update="true"
            insert="true"
            column="Description"
            not-null="false"
            unique="false"/>

        <property
            name="lineItemPrice"
            type="double"
            update="true"
            insert="true"
            column="LineItemPrice"
            not-null="false"
            unique="false"/>
    </class>
</hibernate-mapping>

稍后我们介绍怎样配置 SessionFactory 和 Session,前者说明与哪个数据库通信,使用哪个连接池或使用了 DataSource,加载哪些持久对象,后者用来完成查找、保存、删除和更新这些操作。

3. 配置业务层,这里要创建业务服务对象(Business Service Object),用他们来执行程序的逻辑,调用持久层,得到 UI 层的 requests,处理 transactions,并且控制 exceptions。为了将这些连接起来并且易于管理,我们将使用面向方面的 SpringFramework。Spring 提供了控制倒置(Inversion of Control)和注射依赖设置(Setter Dependency Injection) 这些方式(可供选择),用 XML 文件将对象连接起来。IoC 是一个简单概念(它允许一个对象在上层接受其他对象的创建),用 IoC 这种方式让你的对象从创建中释放了出来,降低了偶合度。

我们将用一个 business service object 接收一个 DAO,用它来控制 domain objects 的持久化。 由于在这个例子中使用了 Hibernate,我们可以很方便的用其他持久框架实现同时通知 Spring 有新的 DAO 可以使用了。在面向接口的编程中,你会明白 “注射依赖”模式是怎样松散耦合你的业务逻辑和持久机制的。

public interface IOrderService {
  public abstract Order saveNewOrder(Order order)
     throws OrderException, OrderMinimumAmountException;
  public abstract List findOrderByUser(String user) throws OrderException;
  public abstract Order findOrderById(int id) throws OrderException;
  public abstract void setOrderDAO(IOrderDAO orderDAO);
}

注意到这段代码里有一个 setOrderDao(),它就是一个DAO Object设置方法(注射器)。

其实现类:
public class OrderServiceSpringImpl implements IOrderService {

 private static final double ORDER_MINIMUM = 100.0;

 private IOrderDAO orderDAO;

 public Order saveNewOrder(Order order)
  throws OrderException, OrderMinimumAmountException {

  // do some business logic
  if (order != null && order.getTotal() == 0) {

   double total = 0.0;

   Set items = order.getOrderLineItems();
   Iterator iter = items.iterator();
   while (iter.hasNext()) {
    OrderLineItem item = (OrderLineItem) iter.next();
    total += item.getLineItemPrice();
   }

   if (total < OrderServiceSpringImpl.ORDER_MINIMUM) {
    throw new OrderMinimumAmountException("Order did not exceed the order minimum");
   } else {
    order.setTotal(total);
   }
  }

  Order savedOrder = null;
  try {
   savedOrder = getOrderDAO().saveOrder(order);
  } catch (RuntimeException e) {
   throw new OrderException("Could not save order " + e.toString());
  }

  return savedOrder;
 }

 public List findOrderByUser(String user) throws OrderException {

  List orders = null;
  try {
   orders = getOrderDAO().findOrdersPlaceByUser(user);
  } catch (RuntimeException e) {
   // should really use a logger instead of System.out
   System.out.println(
    "Could not locate order by user " + e.getMessage());
   throw new OrderException(
    "Could not locate order by user " + e.getMessage());
  }
  return orders;
 }

 public Order findOrderById(int id) throws OrderException {

  Order order = null;
  try {
   order = getOrderDAO().findOrderById(id);
  } catch (RuntimeException e) {
   // should really use a logger instead of System.out
   System.out.println(
    "Could not locate order by ID " + e.getMessage());
   throw new OrderException(
    "Could not locate order by ID " + e.getMessage());
  }
  return order;
 }

 public IOrderDAO getOrderDAO() {
  return orderDAO;
 }

 public void setOrderDAO(IOrderDAO orderDAO) {
  this.orderDAO = orderDAO;
 }
}

接下去对 DAO 的实现类进行编码。既然 Spring 已经有对 Hibernate 的支持,那这个例子就直接继承 HibernateDaoSupport类了,这个类很有用,我们可以参考 HibernateTemplate(它主要是针对 HibernateDaoSupport 的一个用法,具体可以查看 Spring 的 API)。

public interface IOrderDAO {
 public  Order findOrderById(final int id);
 public abstract List findOrdersPlaceByUser(final String placedBy);
 public abstract Order saveOrder(final Order order);
}

此接口的实现类:
public class OrderHibernateDAO
 extends HibernateDaoSupport
 implements IOrderDAO {

 public OrderHibernateDAO() {
  super();
 }

 public Order findOrderById(final int id) {

  Order order = (Order) getHibernateTemplate().load(Order.class, new Integer(id));
   
  if(order.getOrderLineItems().size() > 0){
   // collection initialized. 
  }   
  return order;
 }

 public List findOrdersPlaceByUser(final String placedBy) {

  return getHibernateTemplate().executeFind(new HibernateCallback() {
   public Object doInHibernate(Session session)
    throws HibernateException, SQLException {

    StringBuffer sb = new StringBuffer(100);
    sb.append("select distinct order ");
    sb.append("from Order order ");
    sb.append("join order.lineItems lineItems ");
    sb.append("where order.placedBy = :placedBy");

    sb.append("order by order.id");

    Query query = session.createQuery(sb.toString());
    query.setString("placedBy", placedBy);

    List list = query.list();

    return list;
   }
  });
 }

 public Order saveOrder(final Order order) {
  getHibernateTemplate().save(order);
  return order;
 }
文章来源:http://www.javaresearch.org/article/showarticle.jsp?column=23&thread=53684
posted @ 2006-08-05 16:12 逍遥草 阅读(431) | 评论 (0)编辑 收藏

动态小数据操作是WEB开发中不可避免的,这就涉及到数组 哈希表 属性类等几个功能;本文提供本人常用语法和简单解释:

一般数组定义

//定义数组catalogs
public static final String[] catalogs =
{"Business","Entertainment","Law","Real Estate","Medical","Services","Computers & Internet","Food","Other"};

//在form中逐个显示数组内容
<SELECT name="catalog">
<%
for (int i = 0; i < catalogs.length; i++)
{
String catalog=(String)catalogs[i];
if (i==catalogid) //catalogid是需要selected的int类型
  out.println("<OPTION value="+i+" selected>"+catalog+"</OPTION>");
else
  out.println("<OPTION value="+i+">"+catalog+"</OPTION>");
}
%>
</SELECT>

 

vector

vector是java的动态数组的功能, 随着更多元素加入其中,数组变的更大.

public Vector getGraphFile(Hashtable hashtable)
{
  Vector vector = new Vector();
  for(Enumeration enumeration = hashtable.keys(); enumeration.hasMoreElements();)
  {
    String s1 = (String)enumeration.nextElement();
    if (isGraphFile(s1))
    {
      vector.addElement(s1);
    }
  }

  return vector;
}
本例是将hashtable 的关键词转为vector数组. 要遍历vector或hashtable使用java的Enumeration

for (Enumeration e = v.elements(); e.hasMoreElements();)
{
  out.println((String)e.nextElement());
}

这是将Vector中的值输出.注意使用类型强制转换.


Hashtable

Hashtable是一个关键字对应关键值的表,使用该表,可以迅速索引到某个值.这个表很好用,补充Vector不足.
Hashtable hashtable=new Hashtable();
hashtable.put("1","男");
hashtable.put("2","女");
out.println("性别是"+(String)hashtable.get("2"))

如果一个关键字对应多个关键值,而且这几个关键词中不一定是字符形,可能包含其他复杂类型:
//加入数据
hashtable.put(name, new UploadedFile(dir.toString(), fileName,filePart.getContentType()));

//取数据
UploadedFile file = (UploadedFile)hashtable.get(name);
return file.getFilesystemName(); //得fileName
return file.getContentType(); //得type

//类 UploadedFile定义:
class UploadedFile {

private String dir;
private String filename;
private String type;

UploadedFile(String dir, String filename, String type) {
this.dir = dir;
this.filename = filename;
this.type = type;
}

public String getContentType() {
return type;
}

public String getFilesystemName() {
return filename;
}

public File getFile() {
if (dir == null || filename == null) {
return null;
}
else {
return new File(dir + File.separator + filename);
}
}

台湾蔡学镛先生有专门一篇 谈论Collection 文章,有兴趣者可进一步研究.


Properties

下面是获取系统属性的方法
import java.util.Properties;
public class getSystemProperties
{
  public static void main(String args[])
  {
    //通过获得系统属性构造属性类 prop
    Properties prop = new Properties(
    System.getProperties() );
    //在标准输出中输出系统属性的内容
    prop.list(System.out);
  }
    //根据获取的系统属性确定程序执行流程
}

属性文件的操作:
Properties prop = new Properties();
try
{
  FileInputStream fis=new FileInputStream(“firstProp.txt”);
}catch (FileNotFoundException ex) {
  throw new Exception("error:"+ex.getMessage());
}
prop.load(fis);
out.println(getProperty("fruits"))


Properties可以对下列文件格式进行操作:
firstProp.txt

Truth = Beauty
Truth:Beauty
Truth :Beauty
fruits apple, banana, pear, \
    cantaloupe, watermelon, \
    kiwi, mango

posted @ 2006-05-18 11:57 逍遥草 阅读(285) | 评论 (0)编辑 收藏
在Java 2的Collections框架中,主要包括两个接口及其扩展和实现类:Collection接口和Map接口。两者的区别在于前者存储一组对象,后者则存储一些关键字/值对。

public interface java.util.Map {

    //Altering Methods
    public Object put(Object key, Object value);  
    public Object remove(Object key);        
    public void putAll(java.util.Map);        
    public void clear();  

    //Querying Methods
    public Object get(Object key);      
    public int size();              
    public boolean isEmpty();            
    public boolean containsKey(Object);      
    public boolean containsValue(Object);      
    public boolean equals(Object);          

    //Viewing Methods
    public java.util.Set keySet();             //Gets keys
    public java.util.Collection values();       //Gets values
    public java.util.Set entrySet();           //Gets mappings

    public static interface java.util.Map.Entry {   //a map-entry (single key/value pair)
      public Object getKey();             //returns current entry key
      public Object getValue();             //returns current entry value
      public Object setValue(Object value);    
      public boolean equals(Object);        
      public int hashCode();                
    }
}


Map接口提供了方便易用的方法,通过这些方法可以查询、查看、修改当前Map的内容。注意对于Map接口的keySet()方法返回一个Set,Set是Collection接口的一个扩展,包含不重复的一组对象。因为Map中的key是不可重复的,所以得到所有key的keySet()方法返回一个Set对象。Map接口本身还包含了一个Map.Entry接口,一个Map.Entry就是Map中的一个关键字/值对。Map接口中的entrySet()方法就返回了一个集合对象,其中每一个元素都实现了Map.Entry接口。Map接口的get(Object key),put(Object key,Object value),和remove(Object key)方法都有同一个问题。他们的返回类型都是Object,当返回null时,可以猜测为调用那个方法前那个key不存在。但是只有在null不允许作为Map的值时可以这样猜测。所有Map接口的通用实现都允许null作为key或者value,这就说当返回一个null值,就可以意味着很多事情。只是因为通用实现允许null值,你不能下那个映射有null值的结论。如果你确知没有null值,那返回null值就意味着调用那个方法前,映射里并没有那个键。否则,你必须调用containsKey(Object key)来看看那个Key是否存在。

Hashtable

java.util.Hashtable实现了Map接口,在Hashtable中使用key对象的hashCode()作为对应的对象的相对存储地址,以便实现根据关键字快速查找对象的功能。所以只有一个实现了hashCode()和equals()方法的对象才可作为Hashtable的key。null值不能作为关键字或值。
public class java.util.Hashtable extends Dictionary implements Cloneable, Map, Serializable {

  //Hashtable constructors
  //construct a default Hashtable with default capacity and load of 0.75
  public Hashtable();              
  //construct a Hashtable with passed capacity and default load of 0.75
  public Hashtable (int initialCapacity);
  //construct Hashtable with passed capacity and load
  public Hashtable(int initialCapacity, float load);
  //construct Hashtable with passed mapping
  public Hashtable(Map);            
  
  //Hashtable specific methods
  //checks if Object is in Hashtable
  public boolean contains(Object);    
  //returns Enumeration of elements in Hashtable
  public Enumeration elements();      
  //returns Enumeration of keys in hashtable
  public Enumeration keys();        
  //creates shallow copy of Hashtable(structure copied, but not key/values)
  public Object clone();            
  //prints out key/value pairs of Hashtable elements
  public String toString();          
  //reorganizes all elements in Hashtable, and increases Hashtable capacity
  protected void rehash();          
  
  //get Value from passed in key
  public Object get(Object);        
  //insert key/value pair
  public Object put(Object key, Object value);    

}


Hashtable是Java 2集合框架推出之前的一个老的工具类,在新的Java 2集合框架下,已经被HashMap取代。Hashtable和HashMap的区别主要是前者是同步的,后者是快速失败机制保证不会出现多线程并发错误(Fast-Fail)。在初始化一个Hashtable时,可以指定两个参数:初始容量、负荷,这两个参数强烈的影响着Hashtable的性能。容量是指对象的个数,负荷是指散列表中的实际存储的对象个数和容量的比率。如果初始容量太小,那么Hashtable需要不断的扩容并rehash(),而这是很耗时的;如果初始容量太大,又会造成空间的浪费。负荷则相反,负荷太小会造成空间浪费,负荷太大又会耗时(因为这会造成较多的关键字的散列码重复,Hashtable使用一个链接表来存储这些重复散列码的对象)。容量的缺省值是11,负荷的缺省值是0.75,一般情况下你都可以使用缺省值来生成一个Hashtable。另外,在Hashtable中的大部分的方法都是同步的。

HashMap

HashMap基本实现了Map接口的全部方法。方法的签名大家看上面的Map接口。这儿主要说说几个Map接口中的方法。
按照集合框架的实现,哈希表是单链表作为元素的数组,有着同样索引值的两个或更多入口被一起链结到单链表中。哈希表声明如下:
  private Entry[] table;
组件类型Entry是Map.Entry接口的实现,Map.Entry声明于Map接口内。下边是Map.Entry接口的简化实现:
  
private static class Entry implements Map.Entry{
    int hashCode;
    Object key;
    Object value;
    Entry next;

    Entry(int hashCode,Object key,Object value,Entry next){
        This.hashCode=hashCode;
        This.key=key;
        This.value=value;
        This.next=next;
    }
    public Object getKey(){
      return key;
    }
    public Object getValue(){
      return value;
    }
    public Object setValue(Object value){
      Object oldValue=this.value;
      This.value=value;
      Return oldValue;
    }
}


SortedMap是Map接口的子接口,SortedMap的标准实现是TreeMap,实现了一个排序的Map。
这里翻译的是对《Java 规则》(Java Rules)中对Java2的集合框架的研究。
详细可以参考Java2源码中的集合实现代码和API doc。
posted @ 2006-05-18 11:56 逍遥草 阅读(837) | 评论 (0)编辑 收藏

    软件安装在这里就不多说了,下面进入主题:

  1. 在Apache的安装目录下找到httpd.conf文件,打开后,查找如下内容
                  ScriptAlias /cgi-bin/ "$Apache安装目录/cgi-bin/"
    如果在其前面有#,先把#去掉。然后将引号内的路径名改为你自己要设置cgi存放的路径,注意cgi-bin后面要带/。
  2. 再往下查找如下内容 <Directory "$Apache安装目录/cgi-bin/">,将引号内的路径名改为你自己要设置cgi存放的路径,将下面的AllowOverride  与   Options 项后面改为All,注意大小写。结果应该是这样

            <Directory "$Apache安装目录/cgi-bin/">   
                AllowOverride All
                Options All
                Order allow,deny
                Allow from all
            </Directory>
   3.  一般cgi都有可能是perl语言写成,有些脚本后缀名为.pl,在httpd.conf中查找如下内容
                  AddHandler cgi-script .cgi
         如果在其前面有#,先把#去掉,然后在后面添加.pl,其结果应该是这样
                  AddHandler cgi-script .cgi .pl
         
这样设置后,你的Apache就可以支持以.pl为后缀的脚本文件了。
设置完毕,保存httpd.conf文件,重启Apache服务,命令自己可以到网上查到。现在在浏览器中输入你的服务地址,看看吧:)

这里,引用一篇参考文章,具体出处http://ranbo.osxcn.com/misc/manual/apache/Apache2/zh/howto/cgi.html

Apache指南:CGI动态页面

概要

CGI(公共网关接口[Common Gateway Interface])定义了网站服务器与外部内容协商程序之间交互的方法,通常是指CGI程序或者CGI脚本,是在网站上实现动态页面的最简单而常用的方法。本文将对如何在Apache网站服务器上建立CGI以及如何编写CGI程序作介绍。

配置Apache以允许CGI

要让CGI程序能正常运作,必须配置Apache以允许CGI的执行,其方法有多种。

ScriptAlias

ScriptAlias 指令使Apache允许执行一个特定目录中的CGI程序。当客户端请求此特定目录中的资源时,Apache假定其中文件都是CGI程序并试图运行。

ScriptAlias 指令形如:

ScriptAlias /cgi-bin/ /usr/local/apache/cgi-bin/

如果Apache被安装到默认的位置,默认的配置文件httpd.conf中则会有上述配置。ScriptAlias指令定义了映射到一个特定目录的URL前缀,与Alias指令非常相似,两者一般都用于指定位于DocumentRoot目录以外的目录,其区别是ScriptAlias又多了一层含义,即其URL前缀中任何文件都被视为CGI程序。所以,上述例子会指示Apache,/cgi-bin/应该指向/usr/local/apache/cgi-bin/目录,且视之为CGI程序。

举例,如果有URL为http://www.example.com/cgi-bin/test.pl的请求,Apache会试图执行/usr/local/apache/cgi-bin/test.pl文件并返回其输出。当然,这个文件必须存在而且可执行,并以特定的方法产生输出,否则Apache返回一个出错消息。

ScriptAlias目录以外的CGI

由于安全原因,CGI程序通常被限制在ScriptAlias指定的目录中,如此,管理员就可以严格地控制谁可以使用CGI程序。但是,如果采取了恰当的安全方法措施,则没有理由不允许其他目录中的CGI程序运行。比如,你可能希望用户在UserDir指定的宿主目录中存放页面,而他们有自己的CGI程序,但无权存取cgi-bin目录,这样,就产生了运行其他目录中CGI程序的需求。

用Options显式地允许CGI的执行

可以在主服务器配置文件中,使用Options指令显式地允许特定目录中CGI的执行:

<Directory /usr/local/apache/htdocs/somedir>
Options +ExecCGI
</Directory>

上述指令使Apache允许CGI文件的执行。另外,还必须告诉服务器哪些文件是CGI文件。下面的AddHandler指令告诉服务器所有带有cgipl后缀的文件是CGI程序:

AddHandler cgi-script cgi pl

.htaccess文件

.htaccess文件是针对目录进行配置的一种方法。Apache在提供一个资源时,会在此资源所在目录中寻找.htaccess文件,如果有,则使其中的指令生效。AllowOverride 指令决定了.htaccess文件是否有效,它指定了哪些指令可以出现在其中,或者根本不允许使用。为此,需要在主服务器配置中如此配置:

AllowOverride Options

.htaccess文件中,需要如此配置:

Options +ExecCGI

以使Apache允许此目录中CGI程序的执行。

编写CGI程序

编写CGI程序和``常规''程序之间有两个主要的不同。

首先,在CGI程序的所有输出前面必须有一个MIME类型的头,即HTTP头,对浏览器指明所接收内容的类型,大多数情况下,形如:

Content-type: text/html

其次,输出要求是HTML形式的,或者是浏览器可以显示的其他某种形式。多数情况下,输出是HTML形式的,但偶然也会编写CGI程序以输出一个gif图片或者其他非HTML的内容。

除了这两点,编写CGI程序和编写其他程序大致相同。

第一个CGI程序

这个CGI程序例子在浏览器中打印一行文字。把下列存为first.pl文件,并放在你的cgi-bin目录中。

#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "Hello, World.";

即使不熟悉Perl语言,你也应该能看出它干了什么。第一行,告诉Apache这个文件可以用/usr/bin/perl(或者任何你正在使用的shell)解释并执行。第二行,打印上述要求的内容类型说明,并带有两个换行,在头后面留出空行,以示HTTP头的结束。第三行,打印文字``Hello, World.''。程序到此结束。

打开你喜欢的浏览器并输入地址:

http://www.example.com/cgi-bin/first.pl

或者是你存放程序的其他位置,就可以在浏览器窗口中看到一行Hello, World.。虽然并不怎么激动人心,但是一旦这个程序能正常运作,那么就可能运作其他任何程序。

程序还是不能运行!

从网络访问CGI程序,浏览器中可能会发生四种情况:

CGI程序的输出
太好了!这说明一切正常。
CGI程序的源代码或者一个"POST Method Not Allowed"消息
这说明Apache没有被正确配置以执行CGI程序,重新阅读configuring Apache看看遗漏了什么。
一个以"Forbidden"开头的消息
这说明有权限问题。参考Apache error log和下面的文件的权限
一个"Internal Server Error"消息
查阅Apache error log,可以找到CGI程序产生的出错消息"Premature end of script headers"。对此,需要检查下列各项,以找出不能产生正确HTTP头的原因。

文件的权限

记住,服务器不是以你的用户身份运行的,就是说,在服务器启动后,拥有的是一个非特权用户的权限-通常是``nobody''或者``www'' -而需要更大的权限以允许文件的执行。通常,给予``nobody''足够的权限以执行文件的方法是,对文件赋予everyone execute权限:

chmod a+x first.pl

另外,如果需要对其他文件进行读取或写入,也必须对这些文件赋予正确的权限。

如果服务器被配置为使用suexec则是一个例外。这个程序允许CGI程序根据其所在虚拟主机或用户宿主目录的不同而以不同的用户权限运行。Suexec有极其严格的权限校验,任何校验失败都会使CGI程序运行失败而产生"Internal Server Error"。对此,需要检查suexec的日志文件以发现哪个安全校验出问题了。

路径信息

当你在命令行执行一个程序,某些信息会自动传给shell而无须你操心,比如一个路径,告诉shell你所引用的文件可以在哪儿找到。

但是,在CGI程序通过网站服务器执行时,则没有此路径,所以,你在CGI程序中引用的任何程序(如sendmail)都必须指定其完整的路径,使shell能找到它们以执行你的CGI程序。

一种普通的用法是,在CGI程序的第一行中指明解释器(通常是perl),形如:

#!/usr/bin/perl

必须保证它的确指向解释器。

语法错误

多数CGI程序失败的原因在于程序本身有问题,尤其是在已经消除上述两种错误而CGI挂起的情况下。在用浏览器测试以前,先在命令行中执行你的程序,能够发现大多数的问题。

出错记录

出错记录是你的朋友。任何错误都会在出错记录中有记载,所以你应该首先查看它。如果你的网站空间提供者不允许访问出错记录,那么你应该考虑换一个空间提供者。学会阅读出错记录,可以快速找出问题并快速解决。

幕后是怎样操作的?

当你的CGI编程逐渐深入,理解幕后的操作,尤其是浏览器和服务器如何与其他的通讯,就变得有用了。虽然成功地写了一个程序打印``Hello, World.'',但并没有实际的用处。

环境变量

环境变量是使用计算机时到处都会用到的变量,比如路径(对实际文件的一个搜索路径以补全你的输入)、你的用户名以及你的终端类型等等。在命令行输入env,可以得到你的标准的当天的环境变量列表。

在CGI处理过程中,服务器和浏览器都会设置环境变量,比如浏览器类型(Netscape, IE, Lynx)、服务器类型(Apache, IIS, WebSite)以及将要执行的CGI程序名称等等。

所有这些变量对CGI程序员都有效,但只是客户端-服务器通讯的一半内容。完整的变量列表见http://hoohoo.ncsa.uiuc.edu/cgi/env.html

这个简单的CGI程序列出了环境中所有的环境变量,Apache发行版的cgi-bin中还有两个类似的程序。注意,有些变量是必须的,有些则是可选的,所以你可能会看见一些官方列表中没有的变量。另外,Apache提供了多种不同方法以在默认提供的变量中增加你的专用环境变量

#!/usr/bin/perl
print "Content-type: text/html\n\n";
foreach $key (keys %ENV) {
print "$key --> $ENV{$key}<br>";
}

STDIN和STDOUT

服务器和客户端之间的其他通讯都通过标准输入设备(STDIN)和标准输出设备(STDOUT)完成。通常,STDIN是指键盘或者一个程序所作用于的一个文件,STDOUT指控制台或显示器。

当你POST一个网络表格到一个CGI程序时,表格中的数据被捆扎为一个特殊形式通过STDIN传送给CGI程序,这样,这个程序就可以处理这些数据,仿佛这些数据来自键盘或者一个文件。

这种``特殊形式''很简单,一个字段名称及其值,中间用等号(=)连接,多个这样的字段对用与符号(&)连接。非常规字符,如空格、与符号和等号,被转换为其等值的十六进制以免出问题。整个字串形如:

name=Rich%20Bowen&city=Lexington&state=KY&sidekick=Squirrel%20Monkey

有时,你会发现URL后面缀有这样的字串。这种形式会使服务器以这个字串的内容设置环境变量QUERY_STRING,称为GET请求。你的HTML表格在FORM标记中设置METHOD属性,以指定传送数据的行为使GET或者是POST

接着,你的程序必须把这个字串分离以获得有用的信息。所幸,有库和模块可以帮助你处理这些数据,还有为你的CGI程序达成其他目的的处理器。

CGI模块/库

编写CGI程序时,你应该考虑使用代码库或模块来完成多数琐碎的工作,以减少错误并更快地开发。

如果用Perl语言编写CGI程序,可用的模块见CPAN,最常用的模块是CGI.pm。也可以考虑用CGI::Lite,它实现了一个在多数程序中所有必须的最小功能集

如果用C语言编写CGI程序,则有很多选择,其中之一是CGIC库,来自http://www.boutell.com/cgic/

更多资料

网上有大量的CGI资源。可以在Usenet组comp.infosystems.www.authoring.cgi和别人讨论CGI相关问题。HTML Writers Guild的-servers邮件列表是一个优秀的问题解答资源。更多资源可以在http://www.hwg.org/lists/hwg-servers/找到。

另外,还可以阅读CGI规范,其中有CGI程序操作的所有细节,原始版本见NCSA,另有一个更新草案见Common Gateway Interface RFC project

当你向一个邮件列表或者新闻组提交CGI相关问题时,你应该确保提供了足够的信息以更简单地发现并解决问题,诸如:发生了什么事、你希望得到什么结果、结果与你所期望的有什么出入、你运行的服务器、CGI程序是用什么语言编写的、如果可能就提供那个讨厌的代码。

注意,绝不要把CGI相关问题提交到Apache bug database,除非你坚信发现的是Apache源代码中的问题。

posted @ 2006-05-10 19:54 逍遥草 阅读(677) | 评论 (0)编辑 收藏
Apache的HTTPD是目前比较受欢迎的网站服务器软件,它不但功能强大,而且完全免费,并且支持市场上流行的各种操作系统(Windows,Linux,Mac os)。同时对于Java Servlet/JSP的支持,通常也会使用同样Apache出品的Tomcat。
Tomcat除了支持Java Servlet/JSP之外,也可以当做网站服务器使用,但是在对于静态的html文件、图片文件等的解析效率上不如Apache HTTPD的执行效率高。应用tomcat的服务器如果网站的访问量较大,系统资源占用会明显升高,近日笔者在项目执行过程中遇到这一问题,便也想到同时应用tomcat+apache服务。Apache负责静态资源处理,tomcat负责jsp和java servlet等动态资源的处理。在网上看了不少介绍Apache和Tomcat集成的帖子。大多为互相转贴,有几个关键问题没有讲清楚,并且多数文章讲解中所举的软件版本都已经比较老旧。因而笔者总结自己实际操作的经验,希望对大家有所帮助。

准备工作:
1、  apache 2.0.55
下载地址:http://apache.justdn.org/httpd/binaries/win32/apache_2.0.55-win32-x86-no_ssl.msi
2、  Tomcat 5.5
下载地址:http://tomcat.apache.org/download-55.cgi
3、  JRE1.5.0 update6
下载地址:http://java.sun.com/j2se/1.5.0/download.jsp
你也可以选择安装JDK,JDK不仅包含了运行java应用程序的支持,同时也包含J2SE的开发包。但如果您只是应用的话,我建议您仅下载JRE即可,为什么?因为它小啊:)
4、JK-apache-2.0.55
下载地址:
http://www.apache.org/dist/tomcat/tomcat-connectors/jk/binaries/win32/jk-1.2.15/mod_jk-apache-2.0.55.so
注意jk的版本一定要与你的apache版本相同。Jk下载可以直接到apache.org官网下载,有多种版本,适用于各种操作系统,地址如下:
http://www.apache.org/dist/tomcat/tomcat-connectors/jk/binaries/
windows用户进入win32目录,不同的tomcat对应不同jk文件扩展名不同,有可能是*.dll,或者*.so,根据你的tomcat版本下载合适的jk版本。

安装,软件的安装顺序可以适当调整,但是jre(jdk)一定要在tomcat之前安装:
1、安装JRE(JDK)
此处选择默认安装即可,(旧版本的JDK安装完之后需要设置系统的环境变量,JRE和JDK1.5.0以后版本不设置也没关系。)安装完成之后你可以选择按照下面的示例设置你的系统环境变量:我的电脑->右键属性->常规->高级->环境变量
JAVA_HOME = d:\Java\jre1.5.0_06
CLASSPATH = .;d:\Java\jre1.5.0_06\lib\dt.jar;d:\Java\jre1.5.0_06\lib\tools.jar
PATH = d:\Java\jre1.5.0_06\bin
注意文件路径要改成你实际安装的路径。

2、Apache安装
此处注意,如果你本机已经装了iis并且占用80端口,务必先在服务中将iis停止或禁用。软件默认安装即可。安装过程中设置Network Domain和Server Name为localhost,设置你的email地址,下方会让你选择only for the current user或for all users。默认选择for all users。这样Apache就会占用80端口,并且做为一个系统服务开机自运行。
安装完成之后,你在浏览器中输入http://localhost,将会看到Apache的成功页面.Apache的主目录是d:\Apache\Apache2,此时Apache,已经运行,你可以在窗口的托盘看到他的图标,双击图标,在弹开的窗口选择"Open Apache Monitor",点Stop,停止Apaceh服务,因为下面要安装Tomcat和JK.

3、Tomcat安装
选择安装目录为了d:\Tomcat,其余为默认安装.成功后在浏览器中输入http://localhost:8080,出现Tomcat页面表示安装成功了。关于tomcat的设置已经有很多非常详细的文章,在此就不再详述了。

4、JK的安装
把mod_jk_2.0.55.so拷贝到d:\Apache\Apache2\modules\下.

以上安装全部完成后,打开cmd命令提示符,运行d:\Apache\Apache2\bin\Apache.exe -t 你将会看到"Syntax OK",表示Apache配置正常.这个命令非常有用,下面在继续配置Apache的时候还会再次用到。

配置服务器:

1、配置Tomcat
查找目录下的conf/workers.properties文件,并对比下方文本内容,如有不同以下方内容为准修改。
workers.tomcat_home=d:\Tomcat #让mod_jk模块知道Tomcat的位置
workers.java_home=d:\Java\jre1.5.0_06 #让mod_jk模块知道jre的位置
ps=\
worker.list=ajp13 #模块版本
worker.ajp13.port=8009 #工作端口,若没占用则不用修改
worker.ajp13.host=localhost #本机,若上面的Apache主机不为localhost,作相应修改
worker.ajp13.type=ajp13 #类型
worker.ajp13.lbfactor=1 #代理数,不用修改

如果文件不存在则创建。并填充上述内容。

2、配置Apache
打开d:\Apache\Apache2\conf下的httpd.conf,在最后加入下面这段代码并保存。
#设置Apache与Tomcat之间的连接,让Apache遇到jsp文件时,在后台将其交由Tomcat去处理
LoadModule jk_module modules/mod_jk_2.0.55.so
#此处mod_jk的文件为你下载的文件
JkWorkersFile "d:/Tomcat/conf/workers.properties"
#指定tomcat监听配置文件地址
JkLogFile "d:/Tomcat/logs/mod_jk2.log"
#指定日志存放位置
JkLogLevel info

#设置虚拟主机
<VirtualHost localhost>
ServerAdmin localhost
DocumentRoot d:/test1
#您的站点项目所在路径,应与tomcat中的目录设置相同
ServerName localhost
DirectoryIndex index.html index.htm index.jsp
ErrorLog logs/shsc-error_log.txt
CustomLog logs/shsc-access_log.txt common
JkMount /servlet/* ajp13
#让Apache支持对servlet传送,用以Tomcat解析
JkMount /*.jsp ajp13
#让Apache支持对jsp传送,用以Tomcat解析
JkMount /*.do ajp13
#让Apache支持对.do传送,用以Tomcat解析
</VirtualHost>

#开头的行为注释,可以删除。此处的配置我是以设置虚拟目录,重新指定了serverName,DocumentRoot路径的方式配置站点,实际上,Apache的配置可以非常灵活,你也可以不采用虚拟目录的方式,而直接去修改站点的默认配置,使用任意文本编辑工具,搜索如ServerName,DocumentRoot,ServerAdmin等相关项,然后将该项的配置根据你的实际情况进行修改即可。我建议大家可以多多尝试不同的配置,这是一种很有乐趣的体验。
所有的修改完成这后,再次执行d:\Apache\Apache2\bin\Apache.exe -t 如果看到"Syntax OK",说明你所有配置无误,双击窗口的托盘中的图标,然后点击Start运行。
在地址栏中分别输入http://localhost/,与http://localhost:8080/若结果相同,Apache与Tomcat整合成功
posted @ 2006-05-10 11:20 逍遥草 阅读(523) | 评论 (0)编辑 收藏
由于Apache具有相当高的可移植性,它支持超过30种操作系统,包括Unix、Windows 及Darwin等系统,所以目前在网络上已注册的网域里大部份是使用Apache网页服务器。目前ApacheSoftware Foundation 正致力于发展现在已进入alpha测试阶段的Apache2.0。在这里,我和大家探讨如何修改服务器选项让服务器能提供简单的动态网页内容,也就是支持CGI程序及 Server-Side Include(SSI)程序。

  1、准备工作

  首先,我假设你已经安装好Apache而且你的Apache能提供静态网页供浏览。Apache的安装会自动附上静态的HTML测试页,也就是说如果能看到那测试页,就代表你的Apache能正常运作了。基本安装下的Apache仅能提供静态的HTML网页。然而,你可以通过使用模块(modules)来提升它的功能。在原始的设定下,Apache的编译会包含mod_include 及 moc_cgi 这两个模块。你可以在bin子目录下执行./httpd -l来查看你的Apache是否装有这两个模块。执行的输出会是一长串Apache现在安装的所有模块。如果mod_include及moc_cgi 这两个模块不在清单里,你必须重新编译服务器。重新编译时,确定依照如何含括 mod_include、moc_cgi 模块的说明。此外你还必需有提供实时网页的Server-Side Includes(相关资料网址:http://www.oreilly.com/catalog/apache/excerpt/ch10.html)。有了 server-side includes (SSI)支持,你就能制作出实时的动态网页。接下来,我先从服务器的SSI支持设定开始,然后进入CGI的编写。

  2、Apache的设定

  首先你必须先找到Apache这个设定文件。Apache的原始安装目录在 Unix下是/usr/local/apache,在Windows下则是 c:\Program Files\Apache。接着在conf子目录下你会找到httpd.conf 文件。这就是Apache的设定档。这个设定文件是个纯文字文件,所以你可以使用一般的文字编辑器,如vi或Notepad 等,来编辑。首先要注意的是在这个设定档里有些行的起始文字是#符号,这表示这行的文字全为批注。适当地在你的设定档内做批注是个好习惯,因为那帮你记得你曾做了哪些设定以及为什么。

  3、执行 SSI 程序

  开启设定文件并寻找以下这些文字:

    #
    # To use server-parsedHTMLfiles
    #
    #AddType text/html .shtml
    #AddHandler server-parsed .shtml

  删除AddType及AddHandler这两行指令前的#符号。AddType指令会要求服务器在传回任何附属档名为.shtml的网页时,以 text或HTML做为传回文件的内容格式。AddHandler 则是用来指示服务器将文件内容送交给mod_include 处理。之后,mod_include 就会判断该如何响应这样的文件。接下来,寻找以下文字: 
         <Directory "C:/Program Files/Apache Group/Apache2/htdocs">
        注意:如果你所要部署的应用程序在另外的目录,那么在这个地方可以把路径改成你自己的目录

  在这行文字及对应的</Directory> 间会有一行选项行(options line)。原始的设定是:

  Options Indexes FollowSymLinks MultiViews

  在这行尾端加上Includes ,结果看起来会是这样:

  Options Indexes FollowSymLinks MultiViews Includes

  这是要求Apache在htdocs子目录里执行 server-side includes 程序。为了让这些修改生效,我们必须重新启动服务器。在 Unix 下重新激活,执行"kill -HUP `cat /usr/local/apache/logs/httpd.pid`"。在 Windows 下,执行"Apache-k restart"。现在我们来试试刚才的设定结果。在/usr/local/apache/htdocs 目录里新增一个文件 test.shtml。这个文件必须要包含以下程序代码:

  [<!--#include virtual="hello.txt "-->]; The file hello.txt is [<!--#fsize file="hello.txt "-->] long and it was last modified on[<!--#flastmod file="hello.txt "-->]];

  这段 SSI 程序会去读取一个称为 hello.txt 文件,并将该文件的大小以及最近一次的修改日期输出到网页上。显然的,我们还必须在 htdocs 目录下新增这个hello.txt 文件。在我的hello.txt 文件里只有一行文字:HOW ARE YOU!。完成新增这些文件后,打开你惯用的浏览器并开启http://localhost/test.shtml网页。如果你服务器的安装并不是通过root用户,你可能必须改为开启http://localhost:8080/test.shtml。之后将得到如下结果:

  HOW ARE YOU! The file hello.txt is 1k bytes long and it was last modified on Wednesday, 02-Aug-2000 20:18:28 PDT

  另外一种可以激活支持SSI程序的方法称为XbitHack设定(相关资料网址:http://www.apache.org/docs/mod/mod_include.html#xbithack )。这个方法的由来是当你将文本文件的使用者可执行位(user-executable bit)设为可执行状态后,Apache会将那些文件视为 SSI 程序文件。
    要激活这样的功能必须将以下指令(directive)放在所有目录的 .htaccess 文件里:XbitHack status on (or full) status 的值可以设为on 、off 或是full。on 的设定会强制服务器将所有使用者可执行的文件视为SSI项。Off则使服务器完全忽略使用者可执行的设定状态。若是设定为Full,服务器会视所有使用者可执行档为SSI项,同时也会检查组可执行(group-executable bit)。如果组可执行项设定为可执行时,传回header的last modified time的值就会被设定为该文件最近一次被修改的时间。这样的设定可以让客户端的浏览器及代理服务器(proxy)进行缓存(caching)。不过在使用这样的功能时必须要小心。例如说,如果你的网页有提供轮替式广告看板你就不会想要设定群组可执行位为开启的状态,因为那么做会让第一个下载的广告被快取起来,导致使用者再也看不到其它页的广告。

  4、执行CGI程序

  在Apache原始安装里,cgi-bin子目录下附有两组CGI程序,test-cgi 以及printenv,只不过这两组程序有潜在的安全漏洞。但是由于我们只是要做设定测试,并且我们不会将这样的原始安装设定直接放在主运行服务器(live server),所以我们还是会激活其中一组CGI程序,看看Apache当初是如何被设定来执行这组程序。最后我们会自己撰写一支简单的CGI程序。

  首先,要确定这组程序是能执行的。进入cgi-bin子目录,确定程序文件被设定为使用者(服务器执行时使用者)可执行以及使用组(服务器执行时使用组)可执行。对 Windows系统来说,这一步应该是非必要的。接着,对服务器要求这样的内容:

  http://localhost:8080/cgi-bin/test-cgi

  注意:只有在通过非root使用者进行服务器安装的情况下才需要指定8080端口(port)。这支Apache内建的test-cgi程序会列出CGI程序会存取的变量值。激活CGI支持是设定在httpd.conf设定文件内的ScriptAlias 指令区段。这个指令区段的原始设定值是:

  ScriptAlias /cgi-bin/ "/usr/local/apache/cgi-bin/"

  这行指令是告诉Apache如果要求的网页路径是以cgi-bin为起始,这些文件可在/usr/local/apache/cgi-bin/ 目录下找得到。这行指令同时也告诉Apache要在这个目录下执行文件。在下面我准备了一个会输出"How are you!"的简单CGI程序。我将它命名为 how.sh。

    #!/bin/sh
    echo "Content-type: text/html"
    echo
    echo "How are you!"

  修改这个文件的权限使其成为可执行文件并且向你的服务器提出以下的要求:

  http://localhost:8080/cgi-bin/how.sh

  虽然这组CGI是采用shell script来编写,其实它可以用任何适用于该系统的语言来撰写。至于关于CGI程序的撰写如果有机会我会和大家作更深入的探讨。

  结论:

  Apache支持的所有SSI 指令可以在Apachedocumentation 里找到,Apache所有的功能都可以通过设定文件 (config file)进行调试。在这里我所介绍的仅只是设定文件相关知识的皮毛。设定文件的原始设定有着非常详尽的说明文件,而且每一个系统版本都附有核心模块及标准模块的说明文件,如果你花些时间在这些文件里摸索,你会找到任何你想要的功能。
posted @ 2006-05-09 17:44 逍遥草 阅读(321) | 评论 (0)编辑 收藏
Tomcat对SSI提供了支持,但在默认的情况下这种支持是关闭的。如果你用Tomcat做为HTTP服务器并且需要SSI,那么需要自己来设置。设置方法如下:
  1.         在$CATALINA_BASE/server/lib/目录下找到servlets-ssi.renametojar文件 
  2.         将这个文件重命名为servlets-ssi.jar 
  3.         在$CATALINA_BASE/conf/目录下找到web.xml文件
  4.         使用SSI Servlet就删除在SSI servlet和servlet-mapping周围的注释 
  5.         使用SSI filter就删除在SSI filter和filter-mapping周围的注释
posted @ 2006-05-09 16:22 逍遥草 阅读(413) | 评论 (0)编辑 收藏
SSI有什么用?
之所以要扯到ssi,是因爲shtml--server-parsed HTML 的首字母缩略词。包含有嵌入式服务器方包含命令的 HTML 文本。在被传送给浏览器之前,服务器会对 SHTML 文档进行完全地读取、分析以及修改。
shtml和asp 有一些相似,以shtml命名的文件里,使用了ssi的一些指令,就像asp中的指令,你可以在SHTML文件中写入SSI指令,当客户端访问这些shtml文件时,服务器端会把这些SHTML文件进行读取和解释,把SHTML文件中包含的SSI指令解释出来
比如:你可以在SHTML文件中用SSI指令引用其他的html文件(#include ),服务器传送给客户端的文件,是已经解释的SHTML不会有SSI指令。它实现了HTML所没有的功能,就是可以实现了动态的SHTML,可以说是HTML的一种进化吧。像新浪的新闻系统就是这样的,新闻内容是固定的但它上面的广告和菜单等就是用#include引用进来的。

  目前,主要有以下几种用用途:
[li]  1、显示服务器端环境变量<#echo> [/li]
[li]  2、将文本内容直接插入到文档中<#include> [/li]
[li]  3、显示WEB文档相关信息<#flastmod #fsize> (如文件制作日期/大小等) [/li]
[li]  4、直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序) [/li]
[li]  5、设置SSI信息显示格式<#config>(如文件制作日期/大小显示方式) 高级SSI<XSSI>可设置变量使用if条件语句。[/li]

 
使用SSI
  SSI是为WEB服务器提供的一套命令,这些命令只要直接嵌入到HTML文档的注释内容之中即可。如:
  <!--#include file="info.htm"-->
  就是一条SSI指令,其作用是将"info.htm"的内容拷贝到当前的页面中,当访问者来浏览时,会看到其它HTML文档一样显示info.htm其中的内容。
  其它的SSI指令使用形式基本同刚才的举例差不多,可见SSI使用只是插入一点代码而已,使用形式非常简单。
  当然,如果WEB服务器不支持SSI,它就会只不过将它当作注释信息,直接跳过其中的内容;浏览器也会忽略这些信息。
 
如何在我的WEB服务器上配置SSI功能?
  在一些WEB服务器上(如IIS 4.0/SAMBAR 4.2),包含 #include 指令的文件必须使用已被映射到 SSI 解释程序的扩展名;否则,Web 服务器将不会处理该SSI指令;默认情况下,扩展名 .stm、.shtm 和 .shtml 被映射到解释程序(Ssinc.dll)。

  Apache则是根据你的设置情况而定,修改srm.conf如:
  AddType text/x-server-parsed-html .shtml 将只对.shtml扩展名的文件解析SSI指令
  AddType text/x-server-parsed-html .html将对所有HTML文档解析SSI指令
  Netscape WEB服务器直接使用Administration Server(管理服务器)可打开SSI功能。
  Website使用Server Admin程序中的Mapping标签,扩展名添加内容类型为:wwwserver/html-ssi
  Cern服务器不支持SSI,可用SSI诈骗法,到http://sw.cse.bris.ac.uk/WebTools/fakessi.html ;上下载一个PERL脚本,即可使你的CERN服务器使用一些SSI指令。(不支持exec指令。)
 
SSI指令基本格式
SSI指令基本格式:
程序代码:
<!-– 指令名称="指令参数">
<!-– 指令名称="指令参数">
如 程序代码:

<!--#include file="info.htm"-->
<!--#include file="info.htm"-->
说明:
1.<!-- -->是HTML语法中表示注释,当WEB服务器不支持SSI时,会忽略这些信息。
2.#include 为SSI指令之一。
3.file 为include的参数, info.htm为参数值,在本指令中指将要包含的文档名。

注意:

1.<!--与#号间无空格,只有SSI指令与参数间存在空格。
2.上面的标点="",一个也不能少。
3.SSI指令是大小写敏感的,因此参数必须是小写才会起作用。
 
SSI指令使用详解

#echo 示范
作用:
将环境变量插入到页面中。
语法:
程序代码:

<!--#echo var="变量名称"-->
<!--#echo var="变量名称"-->

本文档名称:程序代码:

<!--#echo var="DOCUMENT_NAME"-->
<!--#echo var="DOCUMENT_NAME"-->
现在时间:程序代码:
<!--#echo var="DATE_LOCAL"-->
<!--#echo var="DATE_LOCAL"-->
你的IP地址是程序代码:

<!--#echo var="REMOTE_ADDR"-->
<!--#echo var="REMOTE_ADDR"-->

#include 示范
作用:
将文本文件的内容直接插入到文档页面中。
语法:
程序代码:

<!--#include file="文件名称"-->
<!--#include virtual="文件名称"-->
<!--#include file="文件名称"-->
<!--#include virtual="文件名称"-->
file 文件名是一个相对路径,该路径相对于使用 #include 指令的文档所在的目录。被包含文件可以在同一级目录或其子目录中,但不能在上一级目录中。如表示当前目录下的的nav_head.htm文档,则为file="nav_head.htm"。
virtual 文件名是 Web 站点上的虚拟目录的完整路径。如表示相对于服务器文档根目录下hoyi目录下的nav_head.htm文件;则为file="/hoyi/nav_head.htm"
参数:
file 指定包含文件相对于本文档的位置
virtual 指定相对于服务器文档根目录的位置
注意:
1、文件名称必须带有扩展名。
2、被包含的文件可以具有任何文件扩展名,我觉得直接使用htm扩展名最方便,微软公司推荐使用 .inc 扩展名(这就看你的爱好了)。
示例:
程序代码:

<!--#include file="nav_head.htm"-->将头文件插入到当前页面
<!--#include file="nav_foot.htm"-->将尾文件插入到当前页面
<!--#include file="nav_head.htm"-->将头文件插入到当前页面
<!--#include file="nav_foot.htm"-->将尾文件插入到当前页面

#flastmod 和#fsize 示范
作用: #flastmod 文件最近更新日期
#fsize 文件的长度
语法:
程序代码:

<!--#flastmod file="文件名称"-->
<!--#fsize file="文件名称"-->
<!--#flastmod file="文件名称"-->
<!--#fsize file="文件名称"-->
参数:
file 指定包含文件相对于本文档的位置 如 info.txt 表示当前目录下的的info.txt文档
virtual 指定相对于服务器文档根目录的位置 如 /hoyi/info.txt 表示
注意:
文件名称必须带有扩展名。
示例:
程序代码:

<!--#flastmod file="news.htm"-->
<!--#flastmod file="news.htm"-->
将当前目录下news.htm文件的最近更新日期插插入到当前页面
程序代码:

<!--#fsize file="news.htm"-->
<!--#fsize file="news.htm"-->
将当前目录下news.htm的文件大小入到当前页面
 
#exec 示范
作用:
将某一外部程序的输出插入到页面中。可插入CGI程序或者是常规应用程序的输入,这取决于使用的参数是cmd还是cgi。
语法:
程序代码:

<!--#exec cmd="文件名称"-->
<!--#exec cgi="文件名称"-->
<!--#exec cmd="文件名称"-->
<!--#exec cgi="文件名称"-->
参数:
cmd 常规应用程序
cgi CGI脚本程序
示例:
程序代码:

<!--#exec cmd="cat /etc/passwd"-->将会显示密码文件
<!--#exec cmd="dir /b"-->将会显示当前目录下文件列表
<!--#exec cgi="/cgi-bin/gb.cgi"-->将会执行CGI程序gb.cgi。
<!--#exec cgi="/cgi-bin/access_log.cgi"-->将会执行CGI程序access_log.cgi。
<!--#exec cmd="cat /etc/passwd"-->将会显示密码文件
<!--#exec cmd="dir /b"-->将会显示当前目录下文件列表
<!--#exec cgi="/cgi-bin/gb.cgi"-->将会执行CGI程序gb.cgi。
<!--#exec cgi="/cgi-bin/access_log.cgi"-->将会执行CGI程序access_log.cgi。
注意:
从上面的示例可以看出,这个指令相当方便,但是也存在安全问题。
禁止方法:
.Apache,将access.conf中的"Options Includes ExecCGI"这行代码删除;
.在IIS中,要禁用 #exec 命令,可修改 SSIExecDisable 元数据库;

#config
作用: 指定返回给客户端浏览器的错误信息、日期和文件大小的格式。
语法:
程序代码:

<!--#config errmsg="自定义错误信息"-->
<!--#config sizefmt="显示单位"-->
<!--#config timefmt="显示格式"-->
<!--#config errmsg="自定义错误信息"-->
<!--#config sizefmt="显示单位"-->
<!--#config timefmt="显示格式"-->
参数:
errmsg 自定义SSI执行错误信息,可以为任何你喜欢的方式。
sizefmt 文件大小显示方式,默认为字节方式("bytes")可以改为千字节方式("abbrev")
timefmt 时间显示方式,最灵活的配置属性。
示例: 显示一个不存在文件的大小
程序代码:

<!--#config errmsg="服务器执行错误,请联系管理员 yiho@126.com,谢谢!"-->
<!--#fsize file="不存在的文件.htm"-->
<!--#config errmsg="服务器执行错误,请联系管理员 yiho@126.com,谢谢!"-->
<!--#fsize file="不存在的文件.htm"-->
以千字节方式显示文件大小
程序代码:

<!--#config sizefmt="abbrev"-->
<!--#fsizefile="news.htm"-->
<!--#config sizefmt="abbrev"-->
<!--#fsizefile="news.htm"-->
以特定的时间格式显示时间
程序代码:

<!--#config timefmt="%Y年/%m月%d日 星期%W 北京时间%H:%M:%s,%Y年已过去了%j天 今天是%Y年的第%U个星期"-->
<!--#echo var="DATE_LOCAL"--> 显示今天是星期几,几月,时区
<!--#config timefmt="今天%A, %B ,服务器时区是 %z,是"-->
<!--#echo var="DATE_LOCAL"-->
<!--#config timefmt="%Y年/%m月%d日 星期%W 北京时间%H:%M:%s,%Y年已过去了%j天 今天是%Y年的第%U个星期"-->
<!--#echo var="DATE_LOCAL"--> 显示今天是星期几,几月,时区
<!--#config timefmt="今天%A, %B ,服务器时区是 %z,是"-->
<!--#echo var="DATE_LOCAL"-->

XSSI
XSSI(Extended SSI)是一组高级SSI指令,内置于Apache 1.2或更高版本的mod-include模块之中。
其中可利用的的指令有:
#printenv
#set
#if
#printenv
作用: 显示当前存在于WEB服务器环境中的所有环境变量。
语法:程序代码:

<!--#printenv-->
<!--#printenv-->
<!--#printenv-->
<!--#printenv-->

#set
作用:可给变量赋值,以用于后面的if语句。
语法:程序代码:

<!--#set var="变量名"value="变量值"-->
<!--#set var="变量名"value="变量值"-->
参数:无
示例: 程序代码:

<!--#set var="color"value="红色"-->
<!--#set var="color"value="红色"-->

#if
作用: 创建可以改变数据的页面,这些数据根据使用if语句时计算的要求予以显示。
语法: 程序代码:

<!--#if expr="$变量名=\"变量值A\""-->
显示内容
<!--#elif expr="$变量名=\"变量值B\""-->
显示内容
<!--#else-->
显示内容
<!--#endif"-->
<!--#if expr="$变量名=\"变量值A\""-->
显示内容
<!--#elif expr="$变量名=\"变量值B\""-->
显示内容
<!--#else-->
显示内容
<!--#endif"-->
示例:
程序代码:

<!--#if expr="$SERVER_NAME=\"hoyi.zb169.net\""-->
欢迎光临好易CGI工厂在淄博热线的分站http://hoyi.zb169.net。
<!--#elif expr="$SERVER_NAME=\"linux.cqi.com.cn\"" -->
欢迎光临好易CGI工厂在太阳城的分站http://linux.cqi.com.cn/~hoyi。
<!--#else-->
欢迎光临好易CGI工厂!
<!--#endif"-->
<!--#if expr="$SERVER_NAME=\"hoyi.zb169.net\""-->
欢迎光临好易CGI工厂在淄博热线的分站http://hoyi.zb169.net。
<!--#elif expr="$SERVER_NAME=\"linux.cqi.com.cn\"" -->
欢迎光临好易CGI工厂在太阳城的分站http://linux.cqi.com.cn/~hoyi。
<!--#else-->
欢迎光临好易CGI工厂!
<!--#endif"-->
注意: 用于前面指令中的反斜杠,是用来代换内部的引号,以便它们不会被解释为结束表达式。不可省略。

1、Config命令

  Config命令主要用于修改SSI的默认设置。其中:

  Errmsg:设置默认错误信息。为了能够正常的返回用户设定的错误信息,在HTML文件中Errmsg参数必须被放置在其它SSI命令的前面,否则客户端只能显示默认的错误信息,而不是由用户设定的自定义信息。

    <!--#config errmsg="Error! Please email webmaster@mydomain.com -->

  Timefmt:定义日期和时间的使用格式。Timefmt参数必须在echo命令之前使用。

    <!--#config timefmt="%A, %B %d, %Y"-->
    <!--#echo var="LAST_MODIFIED" -->

  显示结果为:

    Wednesday, April 12, 2000

  也许用户对上例中所使用的%A %B %d感到很陌生,下面我们就以表格的形式总结一下SSI中较为常用的一些日期和时间格式。

  Sizefmt:决定文件大小是以字节、千字节还是兆字节为单位表示。如果以字节为单位,参数值为"bytes";对于千字节和兆字节可以使用缩写形式。同样,sizefmt参数必须放在fsize命令的前面才能使用。

    <!--#config sizefmt="bytes" -->
    <!--#fsize file="index.html" -->

  2、Include命令

  Include命令可以把其它文档中的文字或图片插入到当前被解析的文档中,这是整个SSI的关键所在。通过Include命令只需要改动一个文件就可以瞬间更新整个站点!

  Include命令具有两个不同的参数:

  Virtual:给出到服务器端某个文档的虚拟路径。例如:

    <!--#include virtual="/includes/header.html" -->

  File:给出到当前目录的相对路径,其中不能使用"../",也不能使用绝对路径。例如:

    <!--#include file="header.html" -->

  这就要求每一个目录中都包含一个header.html文件。

  3、Echo命令

  Echo命令可以显示以下各环境变量:

  DOCUMENT_NAME:显示当前文档的名称。

    <!--#echo var="DOCUMENT_NAME" -->

显示结果为:

    index.html

  DOCUMENT_URI:显示当前文档的虚拟路径。例如:

    <!--#echo var="DOCUMENT_URI" -->

  显示结果为:

    /YourDirectory/YourFilename.html

  随着网站的不断发展,那些越来越长的URL地址肯定会让人头疼。如果使用SSI,一切就会迎刃而解。因为我们可以把网站的域名和SSI命令结合在一起显示完整的URL,即:

  http://YourDomain<;!--#echo var="DOCUMENT_URI" -->

  QUERY_STRING_UNESCAPED:显示未经转义处理的由客户端发送的查询字串,其中所有的特殊字符前面都有转义符"\"。例如:

  <!--#echo var="QUERY_STRING_UNESCAPED" -->

  DATE_LOCAL:显示服务器设定时区的日期和时间。用户可以结合config命令的timefmt参数,定制输出信息。例如:

  <!--#config timefmt="%A, the %d of %B, in the year %Y" -->
 <!--#echo var="DATE_LOCAL" -->

  显示结果为:

  Saturday, the 15 of April, in the year 2000

  DATE_GMT:功能与DATE_LOCAL一样,只不过返回的是以格林尼治标准时间为基准的日期。例如:

  <!--#echo var="DATE_GMT" -->

  LAST_MODIFIED:显示当前文档的最后更新时间。同样,这是SSI中非常实用的一个功能,只要在HTML文档中加入以下这行简单的文字,就可以在页面上动态的显示更新时间。

  <!--#echo var="LAST_MODIFIED" -->

  CGI环境变量

  除了SSI环境变量之外,echo命令还可以显示以下CGI环境变量:

    SERVER_SOFTWARE:显示服务器软件的名称和版本。例如:
    <!--#echo var="SERVER_SOFTWARE" -->
    SERVER_NAME: 显示服务器的主机名称,DNS别名或IP地址。例如:
    <!--#echo var="SERVER_NAME" -->
    SERVER_PROTOCOL:显示客户端请求所使用的协议名称和版本,如HTTP/1.0。例如:
    <!--#echo var="SERVER_PROTOCOL" -->
    SERVER_PORT:显示服务器的响应端口。例如:
    <!--#echo var="SERVER_PORT" -->
    REQUEST_METHOD:显示客户端的文档请求方法,包括GET, HEAD, 和POST。例如:
    <!--#echo var="REQUEST_METHOD" -->
    REMOTE_HOST:显示发出请求信息的客户端主机名称。
    <!--#echo var="REMOTE_HOST" -->
    REMOTE_ADDR:显示发出请求信息的客户端IP地址。
    <!--#echo var="REMOTE_ADDR" -->
    AUTH_TYPE:显示用户身份的验证方法。
    <!--#echo var="AUTH_TYPE" -->
    REMOTE_USER:显示访问受保护页面的用户所使用的帐号名称。
    <!--#echo var="REMOTE_USER" -->
4、Fsize:显示指定文件的大小,可以结合config命令的sizefmt参数定制输出格式。

    <!--#fsize file="index_working.html" -->

  5、Flastmod:显示指定文件的最后修改日期,可以结合config 命令的timefmt参数控制输出格式。

    <!--#config timefmt="%A, the %d of %B, in the year %Y" -->
    <!--#flastmod file="file.html" -->

  这里,我们可以利用flastmod参数显示出一个页面上所有链接页面的更新日期。方法如下:

    <!--#config timefmt=" %B %d, %Y" -->
    <A HREF="/directory/file.html">File</A>
    <!--#flastmod virtual="/directory/file.html" -->
    <A HREF="/another_directory/another_file.html">Another File</A>
    <!--#flastmod virtual="/another_directory/another_file.html" -->
  显示结果为:
    File April 19, 2000
    Another File January 08, 2000

  6、Exec

  Exec命令可以执行CGI脚本或者shell命令。使用方法如下:

   Cmd:使用/bin/sh执行指定的字串。如果SSI使用了IncludesNOEXEC选项,则该命令将被屏蔽。

   Cgi:可以用来执行CGI脚本。例如,下面这个例子中使用服务端cgi-bin目录下的counter.pl脚本程序在每个页面放置一个计数器:

   <!--#exec cgi="/cgi-bin/counter.pl" -->
posted @ 2006-05-09 16:17 逍遥草 阅读(3283) | 评论 (0)编辑 收藏
 
DAO编程模式
J2EE开发人员使用数据访问对象(DAO)设计模式把底层的数据访问逻辑和高层的商务逻辑分开.实现DAO模式能够更加专注于编写数据访问代码.在这篇文章中,Java开发人员Sean C. Sullivan从三个方面讨论DAO编程的结构特征:事务划分,异常处理,日志记录.

在最近的18个月,我和一个优秀的软件开发团队一起工作,开发定制基于WEB的供应链管理应用程序.我们的应用程序访问广泛的持久层数据,包括出货状态,供应链制度,库存,货物发运,项目管理数据,和用户属性等.我们使用JDBC API连接我们公司的各种数据库平台,并且在整个应用程序中应用了DAO设计模式.
下图显示了应用程序和数据源的关系:
 

通过在整个应用程序中应用数据访问对象(DAO)设计模式使我们能够把底层的数据访问逻辑和上层的商务逻辑分开.我们为每个数据源创建了提供CRUD(创建,读取,更新,删除)操作的DAO类.

在之篇文章中,我将向你介绍DAO的实现策略以及创建更好的DAO类的技术.我会明确的介绍日志记录,异常处理,和事务划分三项技术.你将学在你的DAO类中怎样把这三种技术结合在一起.这篇文章假设你熟悉JDBC API,SQL和关系性数据库编程.

我们先来回顾一下DAO设计模式和数据访问对象.
DAO基础
DAO模式是标准的J2EE设计模式之一.开发人员使用这个模式把底层的数据访问操作和上层的商务逻辑分开.一个典型的DAO实现有下列几个组件:
1.    一个DAO工厂类;
2.    一个DAO接口;
3.    一个实现DAO接口的具体类;
4.    数据传递对象(有些时候叫做值对象).

具体的DAO类包含了从特定的数据源访问数据的逻辑。在下面的这段中你将学到设计和实现数据访问对象的技术。
事务划分:
关于DAO要记住的一件重要事情是它们是事务性对象。每个被DAO执行的操作(象创建,更新、或删除数据)都是和事务相关联的。同样的,事务划分(transaction demarcation)的概念是特别重要的。
事务划分是在事务界定定义中的方式。J2EE规范为事务划分描述了两种模式:编程性事务(programmatic)和声明性事务(declarative).下表是对这两种模式的拆分:
声明性事务划分    编程性事务划分
程序员使用EJB的布署描述符声明事务属性    程序员担负编写事务逻辑代码的责任。
运行时环境(EJB容器)使用这些属性来自动的管理事务。    应用程序通过一个API接口来控制事务。 

我将把注意力集中的编程性事务划分上。
象前面的介绍一样,DAOs是一些事务对象。一个典型的DAO要执行象创建、更新、和删除这的事务性操作。在设计一个DAO时,首先要问自己如下问题:
1、    事务将怎样开始?
2、    事务将怎样结束?
3、    那个对象将承担起动一个事务的责任?
4、    那个对象将承担结束一个事务的责任?
5、    DAO应该承担起动和结束事务的责任?
6、    应用程序需要交叉访问多个DAO吗?
7、    一个事务包含一个DAO还是多个DAO?
8、    一个DAO包含其它的DAO中的方法吗?

回答这些问题将有助于你为DAO对象选择最好的事务划分策略。对ADO中的事务划分有两个主要的策略。一种方法是使用DAO承担事务划分的责任;另一种是延期性事务,它把事务划分到调用DAO对象的方法中。如果你选择前者,你将要在DAO类中嵌入事务代码。如果你选择后者,事务代码将被写在DAO类的外部。我们将使用简单的代码实例来更好的理解这两种方法是怎样工作的。
实例1展示了一个带有两种数据操作的DAO:创建(create)和更新(update):
public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);
实例2展示了一个简单的事务,事务划分代码是在DAO类的外部。注意:在这个例子中的调用者把多个DOA操作组合到这个事务中。

tx.begin();    // start the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit();   // end the transaction

这种事务事务划分策略对在一个单一事务中访问多个DAO的应用程序来说尤为重要。

你即可使用JDBC API也可以使用Java 事务API(JTA)来实现事务的划分。JDBC事务划分比JTA事务划分简单,但是JTA提供了更好的灵活性。在下面的这段中,我们会进一步的看事务划分机制。
使用JDBC的事务划分
JDBC事务是使用Connection对象来控制的。JDBC的连接接口(java.sql.Connection)提供了两种事务模式:自动提交和手动提交。Java.sql.Connection为控制事务提供了下列方法:
.public void setAutoCommit(Boolean)
.public Boolean getAutoCommit()
.public void commit()
.public void rollback()
实例3展示怎样使用JDBC API来划分事务:
import java.sql.*;
import javax.sql.*;
// ...
DataSource ds = obtainDataSource();
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
// ...
pstmt = conn.prepareStatement("UPDATE MOVIES ...");
pstmt.setString(1, "The Great Escape");
pstmt.executeUpdate();
// ...
conn.commit();
// ...

使用JDBC事务划分,你能够把多个SQL语句组合到一个单一事务中。JDBC事务的缺点之一就是事务范围被限定在一个单一的数据库连接中。一个JDBC事务不能够跨越多个数据库。接下来,我们会看到怎样使用JTA来做事务划分的。因为JTA不象JDBC那样被广泛的了解,所以我首先概要的介绍一下JTA。

JTA概要介绍
Java事务API(JTA Java Transaction API)和它的同胞Java事务服务(JTS Java Transaction Service),为J2EE平台提供了分布式事务服务。一个分布式事务(distributed transaction)包括一个事务管理器(transaction manager)和一个或多个资源管理器(resource manager)。一个资源管理器(resource manager)是任意类型的持久化数据存储。事务管理器(transaction manager)承担着所有事务参与单元者的相互通讯的责任。下车站显示了事务管理器和资源管理的间的关系。
 

JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接。下列任一个Java平台的组件都可以参与到一个JTA事务中:
.JDBC连接
.JDO PersistenceManager 对象
.JMS 队列
.JMS 主题
.企业JavaBeans(EJB)
.一个用J2EE Connector Architecture 规范编译的资源分配器。

使用JTA的事务划分
要用JTA来划分一个事务,应用程序调用javax.transaction.UserTransaction接口中的方法。示例4显示了一个典型的JNDI搜索的UseTransaction对象。
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup("java:comp/UserTransaction");
UserTransaction utx = (UserTransaction) txObj;

应用程序有了UserTransaction对象的引用之后,就可以象示例5那样来起动事务。

utx.begin();
// ...
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement("UPDATE MOVIES ...");
pstmt.setString(1, "Spinal Tap");
pstmt.executeUpdate();
// ...
utx.commit();
// ...

当应用程序调用commit()时,事务管理器使用两段提交协议来结束事务。
JTA事务控制的方法
.javax.transaction.UserTransaction接口提供了下列事务控制方法:
.public void begin()
.public void commit()
.public void rollback()
.public void getStatus()
.public void setRollbackOnly()
.public void setTransactionTimeout(int)
应用程序调用begin()来起动事务,即可调用commit()也可以调用rollback()来结束事务。
使用JTA和JDBC
开发人员经常使用JDBC来作为DAO类中的底层数据操作。如果计划使用JTA来划分事务,你将需要一个实现了javax.sql.XADataSource,javax.sql.XAConnection和javax.sql.XAResource接口JDBC的驱动。实现了这些接口的驱动将有能力参与到JTA事务中。一个XADataSource对象是一个XAConnection对象的工厂。XAConnections是参与到JTA事务中的连接。

你需要使用应用程序服务器管理工具来建立XADataSource对象。对于特殊的指令请参考应用程序服务器文档和JDBC驱动文档。

J2EE应用程序使用JNDI来查找数据源。一旦应用程序有了一个数据源对象的引用,这会调用javax.sql.DataSource.getConnection()来获得数据库的连接。

XA连接区别于非XA连接。要记住的是XA连接是一个JTA事务中的参与者。这就意味着XA连接不支持JDBC的自动提交特性。也就是说应用程序不必在XA连接上调用java.sql.Connection.commit()或java.sql.Connection.rollback()。相反,应用程序应该使用UserTransaction.begin()、UserTransaction.commit()和UserTransaction.rollback().

选择最好的方法
我们已经讨论了JDBC和JTA是怎样划分事务的。每一种方法都有它的优点,回此你需要决定为你的应用程序选择一个最适应的方法。

在我们团队许多最近的对于事务划分的项目中使用JDBC API来创建DAO类。这DAO类总结如下:
.事务划分代码被嵌入到DAO类内部
.DAO类使用JDBC API来进行事务划分
.调用者没有划分事务的方法
.事务范围被限定在一个单一的JDBC连接

JDBC事务对复杂的企业应用程序不总是有效的。如果你的事务将跨越多个DAO对象或
多个数据库,那么下面的实现策略可能会更恰当。:
.用JTA对事务进行划分
.事务划分代码被DAO分开
.调用者承担划分事务的责任
.DAO参与一个全局的事务中

JDBC方法由于它的简易性而具有吸引力,JTA方法提供了更多灵活性。你选择什么样的实现将依赖于你的应用程序的特定需求。

日志记录和DAO
一个好的DAO实现类将使用日志记录来捕获有关它在运行时的行为细节。你可以选择记录异常、配置信息、连接状态、JDBC驱动程序的元数据或查询参数。日志对开发整个阶段都是有益的。我经常检查应用程序在开发期间、测试期间和产品中的日志记录。
在这段中,我们将展现一段如何把Jakarta Commaons Logging结合中一个DAO中的例子。在我们开始之前,让我们先回顾一些基础知识。

选择一个日志例库
许多开发人员使用的基本日志形式是:System.out.println和System.err.println.Println语句。这种形式快捷方便,但它们不能提供一个完整的日志系统的的能力。下表列出了Java平台的日志类库:
日志类库    开源吗?    URL
Java.util.logging    否    http://java.sun.com/j2ee

Jakarta Log4j    是    http://hajarta.apache.org/log4j/
Jakarta Commons Logging    是    http:/Jakarta.apache.org/commons/logging.html

Java.util.logging是J2SE1.4平台上的标准的API。但是,大多数开发人员都认为Jakarta Log4j提供了更大的功能性和灵活性。Log4j超越java.util.logging的优点之一就是它支持J2SE1.3和J2SE1.4平台。

Jakarta Commons Logging能够被用于和java.util.loggin或Jakarta Log4j一起工作。Commons Logging是一个把你的应用程序独立于日志实现的提取层。使用Commons Logging你能够通过改变一个配置文件来与下面的日志实现来交换数据。Commons Logging被用于JAKARTA Struts1.1和Jakarta HttpClient2.0中。

一个日志示例
示例7显示了在一个DOA类中怎样使用Jakarta Commons Logging

import org.apache.commons.logging.*;
class DocumentDAOImpl implements DocumentDAO
{
      static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);
      public void deleteDocument(String id)
      {
          // ...
          log.debug("deleting document: " + id);
          // ...
          try
          {
              // ... data operations ...
          }
          catch (SomeException ex)
          {
              log.error("Unable to delete document", ex);
              // ... handle the exception ...
  }
      }
}

日志是评估应用程序的基本部分。如果你在一个DAO中遇到了失败,日志经常会为理解发生的什么错误提供最好的信息。把日志结合到你的DAO中,确保得到调试和解决问题的有效手段。

DAO中的异常处理
我们已经看了事务划分和日志记录,并且现在对于它们是怎样应用于数据访问对象的有一个深入的理解。我们第三部分也是最后要讨论的是异常处理。下面的一些简单的异常处理方针使用你的DAO更容易使用,更加健壮和更具有可维护性。

在实现DAO模式的时候,要考滤下面的问题:
.在DAO的public接口中的方法将抛出被检查的异常吗?
.如果是,将抛出什么样的检查性异常?
.在DAO实现类中怎能样处理异常。
在用DAO模式工作的过程中,我们的团队为异常处理开发了一组方针。下面的这些方针会很大程度的改善你的DAO:
.DAO方法应该抛出有意义的异常。
.DAO方法不应该抛出java.lang.Exception异常。因为java.lang.Exception太一般化,它不能包含有关潜在问题的所有信息。
.DAO方法不应该抛出java.sql.SQLException异常。SQLException是一个底层的JDBC异常,DAO应用努力封装JDBC异常而不应该把JDBC异常留给应用程序的其它部分。
.在DAO接口中的方法应该只抛出调用者期望处理的检查性异常。如果调用者不能用适当的方法来处理异常,考滤抛出不检查性(运行时run-time)异常。

.如果你的数据访问代码捕获了一个异常,不可要忽略它。忽略捕获异常的DAO是很处理的。
.使用异常链把底层的异常传递给高层的某个处理器。
.考滤定义一个标准的DAO异常类。Spring框架提供了一个优秀的预定义的DAO异常类的集合。
看Resources,查看有异常和异常处理技术的更详细信息。

实现示例:MovieDAO
MoveDAO是一个示范了在这篇文章中所讨论的所有技术,包括事务划分、日志记录和异常处理。你会在Resources段找到MovieDAO的源代码。它被分下面的三个包:
.daoexamples.exception
.daoexamples.move
.daoexamples.moviedemo

这个DAO模式的实现由下面的类和接口组成:
.daoexamples.movie.MovieDAOFactory
.daoexamples.movie.MovieDAO
.daoexamples.movie.MovieDAOImpl
.daoexamples.movie.MovieDAOImplJTA
.daoexamples.movie.Movie
.daoexamples.movie.MovieImple
.daoexamples.movie.MovieNotFoundException
.daoexamples.movie.MovieUtil

MovieDAO接口定义了DAO的数据操作。这个接口有如下五个方法:
.public Movie findMovieById(String id)
.public java.util.Collection findMoviesByYear(String year)
.public void deleteMovie(String id)
.public Movie createMovie(String rating,String year,String title)
.public void updateMovie(String id,String rating,String year,String title)
daoexamples.movie包包含了两个MovieDAO接口的实现。每个实现使用了一个同的事务划分方法,如下表所示:
    MovieDAOImpl    MovieDAOImplJTA
实现了MovieDAO接口吗?    Yes    Yes
通过JNDI获得DataSource吗?    Yes    Yes
从一个DataSource获得java.sql.Connection对象吗?    Yes    Yes
DAO界定内部的事务吗?    Yes    No
使用JDBC事务吗?    Yes    No
使用一个XA DataSource吗?    No    Yes
分担JTA事务吗?    No    Yes


MovieDAO 示范应用程序
这个示范应用程序是一个叫做daoexamples.moviedemo.DemoServlet.DemoServlet的servlet类,它使用Movie DAO来查询和更新一个表中的movie数据。

这个servlet示范了把JTA感知的MovieDAO和Java消息服务组合到一个单一的事务中,如示例8所示:
UserTransaction utx = MovieUtil.getUserTransaction();
utx.begin();
batman = dao.createMovie("R",
      "2008",
      "Batman Reloaded");
publisher = new MessagePublisher();
publisher.publishTextMessage("I'll be back");
dao.updateMovie(topgun.getId(),
      "PG-13",
      topgun.getReleaseYear(),
      topgun.getTitle());
dao.deleteMovie(legallyblonde.getId());
utx.commit();

要运行这个范例应用程序,在你的应用程序服务器中配置一个XA 数据源和一个非XA数据源。然后布署daoexamples.ear文件。这个应用程序将运行在任何与J2EE兼容的应用程序服务器。
posted @ 2006-03-31 17:58 逍遥草 阅读(267) | 评论 (0)编辑 收藏
仅列出标题
共5页: 1 2 3 4 5