关于作者

用户名:chris_jian
笔名:chris_jian
地区:

日历  

快速登录

+ 用户名:
+ 密 码:

我的博采 我的论坛 我的RSS

在线留言


留言后如果没有显示请刷新页面

最新评论

Oracle&Java

Download&Crack

Book&MTV&MP3

Mail&Job

Friend&Link

访问统计:1262



Powered by BlogDriver 2.1

蜗牛三少的专栏

 

文章

你是老鸟:网络经典命令(转贴)

这是一些值得记忆的命令哦


1、测试物理网络

命令:ping 192.168.0.8 -t ,参数-t是等待用户去中断测试

友情提示:这个是最基本,最常用的网络命令

2.查看DNS、IP、Mac等信息

A.Win98:winipcfg

B.Win2000以上:Ipconfig/all

3.网络信使

命令:Net send 计算机名/IP * (广播) 传送内容,注意不能跨网段

命令:net stop messenger 停止信使服务,也可以在面板-服务修改

命令:net start messenger 开始信使服务

4.探测对方计算机名,所在的组、域及当前用户名 (追捕的工作原理)

命令:ping -a IP -t ,只显示NetBios名

命令:nbtstat -a IP 比较全的

5.netstat -a 显示出你的计算机当前所开放的所有端口

命令:netstat -s -e 

比较详细的显示你的网络资料,包括TCP、UDP、ICMP 和 IP的统计等

6.探测arp绑定(动态和静态)列表,显示所有连接了我的计算机,显示对方IP和MAC地址

命令:arp -a

7.在代理服务器端

捆绑IP和MAC地址,解决局域网内盗用IP!:

命令:ARP -s 192.168.10.59 00 -50-ff-6c-08-75

解除网卡的IP与MAC地址的绑定:

命令:arp -d 网卡IP

8.在网络邻居上隐藏你的计算机

命令:net config server /hidden:yes

命令:net config server /hidden:no 则为开启

9.net命令

A.显示当前工作组服务器列表 net view,当不带选项使用本命令时,它就会显示当前域或网络上的计算机上的列表。

比如:查看这个IP上的共享资源,就可以

C:>net view 192.168.10.8

在 192.168.10.8 的共享资源

资源共享名 类型 用途 注释

 

网站服务 Disk

命令成功完成。

B.查看计算机上的用户帐号列表 net user

C.查看网络链接 net use

例如:net use z: 192.168.10.8movie 将这个IP的movie共享目录映射为本地的Z盘

D.记录链接 net session

例如: C:>net session

计算机 用户名 客户类型 打开空闲时间

 

192.168.10.110 ROME Windows 2000 2195 0 00:03:12

192.168.10.51 ROME Windows 2000 2195 0 00:00:39

命令成功完成。

10.路由跟踪命令

A.tracert software.pchome.net

B.pathping software.pchome.net 除了显示路由外,还提供325S的分析,计算丢失包的%

11.关于共享安全的几个命令

A.查看你机器的共享资源 net share

B.手工删除共享(可以编个bat文件,开机自运行,把共享都删了!)

命令:net share c$ /d

命令:net share d$ /d

命令:net share ipc$ /d

命令:net share admin$ /d

注意$后有空格。

C.增加一个共享:

c:net share mymovie=e:downloadsmovie /users:1

mymovie 共享成功。

同时限制链接用户数为1人。

12.在DOS行下设置静态IP

A.设置静态IP

CMD

netsh

netsh>int

interface>ip

interface ip>set add "本地链接" static IP地址 mask gateway

B.查看IP设置

interface ip>show address

Arp

显示和修改"地址解析协议 (ARP)"缓存中的项目。ARP 缓存中包含一个或多个表,它们用于存储 IP 地址及其经过解析的以太网或令牌环物理地址。计算机上安装的每一个以太网或令牌环网络适配器都有自己单独的表。如果在没有参数的情况下使用,则 arp 命令将显示帮助信息。

- 作者: chris_jian 2005年02月25日, 星期五 22:18  回复(0) |  引用(0) 加入博采

MSComm控件使用详解

MSComm控件使用详解


MSComm控件使用详解


2004-05-27  
 
MSComm 控件通过串行端口传输和接收数据,为应用程序提供串行通讯功能。MSComm控件在串口编程时非常方便,程序员不必去花时间去了解较为复杂的API函数,而且在VC、VB、Delphi等语言中均可使用。 Microsoft Communications Control(以下简称MSComm)是Microsoft公司提供的简化Windows下串行通信编程的ActiveX控件,它为应用程序提供了通过串行接口收发数据的简便方法。具体的来说,它提供了两种处理通信问题的方法:一是事件驱动(Event-driven)方法,一是查询法。

1.MSComm控件两种处理通讯的方式

MSComm控件提供下列两种处理通讯的方式:事件驱动方式和查询方式。
1.1 事件驱动方式

事件驱动通讯是处理串行端口交互作用的一种非常有效的方法。在许多情况下,在事件发生时需要得到通知,例如,在串口接收缓冲区中有字符,或者 Carrier Detect (CD) 或 Request To Send (RTS) 线上一个字符到达或一个变化发生时。在这些情况下,可以利用 MSComm 控件的 OnComm 事件捕获并处理这些通讯事件。OnComm 事件还可以检查和处理通讯错误。所有通讯事件和通讯错误的列表,参阅 CommEvent 属性。在编程过程中,就可以在OnComm事件处理函数中加入自己的处理代码。这种方法的优点是程序响应及时,可靠性高。每个MSComm 控件对应着一个串行端口。如果应用程序需要访问多个串行端口,必须使用多个 MSComm 控件。

1.2 查询方式

查询方式实质上还是事件驱动,但在有些情况下,这种方式显得更为便捷。在程序的每个关键功能之后,可以通过检查 CommEvent 属性的值来查询事件和错误。如果应用程序较小,并且是自保持的,这种方法可能是更可取的。例如,如果写一个简单的电话拨号程序,则没有必要对每接收一个字符都产生事件,因为唯一等待接收的字符是调制解调器的"确定"响应。

2.MSComm 控件的常用属性
MSComm控件有很多重要的属性,但首先必须熟悉几个属性。

CommPort 设置并返回通讯端口号。
Settings 以字符串的形式设置并返回波特率、奇偶校验、数据位、停止位。
PortOpen 设置并返回通讯端口的状态。也可以打开和关闭端口。
Input 从接收缓冲区返回和删除字符。
Output 向传输缓冲区写一个字符串。

下面分别描述:

CommPort属性 设置并返回通讯端口号。
语法 object.CommPort[value ] (value 一整型值,说明端口号。)
说明 在设计时,value 可以设置成从 1 到 16 的任何数(缺省值为 1)。但是如果用 PortOpen 属性打开一个并不存在的端口时,MSComm 控件会产生错误 68(设备无效)。
注意:必须在打开端口之前设置 CommPort 属性。

RThreshold 属性:在 MSComm 控件设置 CommEvent 属性为 comEvReceive 并产生 OnComm 之前,设置并返回的要接收的字符数。
语法 object.Rthreshold [ = value ](value 整型表达式,说明在产生 OnComm 事件之前要接收的字符数。 )
说明 当接收字符后,若 Rthreshold 属性设置为 0(缺省值)则不产生 OnComm 事件。例如,设置 Rthreshold 为 1,接收缓冲区收到每一个字符都会使 MSComm 控件产生 OnComm 事件。

CTSHolding 属性:确定是否可通过查询 Clear To Send (CTS) 线的状态发送数据。Clear To Send 是调制解调器发送到相联计算机的信号,指示传输可以进行。该属性在设计时无效,在运行时为只读。
语法: object.CTSHolding(Boolean)

Mscomm 控件的 CTSHolding 属性设置值:
True Clear To Send 线为高电平。
False Clear To Send 线为低电平。

说明:如果 Clear To Send 线为低电平 (CTSHolding = False) 并且超时时,MSComm 控件设置 CommEvent 属性为 comEventCTSTO (Clear To Send Timeout) 并产生 OnComm 事件。

Clear To Send 线用于 RTS/CTS (Request To Send/Clear To Send) 硬件握手。如果需要确定 Clear To Send 线的状态,CTSHolding 属性给出一种手工查询的方法。

详细信息 有关握手协议,请参阅 Handshaking 属性。

SThreshold 属性: MSComm 控件设置 CommEvent 属性为 comEvSend 并产生 OnComm 事件之前,设置并返回传输缓冲区中允许的最小字符数。

语法 object.SThreshold [ = value ]
value 整形表达式,代表在 OnComm 事件产生之前在传输缓冲区中的最小字符数。

说明:若设置 Sthreshold 属性为 0(缺省值),数据传输事件不会产生 OnComm 事件。若设置 Sthreshold 属性为 1,当传输缓冲区完全空时,MSComm 控件产生 OnComm 事件。如果在传输缓冲区中的字符数小于 value,CommEvent 属性设置为 comEvSend,并产生 OnComm 事件。comEvSend 事件仅当字符数与 Sthreshold 交叉时被激活一次。例如,如果 Sthreshold 等于 5,仅当在输出队列中字符数从 5 降到 4 时,comEvSend 才发生。如果在输出队列中从没有比 Sthreshold 多的字符,comEvSend 事件将绝不会发生。


Handshake 常数

常数 值 描述
comNone 0 无握手。
comXonXoff 1 XOn/Xoff 握手。
comRTS 2 Request-to-send/clear-to-send 握手。
comRTSXOnXOff 3 Request-to-send 和 clear-to-send 握手皆可。


OnComm 常数

常数 值 描述
comEvSend 1 发送事件。
comEvReceive 2 接收事件。
comEvCTS 3 clear-to-send 线变化。
comEvDSR 4 data-set ready 线变化。
comEvCD 5 carrier detect 线变化。
comEvRing 6 振铃检测。
comEvEOF 7 文件结束。


Error 常数

常数 值 描述
comEventBreak 1001 接收到中断信号
comEventCTSTO 1002 Clear-to-send 超时
comEventDSRTO 1003 Data-set ready 超时
comEventFrame 1004 帧错误
comEventOverrun 1006 端口超速
comEventCDTO 1007 Carrier detect 超时
comEventRxOver 1008 接收缓冲区溢出
comEventRxParity 1009 Parity 错误
comEventTxFull 1010 传输缓冲区满
comEventDCB 1011 检索端口 设备控制块 (DCB) 时的意外错误

InputMode 常数
常数 值 描述
comInputModeText 0 (缺省)通过 Input 属性以文本方式取回数据。
comInputModeBinary 1 通过 Input 属性以二进制方式检取回数据。

CDHolding 属性:通过查询 Carrier Detect (CD) 线的状态确定当前是否有传输。Carrier Detect 是从调制解调器发送到相联计算机的一个信号,指示调制解调器正在联机。该属性在设计时无效,在运行时为只读。

语法 object.CDHolding
设置值:CDHolding 属性的设置值为:
设置 描述
True Carrier Detect 线为高电平
False Carrier Detect 线为低电平
说明:注意当 Carrier Detect 线为高电平 (CDHolding = True) 且超时时,MSComm 控件设置CommEvent 属性为 comEventCDTO(Carrier Detect 超时错误),并产生 OnComm 事件。
注意 在主机应用程序中捕获一个丢失的传输是特别重要的,例如一个公告板,因为呼叫者可以随时挂起(放弃传输)。
Carrier Detect 也被称为 Receive Line Signal Detect (RLSD)。
数据类型 Boolean

DSRHolding 属性:确定 Data Set Ready (DSR) 线的状态。Data Set Ready 信号由调制解调器发送到相连计算机,指示作好操作准备。该属性在设计时无效,在运行时为只读。
语法:object.DSRHolding
object 所在处表示对象表达式,其值是"应用于"列表中的对象。
DSRHolding 属性返回以下值:
值 描述
True Data Set Ready 线高
False Data Set Ready 线低
说明:当 Data Set Ready 线为高电平 (DSRHolding = True) 且超时时,MSComm 控件设置 CommEvent 属性为 comEventDSRTO(数据准备超时)并产生 OnComm 事件。
当为 Data Terminal Equipment (DTE) 机器写 Data Set Ready/Data Terminal Ready 握手例程时该属性是十分有用的。
数据类型:Boolean


Settings 属性: 设置并返回波特率、奇偶校验、数据位、停止位参数。

语法: object.Settings[ = value]
说明:当端口打开时,如果 value 非法,则 MSComm 控件产生错误 380(非法属性值)。
Value 由四个设置值组成,有如下的格式:
"BBBB,P,D,S "
BBBB 为波特率,P 为奇偶校验,D 为数据位数,S 为停止位数。value 的缺省值是:
"9600,N,8,1 "


InputLen 属性:设置并返回 Input 属性从接收缓冲区读取的字符数。

语法 object.InputLen [ = value]
InputLen 属性语法包括下列部分:
value 整型表达式,说明 Input 属性从接收缓冲区中读取的字符数。
说明:InputLen 属性的缺省值是 0。设置 InputLen 为 0 时,使用 Input 将使 MSComm 控件读取接收缓冲区中全部.

- 作者: chris_jian 2004年12月31日, 星期五 14:08  回复(0) |  引用(0) 加入博采

Java打印程序设计
Java打印程序设计(未做验证)

Java打印程序设计
内容:
前言
JAVA中的打印
JAVA打印程序设计实例
结束语
参考资料
附件
关于作者
Java 专区中还有:
教学
工具与产品
代码与组件
所有文章
实用技巧

文枫 (wenfb@sina.com)
深圳全通数码技术总监
2002 年 10 月

1 前言

在我们的实际工作中,经常需要实现打印功能。但由于历史原因,Java提供的打印功能一直都比较弱。实际上最初的jdk根本不支持打印,直到jdk1.1才引入了很轻量的打印支持。所以,在以前用Java/Applet/JSP/Servlet设计的程序中,较复杂的打印都是通过调用ActiveX/OCX控件或者VB/VC程序来实现的,非常麻烦。实际上,SUN公司也一直致力于Java打印功能的完善,而Java2平台则终于有了一个健壮的打印模式的开端,该打印模式与Java2D图形包充分结合成一体。更令人鼓舞的是,新发布的jdk1.4则提供了一套完整的"Java 打印服务 API" (Java Print Service API),它对已有的打印功能是积极的补充。利用它,我们可以实现大部分实际应用需求,包括打印文字、图形、文件及打印预览等等。本文将通过一个具体的程序实例来说明如何设计Java打印程序以实现这些功能,并对不同版本的实现方法进行分析比较。希望大家能从中获取一些有益的提示。

2 Java中的打印

2.1 Java的打印API

Java的打印API主要存在于java.awt.print包中。而jdk1.4新增的类则主要存在于javax.print包及其相应的子包javax.print.event和javax.print.attribute中。其中javax.print包中主要包含打印服务的相关类,而javax.print.event则包含打印事件的相关定义,javax.print.attribute则包括打印服务的可用属性列表等。

2.2 如何实现打印

要产生一个打印,至少需要考虑两条:

  1. 需要一个打印服务对象。这可通过三种方式实现:在jdk1.4之前的版本,必须要实现java.awt.print.Printable接口或通过Toolkit.getDefaultToolkit().getPrintJob来获取打印服务对象;在jdk1.4中则还可以通过javax.print.PrintSerivceLookup来查找定位一个打印服务对象。
  2. 需要开始一个打印工作。这也有几种实现方法:在jdk1.4之前可以通过java.awt.print.PrintJob(jdk1.1提供的,现在已经很少用了)调用print或printAll方法开始打印工作;也可以通过java.awt.print.PrinterJob的printDialog显示打印对话框,然后通过print方法开始打印;在jdk1.4中则可以通过javax.print.ServiceUI的printDialog显示打印对话框,然后调用print方法开始一个打印工作。

2.3 打印机对话框

2.3.1 Printable的打印对话框

开始打印工作之前,可以通过PrinterJob.printDialog来显示一个打印对话框。它给用户一个机会以选择应该打印的页码范围,并可供用户改变打印设置。它是一个本地对话框。

 


 

事实上,当从一个Printable对象进行一个打印工作时,打印对象并不知道需要打印多少页。它只是不停地调用print方法。只要print方法返回Printable.PAGE_EXISTS值,打印工作就不停地产生打印页,直到print方法返回Printable.NO_SUCH_PAGE时,打印工作才停止。

由于打印工作只有在打印完成后才进行准确的页数计算,所以在对话框上的页码范围是尚未初始化的[1,9999]。我们可以通过构建一个java.awt.print.Book对象传递给打印对象;也可以通过指定的格式计算需要打印的页数并传递给打印对象,使其准确地知道要打印多少页。

2.3.2 ServiceUI的打印对话框

与Printable的对话框不同的是,在jdk1.4提供ServiceUI的打印机对话框的缺省行为已经用新的 API 更改了:缺省情况下对话框不显示。我们必须使用ServiceUI类调用printDialog方法创建如下所示的打印对话框。

 


 

3 Java打印程序设计实例

3.1 打印文本

3.1.1 应用场景

假设我们需要打印一个窗体的某个文本编辑域(可能只有几行,也可能包含多页)的内容,并且每页最多打印54行,如何实现呢?

3.1.2 解决方法

基本思路如下:首先我们需要实现Printable接口,然后按照每页最多54行的格式计算共需要打印多少页,当打印文本的按钮被点击时,执行相应的打印动作。打印文本的具体操作可通过Graphics2D的drawString方法来实现。

1、实现Printable接口

/*Graphic指明打印的图形环境;PageFormat指明打印页格式(页面大小以点为计量单位,
1点为1英才的1/72,1英寸为25.4毫米。A4纸大致为595×842点);page指明页号*/
public int print(Graphics g, PageFormat pf, int page) throws PrinterException
{
    Graphics2D g2 = (Graphics2D)g;
    g2.setPaint(Color.black); //设置打印颜色为黑色
	if (page >= PAGES) //当打印页号大于需要打印的总页数时,打印工作结束
		return Printable.NO_SUCH_PAGE;
    g2.translate(pf.getImageableX(), pf.getImageableY());//转换坐标,确定打印边界
    drawCurrentPageText(g2, pf, page); //打印当前页文本
return Printable.PAGE_EXISTS; //存在打印页时,继续打印工作
}
	/*打印指定页号的具体文本内容*/
    private void drawCurrentPageText(Graphics2D g2, PageFormat pf, int page)
	{
		String s = getDrawText(printStr)[page];//获取当前页的待打印文本内容
        //获取默认字体及相应的尺寸
FontRenderContext context = g2.getFontRenderContext();
        Font f = area.getFont();
        String drawText;
        float ascent = 16; 	//给定字符点阵
        int k, i = f.getSize(), lines = 0;
        while(s.length() > 0 && lines < 54) //每页限定在54行以内
        {
        	k = s.indexOf('\n'); //获取每一个回车符的位置
            if (k != -1)  //存在回车符
            {
            	lines += 1; //计算行数
                drawText = s.substring(0, k); //获取每一行文本
                g2.drawString(drawText, 0, ascent); //具体打印每一行文本,同时走纸移位
                if (s.substring(k + 1).length() > 0)
                {
                	s = s.substring(k + 1); //截取尚未打印的文本
                    ascent += i; 
                }
            }
            else //不存在回车符
            {
            	lines += 1; //计算行数
               	drawText = s; //获取每一行文本
                g2.drawString(drawText, 0, ascent); //具体打印每一行文本,同时走纸移位
                s = ""; //文本已结束
            }
        }
    }
	/*将打印目标文本按页存放为字符串数组*/
	public String[] getDrawText(String s)
    {
        String[] drawText = new String[PAGES];//根据页数初始化数组
        for (int i = 0; i < PAGES; i++)
            drawText[i] = ""; //数组元素初始化为空字符串
		
        int k, suffix = 0, lines = 0;
        while(s.length() > 0)
        {
            if(lines < 54) //不够一页时
            {
                k = s.indexOf('\n');
                if (k != -1) //存在回车符
                {
                    lines += 1; //行数累加
					//计算该页的具体文本内容,存放到相应下标的数组元素
                    drawText[suffix] = drawText[suffix] + s.substring(0, k + 1);
                    if (s.substring(k + 1).length() > 0)
                        s = s.substring(k + 1);
                }
                else
                {
                    lines += 1; //行数累加
					//将文本内容存放到相应的数组元素
                    drawText[suffix] = drawText[suffix] + s; 
                    s = "";
                }
            }
            else //已满一页时
            {
                lines = 0; //行数统计清零
                suffix++; //数组下标加1
            }
        }
        return drawText;
    }
	

2、计算需要打印的总页数

    public int getPagesCount(String curStr)
	{
        int page = 0;
        int position, count = 0;
        String str = curStr;
	    while(str.length() > 0) //文本尚未计算完毕
	    {
	        position = str.indexOf('\n'); //计算回车符的位置
            count += 1; //统计行数
	        if (position != -1)
                str = str.substring(position + 1); //截取尚未计算的文本
	        else
	            str = ""; //文本已计算完毕
	    }

	    if (count > 0)
	        page = count / 54 + 1; //以总行数除以54获取总页数

        return page; //返回需打印的总页数
	}
	

3.1、以jdk1.4以前的版本实现打印动作按钮监听,并完成具体的打印操作

	private void printTextAction()
    {
        printStr = area.getText().trim(); //获取需要打印的目标文本
        if (printStr != null && printStr.length() > 0) //当打印内容不为空时
        {
            PAGES = getPagesCount(printStr); //获取打印总页数
            PrinterJob myPrtJob = PrinterJob.getPrinterJob(); //获取默认打印作业
            PageFormat pageFormat = myPrtJob.defaultPage(); //获取默认打印页面格式
            myPrtJob.setPrintable(this, pageFormat); //设置打印工作
            if (myPrtJob.printDialog()) //显示打印对话框
            {
                try
                {
                    myPrtJob.print(); //进行每一页的具体打印操作
                }
                catch(PrinterException pe)
                {
                    pe.printStackTrace();
                }
            }
        }
        else
        {
			//如果打印内容为空时,提示用户打印将取消
            JOptionPane.showConfirmDialog
			(null, "Sorry, Printer Job is Empty, Print Cancelled!", "Empty", 
			JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE);
        }
    }
	

3.2、以jdk1.4新版本提供的API实现打印动作按钮监听,并完成具体的打印操作

    private void printText2Action()
    {
        printFlag = 0; //打印标志清零
        printStr = area.getText().trim();//获取需要打印的目标文本
        if (printStr != null && printStr.length() > 0) //当打印内容不为空时
        {
            PAGES = getPagesCount(printStr); //获取打印总页数
			//指定打印输出格式
            DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
            //定位默认的打印服务
PrintService printService = PrintServiceLookup.lookupDefaultPrintService();
            //创建打印作业
DocPrintJob job = printService.createPrintJob();
            //设置打印属性
PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
            DocAttributeSet das = new HashDocAttributeSet();
            //指定打印内容
Doc doc = new SimpleDoc(this, flavor, das);
			//不显示打印对话框,直接进行打印工作
            try
            {
                job.print(doc, pras); //进行每一页的具体打印操作
            }
            catch(PrintException pe)
            {
                pe.printStackTrace();
            }
        }
        else
        {
            //如果打印内容为空时,提示用户打印将取消
JOptionPane.showConfirmDialog(null, "Sorry, Printer Job is Empty, Print Cancelled!", "Empty", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE);
        }
    }
	

3.2 打印预览

3.2.1应用场景

大多少商业应用都需要提供打印预览机制,它可以让我们在屏幕上看到页面,这样就不会因为不喜欢的打印结果而浪费纸张。假设我们在打印上一节所说的文本之前,需要先进行打印预览。那么该怎么实现呢?

界面实现图示如下:(Next预览下一页,Preview预览前一页,Close则关闭预览)

 


 

3.2.2解决方法

基本思路:虽然Java2平台的打印API并不提供标准的打印预览对话框,但是自己来进行设计也并不复杂。正常情况下,print方法将页面环境绘制到一个打印机图形环境上,从而实现打印。而事实上,print方法并不能真正产生打印页面,它只是将待打印内容绘制到图形环境上。所以,我们可以忽略掉屏幕图形环境,经过适当的缩放比例,使整个打印页容纳在一个屏幕矩形里,从而实现精确的打印预览。

在打印预览的设计实现中,主要需要解决两个问题。第一,如何将打印内容按合适的比例绘制到屏幕;第二,如何实现前后翻页。下面我给出这两个问题的具体实现方法,完整的实现请参看附件中的PrintPreviewDialog.java文件。

/*将待打印内容按比例绘制到屏幕*/
public void paintComponent(Graphics g)
    {
super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;
        PageFormat pf = PrinterJob.getPrinterJob().defaultPage(); //获取页面格式

        double xoff; //在屏幕上页面初始位置的水平偏移
        double yoff; //在屏幕上页面初始位置的垂直偏移
        double scale; //在屏幕上适合页面的比例
        double px = pf.getWidth(); //页面宽度
        double py = pf.getHeight(); //页面高度
        double sx = getWidth() - 1;
        double sy = getHeight() - 1;
        if (px / py < sx / sy)
        {
        	scale = sy / py; //计算比例
            xoff = 0.5 * (sx - scale * px); //水平偏移量
            yoff = 0;
        }
        else
        {
        	scale = sx / px; //计算比例
            xoff = 0;
            yoff = 0.5 * (sy - scale * py); //垂直偏移量
        }
        g2.translate((float)xoff, (float)yoff); //转换坐标
        g2.scale((float)scale, (float)scale);

        Rectangle2D page = new Rectangle2D.Double(0, 0, px, py); //绘制页面矩形
        g2.setPaint(Color.white); //设置页面背景为白色
        g2.fill(page);
        g2.setPaint(Color.black);//设置页面文字为黑色
        g2.draw(page);

        try
        {
        	preview.print(g2, pf, currentPage); //显示指定的预览页面
        }
        catch(PrinterException pe)
        {
        	g2.draw(new Line2D.Double(0, 0, px, py));
            g2.draw(new Line2D.Double(0, px, 0, py));
        }
}
/*预览指定的页面*/
public void viewPage(int pos)
    {
    	int newPage = currentPage + pos;
		//指定页面在实际的范围内
        if (0 <= newPage && newPage < preview.getPagesCount(printStr))
        {
        	currentPage = newPage; //将指定页面赋值为当前页
            repaint();
        }
    }
	

这样,在按下"Next"按钮时,只需要调用canvas.viewPage(1);而在按下"Preview"按钮时,只需要调用canvas.viewPage(-1)即可实现预览的前后翻页。

3.3 打印图形

3.3.1应用场景

在实际应用中,我们还需要打印图形。譬如,我们有时需要将一个Java Applet的完整界面或一个应用程序窗体及其所包含的全部组件都打印出来,又应该如何实现呢?

3.3.2解决方法

基本思路如下:在Java的Component类及其派生类中都提供了print和printAll方法,只要设置好属性就可以直接调用这两个方法,从而实现对组件及图形的打印。

/*打印指定的窗体及其包含的组件*/
private void printFrameAction()
{
Toolkit kit = Toolkit.getDefaultToolkit(); //获取工具箱
    Properties props = new Properties();
    props.put("awt.print.printer", "durango");//设置打印属性
    props.put("awt.print.numCopies", "2");
    if(kit != null)
    {
    	//获取工具箱自带的打印对象
PrintJob printJob = kit.getPrintJob(this, "Print Frame", props); 
        if(printJob != null)
        {
        	Graphics pg = printJob.getGraphics();//获取打印对象的图形环境
            if(pg != null)
            {
				try
				{
                	this.printAll(pg);//打印该窗体及其所有的组件
                }
                finally
                {
                	pg.dispose();//注销图形环境
                }
            }
            printJob.end();//结束打印作业
    	}
    }
}

3.4 打印文件

3.4.1应用场景

在很多实际应用情况下,我们可能都需要打印用户指定的某一个文件。该文件可能是图形文件,如GIF、JPEG等等;也可能是文本文件,如TXT、Java文件等等;还可能是复杂的PDF、DOC文件等等。那么对于这样的打印需求,我们又应该如何实现呢?

3.4.2解决方法

基本思路:在jdk1.4以前的版本,要实现这样的打印功能将非常麻烦和复杂,甚至是难以想象的。但幸运的是,jdk1.4的打印服务API提供了一整套的打印文件流的类和方法。利用它们,我们可以非常方便快捷地实现各式各样不同类型文件的打印功能。下面给出一个通用的处理方法。

/*打印指定的文件*/
private void printFileAction()
{
//构造一个文件选择器,默认为当前目录
JFileChooser fileChooser = new JFileChooser(SystemProperties.USER_DIR);
    int state = fileChooser.showOpenDialog(this);//弹出文件选择对话框
    if (state == fileChooser.APPROVE_OPTION)//如果用户选定了文件
    {
    	File file = fileChooser.getSelectedFile();//获取选择的文件
		//构建打印请求属性集
        PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
		//设置打印格式,因为未确定文件类型,这里选择AUTOSENSE
        DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE;
		//查找所有的可用打印服务
        PrintService printService[] = PrintServiceLookup.lookupPrintServices(flavor, pras);
		//定位默认的打印服务
        PrintService defaultService = PrintServiceLookup.lookupDefaultPrintService();
        //显示打印对话框
PrintService service = ServiceUI.printDialog(null, 200, 200, printService
                                           , defaultService, flavor, pras);
        if (service != null)
        {
        	try
            {
            	DocPrintJob job = service.createPrintJob();//创建打印作业
                FileInputStream fis = new FileInputStream(file);//构造待打印的文件流
                DocAttributeSet das = new HashDocAttributeSet();
                Doc doc = new SimpleDoc(fis, flavor, das);//建立打印文件格式
                job.print(doc, pras);//进行文件的打印
            }
            catch(Exception e)
            {
            	e.printStackTrace();
            }
        }
    }
}

在上面的示例中,因尚未确定文件的类型,所以将指定文件的打印格式定义为DocFlavor.INPUT_STREAM.AUTOSENSE。事实上,如果在进行打印之前,就已确定地知道文件的格式,如为GIF,就应定义为DocFlavor.INPUT_STREAM.GIF ;如为PDF,就应该定义为DocFlavor.INPUT_STREAM.PDF;如为纯ASCII文件,就可以定义为 DocFlavor.INPUT_STREAM.TEXT_HTML_US_ASCII。等等。jdk1.4的javax.print.DocFlavor提供了极为丰富的文件流类型,你可以根据具体的应用需求进行合适的选择。具体的API参考文档可见本文的参考资料3。

4 结束语

以上是本人在两年多J2EE应用开发中,总结的关于用Java进行打印程序设计的一些经验,希望能给大家一些启示和裨益。尽管目前用Java来实现打印功能与用Microsoft的MFC API相比确实有更多的麻烦。但jdk1.4的推出,对Java以前较弱的打印功能是一个极好的补充。相信大家如果能够很好地理解前文所述的打印程序设计实例,并加以应用和拓展,应该可以解决目前大部分应用的实际编程问题。而随着Java的进一步发展和完善,必将更好地充实其基础类库及打印API,相信用Java实现高级打印功能也将越来越不成为我们这些Java痴迷者头痛的问题。

5 参考资料

  1. 《Java2核心技术 卷Ⅱ:高级特性》 机械工业出版社
  2. Java打印服务参考文档:http://java.sun.com/j2se/1.4/docs/guide/jps/
  3. jdk1.4 API参考文档:http://java.sun.com/j2se/1.4/docs/api/

6 例程源码

PrintSrc.zip包含下列java源代码和Class代码:

  1. PrintTest.java包含了本文所描述的所有打印功能的实现源代码。相应的打印文本功能通过Print Text和PrintText2(jdk1.4实现)按钮调用;打印文件通过Print File按钮调用;打印图形通过Print Frame按钮调用;而Print Preview则进行打印预览。
  2. PrintPreviewDialog.java包含打印预览源代码,你可以通过PrintTest窗体中的Print Preview按钮来调用。

关于作者

文枫,深圳全通数码技术总监。目前专注于J2EE应用与开发。休闲时间喜欢旅游、踢球。你可以通过 wenfb@sina.com 与我联系。


- 作者: chris_jian 2004年12月31日, 星期五 13:08  回复(0) |  引用(0) 加入博采

利用JAVA操作EXCEL文件

利用JAVA操作EXCEL文件


利用JAVA操作EXCEL文件
内容:
JAVA EXCEL API简介
应用示例
总结
参考资料
关于作者
Java 专区中还有:
教学
工具与产品
代码与组件
所有文章
实用技巧

Rubber (tim@trend.com.cn)

2003 年 1 月

使用Windows操作系统的朋友对Excel(电子表格)一定不会陌生,但是要使用Java语言来操纵Excel文件并不是一件容易的事。在Web应用日益盛行的今天,通过Web来操作Excel文件的需求越来越强烈,目前较为流行的操作是在JSP或Servlet 中创建一个CSV (comma separated values)文件,并将这个文件以MIME,text/csv类型返回给浏览器,接着浏览器调用Excel并且显示CSV文件。这样只是说可以访问到Excel文件,但是还不能真正的操纵Excel文件,本文将给大家一个惊喜,向大家介绍一个开放源码项目,Java Excel API,使用它大家就可以方便地操纵Excel文件了。

JAVA EXCEL API简介

Java Excel是一开放源码项目,通过它Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。使用该API非Windows操作系统也可以通过纯Java应用来处理Excel数据表。因为是使用Java编写的,所以我们在Web应用中可以通过JSP、Servlet来调用API实现对Excel数据表的访问。

现在发布的稳定版本是V2.0,提供以下功能:

  • 从Excel 95、97、2000等格式的文件中读取数据;
  • 读取Excel公式(可以读取Excel 97以后的公式);
  • 生成Excel数据表(格式为Excel 97);
  • 支持字体、数字、日期的格式化;
  • 支持单元格的阴影操作,以及颜色操作;
  • 修改已经存在的数据表;

现在还不支持以下功能,但不久就会提供了:

  1. 不能够读取图表信息;
  2. 可以读,但是不能生成公式,任何类型公式最后的计算值都可以读出;

应用示例

1 从Excel文件读取数据表

Java Excel API既可以从本地文件系统的一个文件(.xls),也可以从输入流中读取Excel数据表。读取Excel数据表的第一步是创建Workbook(术语:工作薄),下面的代码片段举例说明了应该如何操作:(完整代码见ExcelReading.java)

import java.io.*;
import jxl.*;
... ... ... ...
try
{
//构建Workbook对象, 只读Workbook对象
	//直接从本地文件创建Workbook
//从输入流创建Workbook
    InputStream is = new FileInputStream(sourcefile);
    jxl.Workbook rwb = Workbook.getWorkbook(is);
}
catch (Exception e)
{
	e.printStackTrace();
}

一旦创建了Workbook,我们就可以通过它来访问Excel Sheet(术语:工作表)。参考下面的代码片段:

//获取第一张Sheet表
Sheet rs = rwb.getSheet(0);

我们既可能通过Sheet的名称来访问它,也可以通过下标来访问它。如果通过下标来访问的话,要注意的一点是下标从0开始,就像数组一样。

一旦得到了Sheet,我们就可以通过它来访问Excel Cell(术语:单元格)。参考下面的代码片段:

//获取第一行,第一列的值
Cell c00 = rs.getCell(0, 0);
String strc00 = c00.getContents();

//获取第一行,第二列的值
Cell c10 = rs.getCell(1, 0);
String strc10 = c10.getContents();

//获取第二行,第二列的值
Cell c11 = rs.getCell(1, 1);
String strc11 = c11.getContents();

System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());

如果仅仅是取得Cell的值,我们可以方便地通过getContents()方法,它可以将任何类型的Cell值都作为一个字符串返回。示例代码中Cell(0, 0)是文本型,Cell(1, 0)是数字型,Cell(1,1)是日期型,通过getContents(),三种类型的返回值都是字符型。

如果有需要知道Cell内容的确切类型,API也提供了一系列的方法。参考下面的代码片段:

String strc00 = null;
double strc10 = 0.00;
Date strc11 = null;

Cell c00 = rs.getCell(0, 0);
Cell c10 = rs.getCell(1, 0);
Cell c11 = rs.getCell(1, 1);

if(c00.getType() == CellType.LABEL)
{
LabelCell labelc00 = (LabelCell)c00;
strc00 = labelc00.getString();
}
if(c10.getType() == CellType.NUMBER)
{
	NmberCell numc10 = (NumberCell)c10;
strc10 = numc10.getValue();
}
if(c11.getType() == CellType.DATE)
{
DateCell datec11 = (DateCell)c11;
strc11 = datec11.getDate();
}

System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());

在得到Cell对象后,通过getType()方法可以获得该单元格的类型,然后与API提供的基本类型相匹配,强制转换成相应的类型,最后调用相应的取值方法getXXX(),就可以得到确定类型的值。API提供了以下基本类型,与Excel的数据格式相对应,如下图所示:

 


 

每种类型的具体意义,请参见Java Excel API Document。

当你完成对Excel电子表格数据的处理后,一定要使用close()方法来关闭先前创建的对象,以释放读取数据表的过程中所占用的内存空间,在读取大量数据时显得尤为重要。参考如下代码片段:

//操作完成时,关闭对象,释放占用的内存空间
rwb.close();

Java Excel API提供了许多访问Excel数据表的方法,在这里我只简要地介绍几个常用的方法,其它的方法请参考附录中的Java Excel API Document。

Workbook类提供的方法

1. int getNumberOfSheets()
获得工作薄(Workbook)中工作表(Sheet)的个数,示例:

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
int sheets = rwb.getNumberOfSheets();

2. Sheet[] getSheets()
返回工作薄(Workbook)中工作表(Sheet)对象数组,示例:

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
Sheet[] sheets = rwb.getSheets();

3. String getVersion()
返回正在使用的API的版本号,好像是没什么太大的作用。

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
String apiVersion = rwb.getVersion();

Sheet接口提供的方法

1) String getName()
获取Sheet的名称,示例:

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
String sheetName = rs.getName();

2) int getColumns()
获取Sheet表中所包含的总列数,示例:

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsColumns = rs.getColumns();

3) Cell[] getColumn(int column)
获取某一列的所有单元格,返回的是单元格对象数组,示例:

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getColumn(0);

4) int getRows()
获取Sheet表中所包含的总行数,示例:

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsRows = rs.getRows();

5) Cell[] getRow(int row)
获取某一行的所有单元格,返回的是单元格对象数组,示例子:

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getRow(0);

6) Cell getCell(int column, int row)
获取指定单元格的对象引用,需要注意的是它的两个参数,第一个是列数,第二个是行数,这与通常的行、列组合有些不同。

jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell cell = rs.getCell(0, 0);

2 生成新的Excel工作薄

下面的代码主要是向大家介绍如何生成简单的Excel工作表,在这里单元格的内容是不带任何修饰的(如:字体,颜色等等),所有的内容都作为字符串写入。(完整代码见ExcelWriting.java)

与读取Excel工作表相似,首先要使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象,这里要注意的是,只能通过API提供的工厂方法来创建Workbook,而不能使用WritableWorkbook的构造函数,因为类WritableWorkbook的构造函数为protected类型。示例代码片段如下:

import java.io.*;
import jxl.*;
import jxl.write.*;
... ... ... ...
try
{
//构建Workbook对象, 只读Workbook对象
//Method 1:创建可写入的Excel工作薄
    jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile));

//Method 2:将WritableWorkbook直接写入到输出流
/*
    OutputStream os = new FileOutputStream(targetfile);
    jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(os);
*/
}
catch (Exception e)
{
	e.printStackTrace();
}

API提供了两种方式来处理可写入的输出流,一种是直接生成本地文件,如果文件名不带全路径的话,缺省的文件会定位在当前目录,如果文件名带有全路径的话,则生成的Excel文件则会定位在相应的目录;另外一种是将Excel对象直接写入到输出流,例如:用户通过浏览器来访问Web服务器,如果HTTP头设置正确的话,浏览器自动调用客户端的Excel应用程序,来显示动态生成的Excel电子表格。

接下来就是要创建工作表,创建工作表的方法与创建工作薄的方法几乎一样,同样是通过工厂模式方法获得相应的对象,该方法需要两个参数,一个是工作表的名称,另一个是工作表在工作薄中的位置,参考下面的代码片段:

//创建Excel工作表
jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);

"这锅也支好了,材料也准备齐全了,可以开始下锅了!",现在要做的只是实例化API所提供的Excel基本数据类型,并将它们添加到工作表中就可以了,参考下面的代码片段:

//1.添加Label对象
jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell");
ws.addCell(labelC);

//添加带有字型Formatting的对象
jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
jxl.write.Label labelCF = new jxl.write.Label(1, 0, "This is a Label Cell", wcfF);
ws.addCell(labelCF);

//添加带有字体颜色Formatting的对象
jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,
UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.RED);
jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
jxl.write.Label labelCFC = new jxl.write.Label(1, 0, "This is a Label Cell", wcfFC);
ws.addCell(labelCF);

//2.添加Number对象
jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926);
ws.addCell(labelN);

//添加带有formatting的Number对象
jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
ws.addCell(labelNF);

//3.添加Boolean对象
jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
ws.addCell(labelB);

//4.添加DateTime对象
jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date());
ws.addCell(labelDT);

//添加带有formatting的DateFormat对象
jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF);
ws.addCell(labelDTF);

这里有两点大家要引起大家的注意。第一点,在构造单元格时,单元格在工作表中的位置就已经确定了。一旦创建后,单元格的位置是不能够变更的,尽管单元格的内容是可以改变的。第二点,单元格的定位是按照下面这样的规律(column, row),而且下标都是从0开始,例如,A1被存储在(0, 0),B1被存储在(1, 0)。

最后,不要忘记关闭打开的Excel工作薄对象,以释放占用的内存,参见下面的代码片段:

//写入Exel工作表
wwb.write();

//关闭Excel工作薄对象
wwb.close();

这可能与读取Excel文件的操作有少少不同,在关闭Excel对象之前,你必须要先调用write()方法,因为先前的操作都是存储在缓存中的,所以要通过该方法将操作的内容保存在文件中。如果你先关闭了Excel对象,那么只能得到一张空的工作薄了。

3 拷贝、更新Excel工作薄

接下来简要介绍一下如何更新一个已经存在的工作薄,主要是下面二步操作,第一步是构造只读的Excel工作薄,第二步是利用已经创建的Excel工作薄创建新的可写入的Excel工作薄,参考下面的代码片段:(完整代码见ExcelModifying.java)

//创建只读的Excel工作薄的对象
jxl.Workbook rw = jxl.Workbook.getWorkbook(new File(sourcefile));

//创建可写入的Excel工作薄对象
jxl.write.WritableWorkbook  wwb = Workbook.createWorkbook(new File(targetfile), rw);
            
//读取第一张工作表
jxl.write.WritableSheet ws = wwb.getSheet(0);

//获得第一个单元格对象
jxl.write.WritableCell wc = ws.getWritableCell(0, 0);
            
//判断单元格的类型, 做出相应的转化
if(wc.getType() == CellType.LABEL)
{
Label l = (Label)wc;
    l.setString("The value has been modified.");
}

//写入Excel对象
wwb.write();

//关闭可写入的Excel对象
wwb.close();

//关闭只读的Excel对象
rw.close();

之所以使用这种方式构建Excel对象,完全是因为效率的原因,因为上面的示例才是API的主要应用。为了提高性能,在读取工作表时,与数据相关的一些输出信息,所有的格式信息,如:字体、颜色等等,是不被处理的,因为我们的目的是获得行数据的值,既使没有了修饰,也不会对行数据的值产生什么影响。唯一的不利之处就是,在内存中会同时保存两个同样的工作表,这样当工作表体积比较大时,会占用相当大的内存,但现在好像内存的大小并不是什么关键因素了。

一旦获得了可写入的工作表对象,我们就可以对单元格对象进行更新的操作了,在这里我们不必调用API提供的add()方法,因为单元格已经于工作表当中,所以我们只需要调用相应的setXXX()方法,就可以完成更新的操作了。

尽单元格原有的格式化修饰是不能去掉的,我们还是可以将新的单元格修饰加上去,以使单元格的内容以不同的形式表现。

新生成的工作表对象是可写入的,我们除了更新原有的单元格外,还可以添加新的单元格到工作表中,这与示例2的操作是完全一样的。

最后,不要忘记调用write()方法,将更新的内容写入到文件中,然后关闭工作薄对象,这里有两个工作薄对象要关闭,一个是只读的,另外一个是可写入的。

小结

本文只是对Java Excel API中常用的方法作了介绍,要想更详尽地了解API,请大家参考API文档,或源代码。Java Excel API是一个开放源码项目,请大家关注它的最新进展,有兴趣的朋友也可以申请加入这个项目,或者是提出宝贵的意见。

参考资料

  1. Java Excel API 文档
  2. http://www.andykhan.com/jexcelapi/
关于作者
就叫我Rubber吧,我是一个Java迷,希望我们能成为朋友,我的Eamil:tim@trend.com.cn,我的联系电话0755-83501377

- 作者: chris_jian 2004年12月2日, 星期四 17:09  回复(0) |  引用(0) 加入博采

Tomcat5配置Mysql JDBC数据库连接池(windows)
Tomcat5配置Mysql JDBC数据库连接池--包括web设置方式配置

Tomcat5配置Mysql JDBC数据库连接池(windows)
 

如果只是对MySql感兴趣可以照抄这篇短文,如果想配置其他数据库类型的连接池,也可以做简单修改参数即可使用。

配置环境: j2sdk1.4.2_04
           Tomcat 5.0.28
           MySQL  5.0
           
           JDBC driver:  mysql-connector-java-3.0.16-ga-bin

1、安装JSDK
   一般安装即可。
   设置 JAVA_HOME和CLASSPATH;
       我的安装目录是c:\j2sdk1.4.2_04;所以设置为
       JAVA_HOME=c:\j2sdk1.4.2_04;
       CLASSPATH=.;c:\j2sdk1.4.2_04/lib/tools.jar
   如果你的安装目录有所不同,请参考设置。   
      
2、安装Tomcat

   参考Tomcat for window 的安装向导,基本直接安装即可,注意:安装时会提示输入管理用户名和密码,这是以后会用到的用户名和密码,切记。
   设置 Tomcat_HOME=d:\Tomcat5028  因为我的安装目录是d:\Tomcat5028

3、安装MySql

   默认安装即可。

4、在数据库中注入测试数据

   登陆数据库:

mysql> GRANT ALL PRIVILEGES ON *.* TO dbuser@localhost
    ->   IDENTIFIED BY '1234' WITH GRANT OPTION;
   
mysql> create database BookDB;
mysql> use BookDB;
mysql> CREATE TABLE books
       (id VARCHAR(8)
        PRIMARY KEY,
        name VARCHAR(24),
        title VARCHAR(96),
        price FLOAT,
        yr INT,
        description VARCHAR(30),
        saleAmount INT);

mysql> INSERT INTO books VALUES('201', '王芳',  'Java编程指南', 33.75, 1999, '让读者轻轻松松掌握Java语言', 1000);

mysql> INSERT INTO books VALUES('202', '张丙', 'Weblogic技术参考', 45.99, 2002, '真的不错耶', 2000);

mysql> INSERT INTO books VALUES('203', '孙艳', 'Oracle数据库教程', 40, 2003, '关于Oracle的最畅销的技术书', 2000);

mysql> INSERT INTO books VALUES('204', '大卫', '从Oak到Java: 语言的革命', 20.75, 1998, '很值得一看', 2000);

mysql> INSERT INTO books VALUES('205', '阿明', 'Apache从入门到精通', 50.75, 2002, '权威的Apache技术资料', 2000);

mysql> INSERT INTO books VALUES('206', '洪军', 'Java与数据算法 ', 54.75, 2002, '权威的Java技术资料', 2000);

 


4、手动配置 server.xml 和 web.xml

   server.xml修改方式:
     通过文件夹导航到%TOMCAT_HOME%\conf,打开server.xml,在 标志和 标志间加入:
  
             debug="5" reloadable="true" crossContext="true">

                  prefix="localhost_DBTest_log." suffix=".txt"
             timestamp="true"/>

                    auth="Container"
               type="javax.sql.DataSource"/>

    
     
        factory
        org.apache.commons.dbcp.BasicDataSourceFactory
     

     
     
         maxActive
         100
     

     
      
         maxIdle
         30
      

     
     
         maxWait
         10000
     

     
     
       username
       DBuser
     
   
     
        password
        1234
     
  
     
     
        driverClassName
        com.mysql.jdbc.Driver
     
   
     
     
        url
        jdbc:mysql://localhost:3306/BookDB?autoReconnect=true
      
  

   web.xml修改方式:
     通过文件夹导航到%TOMCAT_HOME%\webapps\dbtest\WEB-INF/web.xml ,在前加入
      
     MySQL Test App
    
          DB Connection
          jdbc/BOOkDB
          javax.sql.DataSource
          Container
    
    
    注意res-ref-name填写的内容要与在上文提到的JNDI Name名称一致


5、使用Tomcat的Web管理应用配置数据源 (另一种配置途径配置server.xml,最终可以查询文件看到两者的效果一样)

   启动Tomcat服务器,打开浏览器,输入http://localhost:8080/admin/(其中localhost可能是一台机器的IP或是服务器名称),
   进入管理界面的登陆页面,这时候请输入原来安装时要求输入的用户名和密码,登陆到管理界面,


   选择Resources-Data sources进入配置数据源界面,(这里的是在Service (Catalina)下的树目录,除非你配置的datasource
   是为所有的项目都可用,才用下面面向全局的Data sources)
     
   选择Data Source Actions ->选择Create New Data Source,进入配置详细信息界面,内容如下:

   JNDI Name: jdbc/BookDB
   Data Source URL: jdbc:mysql://127.0.0.1/BookDB
   JDBC Driver Class: com.mysql.jdbc.Driver
   User Name: dbuser
   Password: ********
   Max. Active Connections: 4
   Max. Idle Connections: 2
   Max. Wait for Connection: 500
   Validation Query:


要求输入的JNDI Name等信息,其中除了JDBC DriverClass之外,其他的可以根据你的需要填写。
比如Data Source URL的内容可能是:jdbc:mysql:// IP或是名称/DataBaseName,
其中DataBaseName是你的数据库名称,IP是你的数据库的所在的服务器的IP或是名称。
最后点击Save->Commit Change.这样你的数据源的基本资料配置一半了。

配置完成后,重启tomcat,通过文件夹导航到%TOMCAT_HOME%\conf\Catalina\localhost下,找到你的web应用对应的.xml文件,如 DBtest.xml,
你可以看到你配置的信息

到这里,配置工作就基本完成了。


7、其他注意事项

别忘了JDBC驱动程序mysql-connector-java-3.0.16-stable-bin.jar一定要放置到Tomcat的对应目录,你的JDBC驱动可能版比笔者高,不过只要能与所使用的MySql对应就可以了,因为我发现版本太低的JDBC驱动不能支持4.0.*版本的MySQL数据库,建议放置在%TOMCAT_HOME%\common\lib和应用的WEB-INF\lib下。两者有什么不同呢?其实一看就明白了,common\li是所有的应用都可以使用的库文件位置。

重启你的Tomcat服务。

 

8、 测试代码

在应用的目录下建立一个TestDB.jsp文件,代码如下:











 





7、 结束语

图形配置过程及其简单明了,这些配置免除了以往开发人员自己动手写数据源连接池的痛苦,这也是对开发过程的有力支持。
 

- 作者: chris_jian 2004年12月2日, 星期四 14:31  回复(0) |  引用(0) 加入博采

mysql管理和使用小结
mysql管理和使用小结

1,数据库的连接,启动和关闭。

   mysql安装完毕后,无论连接的用户指定的是localhost 主机名还是实际的主机名,从本地主机到服务器的连
接都将是允许的。例如,如果服务器在pit - viper.snake.net 上,则该主机上的客户机能够不使
用口令而连接到该服务器,从而可利用下列之一的语句使用test 数据库:
  
   %mysql -h loaclhost test
   %mysql -h viper.snake.net test
 
  说明:初始的安装是不安全的。因此,作为MySQL 管理员最初的行动之一应该是为root 用户设置口令.
 
  (1).口令修改:
 
    %mysqladmin -u root password 'yourpws'
   
    或者  %mysql -u root mysql
          mysql>update user set Password=PASSWORD('yourpws') where user='root';
   
    在设置口令后,要看看是否需要通过运行下列命令指示服务器重新加载授权的表:
   
         %mysqladmin -uroot status
         %mysqladmin -uroot reload
   
  (2). Mysql的启动和关闭:
 
    应该紧记MySQL 服务器启动过程的两个目标:     
     ■ 要服务器以某些非root 的用户身份启动。通常,除非进程真的需要root 访问权而
        mysql 办不到,否则应限制任何进程的能力。
     ■ 要服务器始终以同一个用户的身份运行。服务器有时作为一个用户运行而有时又作为
        另一个用户运行时会产生矛盾。这将导致文件和目录以不同的所有权在该数据下被创
        建,甚至引起服务器不能访问数据库或表。以同一个用户的身份一致地运行服务器可
        以避免该问题
       
    为了以标准的、非特权的用户身份运行数据库,可按如下步骤执行该过程:
     1)  选择用于运行服务器的账号。mysqld 可以以任何用户身份运行,但是很明显,它只为
       MySQL 活动创建了一个单独的账号。您也可以为MySQL 专门指定一个组。笔者将调用的这
       些用户和组的名字命名为mysqladm 和m y s q l g r p。如果您使用了其他的名字,则在本书中有
       mysqladm 和mysqlgrp 的地方替换它们
          如果您在自己的账号下安装了MySQL 并且系统中没有特定的管理权限,则您可以在自
       己的ID 用户下运行服务器。在这种情况下,应使用您自己的注册名和组名替代mysqladm 和
       mysqlgrp 。
       如果您利用RPM 文件在RedHat Linux 下安装了M y S Q L,则该安装程序将在mysql 名下
       自动创建了一个账号。应使用该名字替换mysqladm 。
     2)  如果必要的话,可用系统常用的账号创建过程( a c c o u n t - c r e a t i o n)来创建服务器账号。
       这需要以root 身份进行操作。
     3) 关闭服务器(如果它在运行)。
     4) 修改数据目录以及任何子目录和文件的所有权,使mysqladm 用户拥有它们。例如,
        如果数据目录是/ u s r / l o c a l / v a r,则可按以下设置mysqladm 用户的所有权:
       
        # cd /usr/local/var 移动到数据目录
        # chown -R mysqladm.mysqlgrp 设置所有目录和文件的所有权
       
     5) 修改数据目录以及任何子目录和文件的许可权,使得只有mysqladm 用户能够访问它
        们。设置该方式以避免其他人员访问是一种好得安全预防措施。如果数据目录是/ u s r / l o c a l / v a r,
        则可通过mysqladm 用户按下列操作设置应具有的一切(您需要以root 身份运行这些命令):
       
        # cd /usr/local/var 移动到数据目录
        # chmod -R go -rwx  使所有一切只对mysqladm 可访问
       
        在设置数据目录及其内容的所有权和方式时,观察符号连接。您需要跟踪符号连接并修
        改所指向的文件或目录的所有权和方式。如果这些连接文件所定位的目录不属于您,则这样
        做可能会引起麻烦,因此您必须是root 用户。
        在完成前述过程后,应确保无论是作为mysqladm 还是作为root 用户注册都始终启动服
        务器。在后者中,要确保指定了--user = mysqladm 的选项,使服务器可以将其用户ID 切换
        到m y s q l a d m(该选项在系统启动过程中也可使用)。  

 


    关闭服务器
    要想手工关闭服务器,可使用m y s q l a d m i n:
         %mysqladmin shutdown;

- 作者: chris_jian 2004年11月23日, 星期二 13:19  回复(0) |  引用(0) 加入博采

JbuilderX+Jboss+tomcat+mysql/MSsql配置
配置一个Jbuilder的J2ee开发环境,具体步骤如下
配置一个Jbuilder的开发环境,具体步骤如下:

1、 安装JbuilderX到D:\JbuilderX目录(基本操作不作详叙)

2、 安装MySQL5.0到C:\mySQL目录(基本操作不作详叙)

3、 安装MS SQL Server2k 到C:\Program Files\下(基本操作不作详叙)

4、 安装JDK.14,下载后直接安装到C:\j2sdk1.4.2_05目录下,然后在系统环境变量中设置:JAVA_HOME= C:\j2sdk1.4.2_05即可。

5、 安装JBOSS3.2.1_tomcat-4.1.24到C:\JBOSS目录下。
    注意:JBOSS的安装很简单,将下载后的文件解开放入C:\JBOSS下即可,然后在系统环境变量中设置:JBOSS_HOME=C:\JBOSS即可。

6、 下载并安装mySql JDBC Driver和MS SQLServer JDBC Driver。下载后的文件史分别是:
     mysql-connector-jdbc-3.0.10-stable.zip和SQLServer2KJDBCsetup.exe。
     安装方法如下:将mysql-connector-jdbc-3.0.10-stable.zip解开,将其中的mysql-connector-jdbc-3.0.10-stable目录下的mysql-connector-java-3.0.10-stable-bin.jar文件持拷入JBOSS安装目录C:\JBOSS下的c:\jboss\server\default\lib目录下。
     执行SQLServer2KJDBCsetup.exe安装到C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC目录下,并将其下lib目录(即C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\lib)目录下的msbase.jar、mssqlserver.jar和msutil.jar三个文件拷入JBOSS安装目录C:\JBOSS下的c:\jboss\server\default\lib目录下。
    同时,最后启动MS SQL Server2k服务器并用查询分析器连接到上面,执行:
C:\Program Files\Microsoft SQL Server 2000 Driver for JDBC\SQLServer JTA目录下的:
instjdbc.sql脚本。

7、 配置JBOSS的数据库连接池,使它能正确连接到MySQL和SQL Server上。方法如下:

进入JBOSS安装目录C:\JBOSS下,进入其下的C:\JBOSS\server\default\deploy目录中,用记事本新建两个文件mssql-ds.xml和mysql-ds.xml,内容如下:


(1)、 mysql-ds.xml

<!-- //127.0.0.1  可修改为你机器的IP-->
<!-- Edit it by Chris Jian  2004/11/20 -->

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>MySql</jndi-name>
<connection-url>jdbc:mysql://127.0.0.1:3306/test</connection-url>
<driver-class>org.gjt.mm.mysql.Driver</driver-class>
<user-name>root</user-name>
<password></password>
</local-tx-datasource>
</datasources>


(2)、 mssql-ds.xml

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>MSSQL</jndi-name> 
<connection-url>jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=test</connection-url>
<driver-class>com.microsoft.jdbc.sqlserver.SQLServerDriver</driver-class>
<user-name>sa</user-name>
<password></password>
</local-tx-datasource>
</datasources>


8、 下载JBossOpenTool-2.6alpha.zip,用于连接JBOSS和JbuildrerX,下载后,将其解开,将解开后产生的JBossOpenTool.jar文件放入JbuilderX安装目录下的:D:\JBuilderX\lib\ext目录中。

9、 配置JBuilderX,过程如下:

(1)、 启动JbulderX,选择Tools->EnterpriseSetup,在弹出的窗口中选DataBase Driver页面,选择Add,在弹出的窗口中选择new,然后再在接着的窗口中Name输入:MySqlJDBC;Location选择User Home;并单击Add将刚才MySql的JDBC驱动文件C:\JBOSS\server\default\lib\mysql-connector-java-3.0.10-stable-bin.jar选入。
(2)、 同1步,选择Tools->EnterpriseSetup,在弹出的窗口中选DataBase Driver页面,选择Add,在弹出的窗口中选择new,然后再在接着的窗口中Name输入:MSSqlJDBC;Location选择User Home;并单击Add将刚才MSSql Server的JDBC驱动文件C:\JBOSS\server\default\lib\msbase.jar、mssqlserver.jar和msutil.jar三个文件选入。单击OK直至结束。重启JBuilderX.
上面配置了两个JDBC Driver,在开发时需要使用哪一个就选择哪一个。
(3)、 启动JbuilderX,选择:Tools->Configure Servers,选中左侧的JBoss 3.X,选中右边的Enable Server,按下表信息填写:
General页:
Home Directiry = C:\jboss 这里是JBoss的安装目录
Main Class = org.jboss.Main
VM Parameters =-ms64m -mx64m -Dprogram.name=run.bat 
Server Parameters = -c default
Working Directory = C:\jboss\bin
Custom页:
JBoss Installation Directory = C:\jboss 这里是JBoss的安装目录
这时是JbuilderX选择的是Tomcat4.0.6。注意,Jbuilder9还安装了一个Tomcat4.1,但是好像最新版本的JbossOpenTool不支持它,所以只有选择Tomcat4.0.6这个Tomcat版本。
 
4、 配置Jbuilder的Lib,在JbuilderX中,选择Tools-> Configure Libraries,在弹出的对话框中,UserHome下面的自动出来了两个目录的JBoss3.xServlet、JBoss3.xCient;如果是红色的,它要求我们配置LIB,
选中它,在右边的Class页面上单击Add,加入JBoss安装目录下C:\JBOSS\Server\default\lib目录下的javax.servlet.jar和jboss-j2ee.jar文件,单击OK。
5、 一切搞定,只需重启JbuilderX,就可以做J2ee开发了。

我们这里的开发环境是:JbuilderX+JBoss3.2.2+MySQL/MS SQL Server2K

- 作者: chris_jian 2004年11月23日, 星期二 09:37  回复(0) |  引用(0) 加入博采

SQL语句性能调整原则
SQL语句性能调整原则    

• SQL语句性能调整原则

一、问题的提出

在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实际应用后,随着数据库中数据的增加,系统的响应速度就成为目前系统需要解决的最主要的问题之一。系统优化中一个很重要的方面就是SQL语句的优化。对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性。

在多数情况下,Oracle使用索引来更快地遍历表,优化器主要根据定义的索引来提高性能。但是,如果在SQL语句的where子句中写的SQL代码不合理,就会造成优化器删去索引而使用全表扫描,一般就这种SQL语句就是所谓的劣质SQL语句。在编写SQL语句时我们应清楚优化器根据何种原则来删除索引,这有助于写出高性能的SQL语句。

二、SQL语句编写注意问题

下面就某些SQL语句的where子句编写中需要注意的问题作详细介绍。在这些where子句中,即使某些列存在索引,但是由于编写了劣质的SQL,系统在运行该SQL语句时也不能使用该索引,而同样使用全表扫描,这就造成了响应速度的极大降低。

1. IS NULL 与 IS NOT NULL

不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的。

2. 联接列

对于有联接的列,即使最后的联接值为一个静态值,优化器是不会使用索引的。我们一起来看一个例子,假定有一个职工表(employee),对于一个职工的姓和名分成两列存放(FIRST_NAME和LAST_NAME),现在要查询一个叫比尔.克林顿(Bill Cliton)的职工。

下面是一个采用联接查询的SQL语句,

select * from employss

where

first_name||‘‘||last_name =‘Beill Cliton‘;

上面这条语句完全可以查询出是否有Bill Cliton这个员工,但是这里需要注意,系统优化器对基于last_name创建的索引没有使用。

当采用下面这种SQL语句的编写,Oracle系统就可以采用基于last_name创建的索引。

Select * from employee

where

first_name =‘Beill‘ and last_name =‘Cliton‘;

遇到下面这种情况又如何处理呢?如果一个变量(name)中存放着Bill Cliton这个员工的姓名,对于这种情况我们又如何避免全程遍历,使用索引呢?可以使用一个函数,将变量name中的姓和名分开就可以了,但是有一点需要注意,这个函数是不能作用在索引列上。下面是SQL查询脚本:

select * from employee

where

first_name = SUBSTR(‘&&name‘,1,INSTR(‘&&name‘,‘ ‘)-1)

and

last_name = SUBSTR(‘&&name‘,INSTR(‘&&name',‘ ‘)+1)

3. 带通配符(%)的like语句

同样以上面的例子来看这种情况。目前的需求是这样的,要求在职工表中查询名字中包含cliton的人。可以采用如下的查询SQL语句:

select * from employee where last_name like ‘%cliton%‘;

这里由于通配符(%)在搜寻词首出现,所以Oracle系统不使用last_name的索引。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用:

select * from employee where last_name like ‘c%‘;

4. Order by语句

ORDER BY语句决定了Oracle如何将返回的查询结果排序。Order by语句对要排序的列没有什么特别的限制,也可以将函数加入列中(象联接或者附加等)。任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。

仔细检查order by语句以找出非索引项或者表达式,它们会降低性能。解决这个问题的办法就是重写order by语句以使用索引,也可以为所使用的列建立另外一个索引,同时应绝对避免在order by子句中使用表达式。

5. NOT

我们在查询时经常在where子句使用一些逻辑表达式,如大于、小于、等于以及不等于等等,也可以使用and(与)、or(或)以及not(非)。NOT可用来对任何逻辑运算符号取反。下面是一个NOT子句的例子:

... where not (status =‘VALID‘)

如果要使用NOT,则应在取反的短语前面加上括号,并在短语前面加上NOT运算符。NOT运算符包含在另外一个逻辑运算符中,这就是不等于(<>)运算符。换句话说,即使不在查询where子句中显式地加入NOT词,NOT仍在运算符中,见下例:

... where status <>‘INVALID‘;

再看下面这个例子:

select * from employee where  salary<>3000;

对这个查询,可以改写为不使用NOT:

select * from employee where  salary<3000 or salary>3000;

虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oracle对salary列使用索引,而第一种查询则不能使用索引。

6.  IN和EXISTS

有时候会将一列和一系列值相比较。最简单的办法就是在where子句中使用子查询。在where子句中可以使用两种格式的子查询。

第一种格式是使用IN操作符:

... where column in(select * from ... where ...);

第二种格式是使用EXIST操作符:

... where exists (select ‘X‘ from ...where ...);

我相信绝大多数人会使用第一种格式,因为它比较容易编写,而实际上第二种格式要远比第一种格式的效率高。在Oracle中可以几乎将所有的IN操作符子查询改写为使用EXISTS的子查询。

第二种格式中,子查询以‘select ‘X‘开始。运用EXISTS子句不管子查询从表中抽取什么数据它只查看where子句。这样优化器就不必遍历整个表而仅根据索引就可完成工作(这里假定在where语句中使用的列存在索引)。相对于IN子句来说,EXISTS使用相连子查询,构造起来要比IN子查询困难一些。

通过使用EXIST,Oracle系统会首先检查主查询,然后运行子查询直到它找到第一个匹配项,这就节省了时间。Oracle系统在执行IN子查询时,首先执行子查询,并将获得的结果列表存放在在一个加了索引的临时表中。在执行子查询之前,系统先将主查询挂起,待子查询执行完毕,存放在临时表中以后再执行主查询。这也就是使用EXISTS比使用IN通常查询速度快的原因。

同时应尽可能使用NOT EXISTS来代替NOT IN,尽管二者都使用了NOT(不能使用索引而降低速度),NOT EXISTS要比NOT IN查询效率更高。

- 作者: chris_jian 2004年11月16日, 星期二 11:15  回复(0) |  引用(0) 加入博采

TOMCAT源码分析(启动框架)
主要是讲解TOMCAT的系统框架, 以及启动流程。

TOMCAT源码分析(启动框架)
2004-05-27    sojan_private       点击: 155
TOMCAT源码分析(启动框架)
本文是我阅读了TOMCAT源码后的一些心得。 主要是讲解TOMCAT的系统框架, 以及启动流程。若有错漏之处,敬请批评指教!
建议:
毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, 是不那么容易掌握TOMCAT的框架的。 所以得实践、实践、再实践。 建议下载一份TOMCAT的源码, 调试通过, 然后单步跟踪其启动过程。 如果有不明白的地方, 再来查阅本文, 看是否能得到帮助。 我相信这样效果以及学习速度都会好很多!

1. Tomcat的整体框架结构
Tomcat的基本框架, 分为4个层次。
Top Level Elements:
Server
Service
Connector
HTTP
AJP
Container
Engine
Host
Context
Component
manager
logger
loader
pipeline
valve
...
站在框架的顶层的是Server和Service
Server: 其实就是BackGroud程序, 在Tomcat里面的Server的用处是启动和监听服务端事件(诸如重启、关闭等命令。 在tomcat的标准配置文件:server.xml里面, 我们可以看到""这里的"SHUTDOWN"就是server在监听服务端事件的时候所使用的命令字)
Service: 在tomcat里面, service是指一类问题的解决方案。 通常我们会默认使用tomcat提供的:Tomcat-Standalone 模式的service。 在这种方式下的service既给我们提供解析jsp和servlet的服务, 同时也提供给我们解析静态文本的服务。

Connector: Tomcat都是在容器里面处理问题的, 而容器又到哪里去取得输入信息呢?
Connector就是专干这个的。 他会把从socket传递过来的数据, 封装成Request, 传递给容器来处理。
通常我们会用到两种Connector,一种叫http connectoer, 用来传递http需求的。 另一种叫AJP, 在我们整合apache与tomcat工作的时候, apache与tomcat之间就是通过这个协议来互动的。 (说到apache与tomcat的整合工作, 通常我们的目的是为了让apache 获取静态资源, 而让tomcat来解析动态的jsp或者servlet。)
Container: 当http connector把需求传递给顶级的container: Engin的时候, 我们的视线就应该移动到Container这个层面来了。
在Container这个层, 我们包含了3种容器: Engin, Host, Context.
Engin: 收到service传递过来的需求, 处理后, 将结果返回给service( service 是通过 connector 这个媒介来和Engin互动的 ).
Host: Engin收到service传递过来的需求后,不会自己处理, 而是交给合适的Host来处理。
Host在这里就是虚拟主机的意思, 通常我们都只会使用一个主机,既"localhost"本地机来处理。
Context: Host接到了从Host传过来的需求后, 也不会自己处理, 而是交给合适的Context来处理。
比如:

前者交给foo这个Context来处理, 后者交给bar这个Context来处理。
很明显吧! context的意思其实就是一个web app的意思。
我们通常都会在server.xml里面做这样的配置

这个context容器,就是用来干我们该干的事儿的地方的。

Compenent: 接下来, 我们继续讲讲component是干什么用的。
我们得先理解一下容器和组件的关系。
需求被传递到了容器里面, 在合适的时候, 会传递给下一个容器处理。
而容器里面又盛装着各种各样的组件, 我们可以理解为提供各种各样的增值服务。
manager: 当一个容器里面装了manager组件后,这个容器就支持session管理了, 事实上在tomcat里面的session管理, 就是靠的在context里面装的manager component.
logger: 当一个容器里面装了logger组件后, 这个容器里所发生的事情, 就被该组件记录下来啦! 我们通常会在logs/ 这个目录下看见 catalina_log.time.txt 以及 localhost.time.txt 和localhost_examples_log.time.txt。 这就是因为我们分别为:engin, host以及context(examples)这三个容器安装了logger组件, 这也是默认安装, 又叫做标配 :)
loader: loader这个组件通常只会给我们的context容器使用, loader是用来启动context以及管理这个context的classloader用的。
pipline: pipeline是这样一个东西, 当一个容器决定了要把从上级传递过来的需求交给子容器的时候, 他就把这个需求放进容器的管道(pipeline)里面去。 而需求傻呼呼得在管道里面流动的时候, 就会被管道里面的各个阀门拦截下来。 比如管道里面放了两个阀门。 第一个阀门叫做"access_allow_vavle", 也就是说需求流过来的时候,它会看这个需求是哪个IP过来的, 如果这个IP已经在黑名单里面了, sure, 杀! 第二个阀门叫做"defaul_access_valve"它会做例行的检查, 如果通过的话,OK, 把需求传递给当前容器的子容器。 就是通过这种方式, 需求就在各个容器里面传递,流动, 最后抵达目的地的了。
valve: 就是上面所说的阀门啦。
Tomcat里面大概就是这么些东西, 我们可以简单地这么理解tomcat的框架,它是一种自上而下, 容器里又包含子容器的这样一种结构。
2. Tomcat的启动流程
这篇文章是讲tomcat怎么启动的,既然我们大体上了解了TOMCAT的框架结构了, 那么我们可以望文生意地就猜到tomcat的启动, 会先启动父容器,然后逐个启动里面的子容器。 启动每一个容器的时候, 都会启动安插在他身上的组件。 当所有的组件启动完毕, 所有的容器启动完毕的时候, tomcat本身也就启动完毕了。
顺理成章地, 我们同样可以猜到, tomcat的启动会分成两大部分, 第一步是装配工作。 第二步是启动工作。
装配工作就是为父容器装上子容器, 为各个容器安插进组件的工作。 这个地方我们会用到digester模式, 至于digester模式什么, 有什么用, 怎么工作的. 请参考
启动工作是在装配工作之后, 一旦装配成功了, 我们就只需要点燃最上面的一根导线, 整个tomcat就会被激活起来。 这就好比我们要开一辆已经装配好了的汽车的时候一样,我们只要把钥匙插进钥匙孔,一拧,汽车的引擎就会发动起来,空调就会开起来, 安全装置就会生效, 如此一来,汽车整个就发动起来了。(这个过程确实和TOMCAT的启动过程不谋而和, 让我们不得不怀疑 TOMCAT的设计者是在GE做JAVA开发的)。
2.1 一些有意思的名称:
Catalina
Tomcat
Bootstrap
Engin
Host
Context
他们的意思很有意思:
Catalina: 远程轰炸机
Tomcat: 熊猫轰炸机 -- 轰炸机的一种(这让我想起了让国人引以为豪的熊猫手机,是不是英文可以叫做tomcat??? , 又让我想起了另一则广告: 波导-手机中的战斗机、波音-客机中的战斗机 )
Bootstap: 引导
Engin: 发动机
Host: 主机,领土
Context: 内容, 目标, 上下文

... 在许多许多年后, 现代人类已经灭绝。 后现代生物发现了这些单词零落零落在一块。 一个自以为聪明的家伙把这些东西翻译出来了:
在地勤人员的引导(bootstrap)下, 一架轰炸架(catalina)腾空跃起, 远看是熊猫轰炸机(tomcat), 近看还是熊猫轰炸机! 凭借着优秀的发动机技术(engin), 这架熊猫轰炸机飞临了敌国的领土上空(host), 对准目标(context)投下了毁天灭地的核弹头,波~ 现代生物就这么隔屁了~

综上所述, 这又不得不让人联想到GE是不是也参与了军事设备的生产呢?
反对美帝国主义! 反对美霸权主义! 和平万岁! 自由万岁!

2.2 历史就是那么惊人的相似! tomcat的启动就是从org.apache.catalina.startup.Bootstrap这个类悍然启动的!
在Bootstrap里做了两件事:
1. 指定了3种类型classloader:
commonLoader: common/classes、common/lib、common/endorsed
catalinaLoader: server/classes、server/lib、commonLoader
sharedLoader: shared/classes、shared/lib、commonLoader
2. 引导Catalina的启动。
用Reflection技术调用org.apache.catalina.startup.Catalina的process方法, 并传递参数过去。

2.3 Catalina.java
Catalina完成了几个重要的任务:
1. 使用Digester技术装配tomcat各个容器与组件。
1.1 装配工作的主要内容是安装各个大件。 比如server下有什么样的servcie。 Host会容纳多少个context。 Context都会使用到哪些组件等等。
1.2 同时呢, 在装配工作这一步, 还完成了mbeans的配置工作。 在这里,我简单地但不十分精确地描述一下mbean是什么,干什么用的。
我们自己生成的对象, 自己管理, 天经地义! 但是如果我们创建了对象了, 想让别人来管, 怎么办呢? 我想至少得告诉别人我们都有什么, 以及通过什么方法可以找到 吧! JMX技术给我们提供了一种手段。 JMX里面主要有3种东西。Mbean, agent, connector.
Mbean: 用来映射我们的对象。也许mbean就是我们创建的对象, 也许不是, 但有了它, 就可以引用到我们的对象了。
Agent: 通过它, 就可以找到mbean了。
Connector: 连接Agent的方式。 可以是http的, 也可以是rmi的,还可以直接通过socket。
发生在tomcat 装配过程中的事情: GlobalResourcesLifecycleListener 类的初始化会被触发:
protected static Registry registry = MBeanUtils.createRegistry(); 会运行
MBeanUtils.createRegistry() 会依据/org/apache/catalina/mbeans/mbeans-descriptors.xml这个配置文件创建 mbeans. Ok, 外界就有了条途径访问tomcat中的各个组件了。(有点像后门儿)
2. 为top level 的server 做初始化工作。 实际上就是做通常会配置给service的两条connector.(http, ajp)
3. 从server这个容器开始启动, 点燃整个tomcat.
4. 为server做一个hook程序, 检测当server shutdown的时候, 关闭tomcat的各个容器用。
5. 监听8005端口, 如果发送"SHUTDOWN"(默认培植下字符串)过来, 关闭8005serverSocket。
2.4 启动各个容器
1. Server
触发Server容器启动前(before_start), 启动中(start), 启动后(after_start)3个事件, 并运行相应的事件处理器。
启动Server的子容器:Servcie.
2. Service
启动Service的子容器:Engin
启动Connector
3. Engin
到了Engin这个层次,以及以下级别的容器, Tomcat就使用了比较一致的启动方式了。
首先, 运行各个容器自己特有一些任务
随后, 触发启动前事件
立即, 设置标签,就表示该容器已经启动
接着, 启动容器中的各个组件: loader, logger, manager等等
再接着,启动mapping组件。(注1)
紧跟着,启动子容器。
接下来,启动该容器的管道(pipline)
然后, 触发启动中事件
最后, 触发启动后事件。

Engin大致会这么做, Host大致也会这么做, Context大致还是会这么做。 那么很显然地, 我们需要在这里使用到代码复用的技术。 tomcat在处理这个问题的时候, 漂亮地使用了抽象类来处理。 ContainerBase. 最后使得这部分完成复杂功能的代码显得干净利落, 干练爽快, 实在是令人觉得叹为观止, 细细品来, 直觉如享佳珍, 另人齿颊留香, 留恋往返啊!

Engin的触发启动前事件里, 会激活绑定在Engin上的唯一一个Listener:EnginConfig。
这个EnginConfig类基本上没有做什么事情, 就是把EnginConfig的调试级别设置为和Engin相当。 另外就是输出几行文本, 表示Engin已经配置完毕, 并没有做什么实质性的工作。
注1: mapping组件的用处是, 当一个需求将要从父容器传递到子容器的时候, 而父容器又有多个子容器的话, 那么应该选择哪个子容器来处理需求呢? 这个由mapping 组件来定夺。

4. Host
同Engin一样, 也是调用ContainerBase里面的start()方法, 不过之前做了些自个儿的任务,就是往Host这个容器的通道(pipline)里面, 安装了一个叫做
"org.apache.catalina.valves.ErrorReportValve"的阀门。
这个阀门的用处是这样的: 需求在被Engin传递给Host后, 会继续传递给Context做具体的处理。 这里需求其实就是作为参数传递的Request, Response。 所以在context把需求处理完后, 通常会改动response。 而这个org.apache.catalina.valves.ErrorReportValve的作用就是检察response是否包含错误, 如果有就做相应的处理。
5. Context
到了这里, 就终于轮到了tomcat启动中真正的重头戏,启动Context了。
StandardContext.start() 这个启动Context容器的方法被StandardHost调用.
5.1 webappResources 该context所指向的具体目录
5.2 安装defaultContex, DefaultContext 就是默认Context。 如果我们在一个Host下面安装了DefaultContext,而且defaultContext里面又安装了一个数据库连接池资源的话。 那么其他所有的在该Host下的Context, 都可以直接使用这个数据库连接池, 而不用格外做配置了。
5.3 指定Loader. 通常用默认的org.apache.catalina.loader.WebappLoader这个类。 Loader就是用来指定这个context会用到哪些类啊, 哪些jar包啊这些什么的。
5.4 指定 Manager. 通常使用默认的org.apache.catalina.session. StandardManager 。 Manager是用来管理session的。
其实session的管理也很好实现。 以一种简单的session管理为例。 当需求传递过来的时候, 在Request对象里面有一个sessionId 属性。 OK, 得到这个sessionId后, 我们就可以把它作为map的key,而value我们可以放置一个HashMap. HashMap里边儿, 再放我们想放的东西。
5.5 postWorkDirectory (). Tomcat下面有一个work目录。 我们把临时文件都扔在那儿去。 这个步骤就是在那里创建一个目录。 一般说来会在%CATALINA_HOME%/work/Standalone\localhost\ 这个地方生成一个目录。
5.6 Binding thread。到了这里, 就应该发生 class Loader 互换了。 之前是看得见tomcat下面所有的class和lib. 接下来需要看得见当前context下的class。 所以要设置contextClassLoader, 同时还要把旧的ClassLoader记录下来,因为以后还要用的。
5.7 启动 Loader. 指定这个Context具体要使用哪些classes, 用到哪些jar文件。 如果reloadable设置成了true, 就会启动一个线程来监视classes的变化, 如果有变化就重新启动Context。
5.8 启动logger
5.9 触发安装在它身上的一个监听器。
lifecycle.fireLifecycleEvent(START_EVENT, null);
作为监听器之一,ContextConfig会被启动. ContextConfig就是用来配置web.xml的。 比如这个Context有多少Servlet, 又有多少Filter, 就是在这里给Context装上去的。
5.9.1 defaultConfig. 每个context都得配置 tomcat/conf/web.xml 这个文件。
5.9.2 applicationConfig 配置自己的 WEB-INF/web.xml 文件
5.9.3 validateSecurityRoles 权限验证。 通常我们在访问/admin 或者/manager的时候,需要用户要么是admin的要么是manager的, 才能访问。 而且我们还可以限制那些资源可以访问, 而哪些不能。 都是在这里实现的。
5.9.4 tldScan: 扫描一下, 需要用到哪些标签(tag lab)
5.10 启动 manager
5.11 postWelcomeFiles() 我们通常会用到的3个启动文件的名称:
index.html、index.htm、index.jsp 就被默认地绑在了这个context上
5.12 listenerStart 配置listener
5.13 filterStart 配置 filter
5.14 启动带有1的Servlet.
顺序是从小到大: 1,2,3... 最后是0
默认情况下, 至少会启动如下3个的Servlet:
org.apache.catalina.servlets.DefaultServlet
处理静态资源的Servlet. 什么图片啊, html啊, css啊, js啊都找他
org.apache.catalina.servlets.InvokerServlet
处理没有做Servlet Mapping的那些Servlet.
org.apache.jasper.servlet.JspServlet
处理JSP文件的.
5.15 标识context已经启动完毕。
走了多少个步骤啊, Context总算是启动完毕喽。
OK! 走到了这里, 每个容器以及组件都启动完毕。 Tomcat终于不辞辛劳地为人民服务了!
3. 参考文献:



4. 后记
这篇文章是讲解tomcat启动框架的,还有篇文章是讲解TOMCAT里面的消息处理流程的细节的。 文章内容已经写好了, 现在正在整理阶段。 相信很快就可以做出来, 大家共同研究共同进步。
这篇文章是独自分析TOMCAT源码所写的, 所以一定有地方是带有个人主观色彩, 难免会有片面之处。若有不当之处敬请批评指教,这样不仅可以使刚开始研究TOMCAT的兄弟们少走弯路, 我也可以学到东西。

- 作者: chris_jian 2004年11月11日, 星期四 14:56  回复(0) |  引用(0) 加入博采

已锁定
此日志的浏览权限已被作者锁定,请同作者联系,发送短消息,如果你的身份符合作者的要求,点击此处可以进行浏览

- 作者: chris_jian 2004年11月11日, 星期四 14:13  回复(0) |  引用(0) 加入博采