﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-zeroone0-文章分类-编程</title><link>http://www.blogjava.net/zeroone0/category/9397.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 15:40:51 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 15:40:51 GMT</pubDate><ttl>60</ttl><item><title>jxl的一些总结</title><link>http://www.blogjava.net/zeroone0/articles/42343.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Fri, 21 Apr 2006 08:12:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/42343.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/42343.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/42343.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/42343.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/42343.html</trackback:ping><description><![CDATA[要往xls文件里面写入数据的时候需要注意的是第一要新建一个xls文件 <br />OutputStream os=new FileOutputStream("c:\\excel2.xls"); <br /><br />再建完这个文件的时候再建立工作文件 <br />jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(os)); <br /><br />如果这个文件已经存在,那么我们可以在这个文件里面加入一个sheet为了和以前的数据进行分开; <br />jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0); <br />在createSheet方法里前面的参数是sheet名，后面是要操作的sheet号 <br /><br />接下来就可以往这个文件里面写入数据了 <br /><br /><br />写入数据的时候注意的格式 <br /><br /><br />（1）添加的字体样式 <br />jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true); <br />WritableFont()方法里参数说明： <br />这个方法算是一个容器，可以放进去好多属性 <br />第一个: TIMES是字体大小，他写的是18 <br />第二个: BOLD是判断是否为斜体,选择true时为斜体 <br />第三个: ARIAL <br />第四个: UnderlineStyle.NO_UNDERLINE 下划线 <br />第五个: jxl.format.Colour.RED 字体颜色是红色的 <br /><br />jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf); <br /><br />jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell"，wcfF); <br />ws.addCell(labelC); <br />在Label()方法里面有三个参数 <br />第一个是代表列数, <br />第二是代表行数， <br />第三个代表要写入的内容 <br />第四个是可选项，是输入这个label里面的样式 <br />然后通过写sheet的方法addCell（）把内容写进sheet里面。 <br /><br />（2）添加带有formatting的Number对象 <br />jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##"); <br /><br /><br />（3）添加Number对象 <br />（3.1）显示number对象数据的格式 <br /><br />jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##"); <br />jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf); <br /><br />jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN); <br />ws.addCell(labelNF); <br />Number()方法参数说明: <br />前两上表示输入的位置 <br />第三个表示输入的内容 <br /><br /><br />（4）添加Boolean对象 <br />jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false); <br />ws.addCell(labelB); <br /><br /><br />（5）添加DateTime对象 <br />jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date()); <br />ws.addCell(labelDT); <br />DateTime()方法的参数说明 <br />前两个表示输入的位置 <br />第三个表示输入的当前时间 <br /><br /><br />（6）添加带有formatting的DateFormat对象 <br />这个显示当前时间的所有信息，包括年月日小时分秒 <br />jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss"); <br />jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df); <br />jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF); <br />ws.addCell(labelDTF); <br /><br />（7）添加带有字体颜色Formatting的对象 <br />jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.RED); <br />jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc); <br /><br />import="jxl.format.* <br />jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL,20,WritableFont.BOLD,false,UnderlineStyle.NO_UNDERLINE,jxl.format.Colour.GREEN); <br /><br />（8）设置单元格样式 <br /><br />jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc); <br />wcfFC.setBackGround(jxl.format.Colour.RED);//设置单元格的颜色为红色 <br />wcfFC = new jxl.write.Label(6,0,"i love china",wcfFC);<br /><br />FROM: <a href="http://forum.javaeye.com/viewtopic.php?t=4157&amp;postdays=0&amp;postorder=asc&amp;start=0">http://forum.javaeye.com/viewtopic.php?t=4157&amp;postdays=0&amp;postorder=asc&amp;start=0</a><img src ="http://www.blogjava.net/zeroone0/aggbug/42343.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-21 16:12 <a href="http://www.blogjava.net/zeroone0/articles/42343.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAVA生成EXCEL文件</title><link>http://www.blogjava.net/zeroone0/articles/42340.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Fri, 21 Apr 2006 07:59:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/42340.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/42340.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/42340.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/42340.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/42340.html</trackback:ping><description><![CDATA[
		<p>
				<a name="N1006F">
						<span class="smalltitle">
								<strong>
										<font face="Arial">1 从Excel文件读取数据表</font>
								</strong>
						</span>
				</a>
		</p>
		<p>
				<strong>
						<font face="Arial">
						</font>
				</strong>
		</p>
		<p>Java Excel API既可以从本地文件系统的一个文件(.xls)，也可以从输入流中读取Excel数据表。读取Excel数据表的第一步是创建Workbook(术语：工作薄)，下面的代码片段举例说明了应该如何操作：(完整代码见ExcelReading.java)</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">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();
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>一旦创建了Workbook，我们就可以通过它来访问Excel Sheet(术语：工作表)。参考下面的代码片段：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">//获取第一张Sheet表
Sheet rs = rwb.getSheet(0);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>我们既可能通过Sheet的名称来访问它，也可以通过下标来访问它。如果通过下标来访问的话，要注意的一点是下标从0开始，就像数组一样。</p>
		<p>一旦得到了Sheet，我们就可以通过它来访问Excel Cell(术语：单元格)。参考下面的代码片段：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">//获取第一行，第一列的值
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());
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果仅仅是取得Cell的值，我们可以方便地通过getContents()方法，它可以将任何类型的Cell值都作为一个字符串返回。示例代码中Cell(0, 0)是文本型，Cell(1, 0)是数字型，Cell(1,1)是日期型，通过getContents()，三种类型的返回值都是字符型。</p>
		<p>如果有需要知道Cell内容的确切类型，API也提供了一系列的方法。参考下面的代码片段：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">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());
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在得到Cell对象后，通过getType()方法可以获得该单元格的类型，然后与API提供的基本类型相匹配，强制转换成相应的类型，最后调用相应的取值方法getXXX()，就可以得到确定类型的值。API提供了以下基本类型，与Excel的数据格式相对应，如下图所示：</p>
		<br />
		<img height="218" alt="" src="http://www-128.ibm.com/developerworks/cn/java/l-javaExcel/image001.jpg" width="365" />
		<br />
		<p>每种类型的具体意义，请参见Java Excel API Document。</p>
		<p>当你完成对Excel电子表格数据的处理后，一定要使用close()方法来关闭先前创建的对象，以释放读取数据表的过程中所占用的内存空间，在读取大量数据时显得尤为重要。参考如下代码片段：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">//操作完成时，关闭对象，释放占用的内存空间
rwb.close();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>Java Excel API提供了许多访问Excel数据表的方法，在这里我只简要地介绍几个常用的方法，其它的方法请参考附录中的Java Excel API Document。</p>
		<p>
				<b>Workbook类提供的方法</b>
		</p>
		<p>1. int getNumberOfSheets() <br />获得工作薄（Workbook）中工作表（Sheet）的个数，示例： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
int sheets = rwb.getNumberOfSheets();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>2. Sheet[] getSheets() <br />返回工作薄（Workbook）中工作表（Sheet）对象数组，示例： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
Sheet[] sheets = rwb.getSheets();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>3. String getVersion() <br />返回正在使用的API的版本号，好像是没什么太大的作用。 </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
String apiVersion = rwb.getVersion();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<b>Sheet接口提供的方法</b>
		</p>
		<p>1) String getName() <br />获取Sheet的名称，示例： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
String sheetName = rs.getName();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>2) int getColumns() <br />获取Sheet表中所包含的总列数，示例： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsColumns = rs.getColumns();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>3) Cell[] getColumn(int column) <br />获取某一列的所有单元格，返回的是单元格对象数组，示例： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getColumn(0);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>4) int getRows() <br />获取Sheet表中所包含的总行数，示例： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsRows = rs.getRows();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>5) Cell[] getRow(int row) <br />获取某一行的所有单元格，返回的是单元格对象数组，示例子： </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getRow(0);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>6) Cell getCell(int column, int row) <br />获取指定单元格的对象引用，需要注意的是它的两个参数，第一个是列数，第二个是行数，这与通常的行、列组合有些不同。 </p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell cell = rs.getCell(0, 0);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>
				<a name="N10114">
						<span class="smalltitle">
								<strong>
										<font face="Arial">2 生成新的Excel工作薄</font>
								</strong>
						</span>
				</a>
		</p>
		<p>
				<strong>
						<font face="Arial">
						</font>
				</strong>
		</p>
		<p>下面的代码主要是向大家介绍如何生成简单的Excel工作表，在这里单元格的内容是不带任何修饰的(如：字体，颜色等等)，所有的内容都作为字符串写入。(完整代码见ExcelWriting.java)</p>
		<p>与读取Excel工作表相似，首先要使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象，这里要注意的是，只能通过API提供的工厂方法来创建Workbook，而不能使用WritableWorkbook的构造函数，因为类WritableWorkbook的构造函数为protected类型。示例代码片段如下：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">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();
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>API提供了两种方式来处理可写入的输出流，一种是直接生成本地文件，如果文件名不带全路径的话，缺省的文件会定位在当前目录，如果文件名带有全路径的话，则生成的Excel文件则会定位在相应的目录；另外一种是将Excel对象直接写入到输出流，例如：用户通过浏览器来访问Web服务器，如果HTTP头设置正确的话，浏览器自动调用客户端的Excel应用程序，来显示动态生成的Excel电子表格。</p>
		<p>接下来就是要创建工作表，创建工作表的方法与创建工作薄的方法几乎一样，同样是通过工厂模式方法获得相应的对象，该方法需要两个参数，一个是工作表的名称，另一个是工作表在工作薄中的位置，参考下面的代码片段：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">//创建Excel工作表
jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>"这锅也支好了，材料也准备齐全了，可以开始下锅了！"，现在要做的只是实例化API所提供的Excel基本数据类型，并将它们添加到工作表中就可以了，参考下面的代码片段：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">//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);
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这里有两点大家要引起大家的注意。第一点，在构造单元格时，单元格在工作表中的位置就已经确定了。一旦创建后，单元格的位置是不能够变更的，尽管单元格的内容是可以改变的。第二点，单元格的定位是按照下面这样的规律(column, row)，而且下标都是从0开始，例如，A1被存储在(0, 0)，B1被存储在(1, 0)。</p>
		<p>最后，不要忘记关闭打开的Excel工作薄对象，以释放占用的内存，参见下面的代码片段：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">//写入Exel工作表
wwb.write();

//关闭Excel工作薄对象
wwb.close();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这可能与读取Excel文件的操作有少少不同，在关闭Excel对象之前，你必须要先调用write()方法，因为先前的操作都是存储在缓存中的，所以要通过该方法将操作的内容保存在文件中。如果你先关闭了Excel对象，那么只能得到一张空的工作薄了。</p>
		<p>
				<a name="N10144">
						<span class="smalltitle">
								<strong>
										<font face="Arial">3 拷贝、更新Excel工作薄</font>
								</strong>
						</span>
				</a>
		</p>
		<p>接下来简要介绍一下如何更新一个已经存在的工作薄，主要是下面二步操作，第一步是构造只读的Excel工作薄，第二步是利用已经创建的Excel工作薄创建新的可写入的Excel工作薄，参考下面的代码片段：(完整代码见ExcelModifying.java)</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">//创建只读的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();
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>之所以使用这种方式构建Excel对象，完全是因为效率的原因，因为上面的示例才是API的主要应用。为了提高性能，在读取工作表时，与数据相关的一些输出信息，所有的格式信息，如：字体、颜色等等，是不被处理的，因为我们的目的是获得行数据的值，既使没有了修饰，也不会对行数据的值产生什么影响。唯一的不利之处就是，在内存中会同时保存两个同样的工作表，这样当工作表体积比较大时，会占用相当大的内存，但现在好像内存的大小并不是什么关键因素了。</p>
		<p>一旦获得了可写入的工作表对象，我们就可以对单元格对象进行更新的操作了，在这里我们不必调用API提供的add()方法，因为单元格已经于工作表当中，所以我们只需要调用相应的setXXX()方法，就可以完成更新的操作了。</p>
		<p>尽单元格原有的格式化修饰是不能去掉的，我们还是可以将新的单元格修饰加上去，以使单元格的内容以不同的形式表现。</p>
		<p>新生成的工作表对象是可写入的，我们除了更新原有的单元格外，还可以添加新的单元格到工作表中，这与示例2的操作是完全一样的。</p>
		<p>最后，不要忘记调用write()方法，将更新的内容写入到文件中，然后关闭工作薄对象，这里有两个工作薄对象要关闭，一个是只读的，另外一个是可写入的。<br /><br />FROM: <a href="http://www-128.ibm.com/developerworks/cn/java/l-javaExcel/index.html">http://www-128.ibm.com/developerworks/cn/java/l-javaExcel/index.html</a></p>
<img src ="http://www.blogjava.net/zeroone0/aggbug/42340.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-21 15:59 <a href="http://www.blogjava.net/zeroone0/articles/42340.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java Annotation</title><link>http://www.blogjava.net/zeroone0/articles/42171.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Thu, 20 Apr 2006 07:30:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/42171.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/42171.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/42171.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/42171.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/42171.html</trackback:ping><description><![CDATA[
		<span style="COLOR: blue">一、Annotation究竟是什么？</span>
		<br />
		<br />Annotation提供了一条与程序元素关联任何信息或者任何元数据（metadata）的途径。从某些方面看，annotation就像修饰符一样被使用，并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。这些信息被存储在annotation的“name=value”结构对中。annotation类型是一种接口，能够通过java反射API的方式提供对其信息的访问。<br /><br />annotation能被用来为某个程序元素（类、方法、成员变量等）关联任何的信息。需要注意的是，这里存在着一个基本的潜规则：annotaion不能影响程序代码的执行，无论增加、删除annotation，代码都始终如一的执行。另外，尽管一些annotation通过java的反射api方法在运行时被访问，而java语言解释器在工作时忽略了这些annotation。正是由于java虚拟机忽略了annotation，导致了annotation类型在代码中是“不起作用”的；只有通过某种配套的工具才会对annotation类型中的信息进行访问和处理。本文中将涵盖标准的annotation和meta-annotation类型，陪伴这些annotation类型的工具是java编译器（当然要以某种特殊的方式处理它们）。<br /><br />由于上述原因，annotation在使用时十分简便。一个本地变量可以被一个以NonNull命名的annotation类型所标注，来作为对这个本地变量不能被赋予null值的断言。而我们可以编写与之配套的一个annotation代码分析工具，使用它来对具有前面变量的代码进行解析，并且尝试验证这个断言。当然这些代码并不必自己编写。在JDK安装后，在JDK/bin目录中可以找到名为“apt”的工具，它提供了处理annotation的框架：它启动后扫描源代码中的annotation，并调用我们定义好的annotation处理器完成我们所要完成的工作（比如验证前面例子中的断言）。说到这里，annotation的强大功能似乎可以替代XDoclet这类的工具了，随着我们的深入，大家会更加坚信这一点。<br />注：详细描述请参看jsr250规范：<br /><a href="http://www.jcp.org/aboutJava/communityprocess/pfd/jsr250/" target="_new">http://www.jcp.org/aboutJava/communityprocess/pfd/jsr250/</a><br /><br /><span style="COLOR: blue">二、Annotation的定义：</span><br /><br />这段文字开始介绍annotation相关技术。在此大家将看到java5.0的标准annotation类型，这种标准类型就是前文中所说的“内建”类型，它们可以直接被javac支持。可喜的是，在java6.0beta版中的javac已经加入了对自定义annotation的支持。<br /><br /><span style="COLOR: blue">1。Annotation的概念和语法：</span><br /><br />首先，关键的概念是理解annotation是与一个程序元素相关联信息或者元数据的标注。它从不影响java程序的执行，但是对例如编译器警告或者像文档生成器等辅助工具产生影响。<br /><br />下面是常用的annotation列表，我们应该注意在annotation和annotation类型之间的不同：<br /><br /><span style="COLOR: green">A.annotation：</span><br />annotation使用了在java5.0所带来的新语法，它的行为十分类似public、final这样的修饰符。每个annotation具有一个名字和成员个数&gt;=0。每个annotation的成员具有被称为name=value对的名字和值（就像javabean一样），name=value装载了annotation的信息。<br /><br /><span style="COLOR: green">B.annotation类型：</span><br />annotation类型定义了annotation的名字、类型、成员默认值。一个annotation类型可以说是一个特殊的java接口，它的成员变量是受限制的，而声明annotation类型时需要使用新语法。当我们通过java反射api访问annotation时，返回值将是一个实现了该annotation类型接口的对象，通过访问这个对象我们能方便的访问到其annotation成员。后面的章节将提到在java5.0的java.lang包里包含的3个标准annotation类型。<br /><br /><span style="COLOR: green">C.annotation成员：</span><br />annotation的成员在annotation类型中以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认语法：允许声明任何annotation成员的默认值：一个annotation可以将name=value对作为没有定义默认值的annotation成员的值，当然也可以使用name=value对来覆盖其它成员默认值。这一点有些近似类的继承特性，父类的构造函数可以作为子类的默认构造函数，但是也可以被子类覆盖。<br /><br /><span style="COLOR: green">D.marker annotation类型：</span><br />一个没有成员定义的annotation类型被称为marker annotation。这种annotation类型仅使用自身的存在与否来为我们提供信息。如后面要说的Override。<br /><br /><span style="COLOR: green">E.meta-annotation：</span><br />meta-annotation也称为元annotation，它是被用来声明annotation类型的annotation。Java5.0提供了一些标准的元-annotation类型。下面介绍的target、retention就是meta-annotation。<br /><br /><span style="COLOR: green">F.target：</span><br />annotation的target是一个被标注的程序元素。target说明了annotation所修饰的对象范围：annotation可被用于packages、types（类、接口、枚举、annotation类型）、类型成员（方法、构造方法、成员变量、枚举值）、方法参数和本地变量（如循环变量、catch参数）。在annotation类型的声明中使用了target可更加明晰其修饰的目标。<br /><br /><span style="COLOR: green">G.retention：</span><br />annotation的retention定义了该annotation被保留的时间长短：某些annotation仅出现在源代码中，而被编译器丢弃；而另一些却被编译在class文件中；编译在class文件中的annotation可能会被虚拟机忽略，而另一些在class被装载时将被读取（请注意并不影响class的执行，因为annotation与class在使用上是被分离的）。使用这个meta-annotation可以对annotation的“生命周期”限制。<br /><br /><span style="COLOR: green">H.metadata：</span><br />由于metadata被广泛使用于各种计算机开发过程中，所以当我们在这里谈论的metadata即元数据通常指被annotation装载的信息或者annotation本身。<br /><br /><span style="COLOR: blue">2。使用标准Annotation：</span><br />java5.0在java.lang包中定义了3种标准的annotation类型：<br /><br /><span style="COLOR: green">A.Override：</span><br />java.lang.Override是一个marker annotation类型，它被用作标注方法。它说明了被标注的方法重载了父类的方法，起到了断言的作用。如果我们使用了这种annotation在一个没有覆盖父类方法的方法时，java编译器将以一个编译错误来警示。<br />这个annotaton常常在我们试图覆盖父类方法而确又写错了方法名时发挥威力。<br /><br />使用方法极其简单：在使用此annotation时只要在被修饰的方法前面加上@Override。<br />下面的代码是一个使用@Override修饰一个企图重载父类的toString方法，而又存在拼写错误的sample：<br /><pre class="overflow" title="pre code"><br />@Override<br />public String toSting() {   // 注意方法名拼写错了<br />    return "[" + super.toString() + "]";<br />}<br /></pre><br /><br /><span style="COLOR: green">B.Deprecated：</span><br />同样Deprecated也是一个marker annotation。当一个类型或者类型成员使用@Deprecated修饰的话，编译器将不鼓励使用这个被标注的程序元素。而且这种修饰具有一定的“延续性”：如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员，虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated，但编译器仍然要报警。<br />值得注意，@Deprecated这个annotation类型和javadoc中的@deprecated这个tag是有区别的：前者是java编译器识别的，而后者是被javadoc工具所识别用来生成文档（包含程序成员为什么已经过时、它应当如何被禁止或者替代的描述）。<br />在java5.0，java编译器仍然象其从前版本那样寻找@deprecated这个javadoc tag，并使用它们产生警告信息。但是这种状况将在后续版本中改变，我们应在现在就开始使用@Deprecated来修饰过时的方法而不是@deprecated javadoc tag。<br /><pre class="overflow" title="pre code"><br />下面是一段使用@Deprecated的代码：<br />/**<br /> * 这里是javadoc的@deprecated声明.<br /> * @deprecated No one has players for this format any more.  Use VHS instead.<br /> */<br />@Deprecated public class Betamax { ... }<br /></pre><br /><br /><span style="COLOR: green">C.SuppressWarnings：</span><br />@SuppressWarnings被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。在java5.0，sun提供的javac编译器为我们提供了-Xlint选项来使编译器对合法的程序代码提出警告，此种警告从某种程度上代表了程序错误。例如当我们使用一个generic collection类而又没有提供它的类型时，编译器将提示出"unchecked warning"的警告。<br /><br />通常当这种情况发生时，我们就需要查找引起警告的代码。如果它真的表示错误，我们就需要纠正它。例如如果警告信息表明我们代码中的switch语句没有覆盖所有可能的case，那么我们就应增加一个默认的case来避免这种警告。<br />相仿，有时我们无法避免这种警告，例如，我们使用必须和非generic的旧代码交互的generic collection类时，我们不能避免这个unchecked warning。此时@SuppressWarning就要派上用场了，在调用的方法前增加@SuppressWarnings修饰，告诉编译器停止对此方法的警告。<br />SuppressWarning不是一个marker annotation。它有一个类型为String[]的成员，这个成员的值为被禁止的警告名。对于javac编译器来讲，被-Xlint选项有效的警告名也同样对@SuppressWarings有效，同时编译器忽略掉无法识别的警告名。<br /><br />annotation语法允许在annotation名后跟括号，括号中是使用逗号分割的name=value对用于为annotation的成员赋值：<br /><pre class="overflow" title="pre code"><br />@SuppressWarnings(value={"unchecked","fallthrough"})<br />public void lintTrap() { /* sloppy method body omitted */ }<br /></pre><br /><br />在这个例子中SuppressWarnings annotation类型只定义了一个单一的成员，所以只有一个简单的value={...}作为name=value对。又由于成员值是一个数组，故使用大括号来声明数组值。<br /><br />注意：我们可以在下面的情况中缩写annotation：当annotation只有单一成员，并成员命名为"value="。这时可以省去"value="。比如将上面的SuppressWarnings annotation进行缩写：<br /><pre class="overflow" title="pre code"><br />@SuppressWarnings({"unchecked","fallthrough"})<br /></pre><br />如果SuppressWarnings所声明的被禁止警告个数为一个时，可以省去大括号：<br /><pre class="overflow" title="pre code"><br />@SuppressWarnings("unchecked")<br /></pre><br /><br /><span style="COLOR: blue">3。Annotation语法：</span><br /><br />在上一个章节中，我们看到书写marker annotation和单一成员annotation的语法。下面本人来介绍一下完整的语法：<br /><br />annotation由“@+annotation类型名称+(..逗号分割的name-value对...)”组成。其中成员可以按照任何的顺序。如果annotation类型定义了某个成员的默认值，则这个成员可以被省略。成员值必须为编译时常量、内嵌的annotation或者数组。<br /><br />下面我们将定义一个annotation类型名为Reviews，它有一个由@Review annotation数组构成的成员。这个@Review annotation类型有三个成员："reviewer"是一个字符串，"comment" 是一个具有默认值的可选的字符串，"grade"是一个Review.Grade枚举类型值。<br /><pre class="overflow" title="pre code"><br />@Reviews({  // Single-value annotation, so "value=" is omitted here<br />    @Review(grade=Review.Grade.EXCELLENT,<br />            reviewer="df"),<br />    @Review(grade=Review.Grade.UNSATISFACTORY,<br />            reviewer="eg",<br />            comment="This method needs an @Override annotation")<br />})<br /></pre><br />annotation语法的另一个重要规则是没有程序成员可以有多于一个的同一annotation实例。例如在一个类中简单的放置多个@Review annotation。这也是在上面代码中定义@Reviews annotation类型数组的原因。<br /><br /><span style="COLOR: blue">4。Annotation成员类型和值：</span><br /><br />annotation成员必须是非空的编译时常量表达式。可用的成员类型为：primitive类型、, String, Class, enumerated类型, annotation类型, 和前面类型的数组。<br /><br />下面我们定义了一个名为UncheckedExceptions 的annotation类型，它的成员是一个扩展了RuntimeException类的类数组。<br /><pre class="overflow" title="pre code"><br />@UncheckedExceptions({<br />    IllegalArgumentException.class, StringIndexOutOfBoundsException.class<br />})<br /></pre><br /><br /><span style="COLOR: blue">5。Annotation的目标：</span><br /><br />annotation通常被放在类型定义和成员定义的前面。然而它也出现在package、方法参数、本地变量的前面。下面，我们来讨论一下这些不大常用的写法：<br /><br />package annotation出现在package声明的前面。<br />下面的例子package-info.java中不包含任何的公共类型定义，却包含一个可选的javadoc注释。<br /><pre class="overflow" title="pre code"><br />/**<br /> * This package holds my custom annotation types.<br /> */<br />@com.davidflanagan.annotations.Author("David Flanagan")<br />package com.davidflanagan.annotations;<br /></pre><br />当package-info.java文件被编译时，它将产生名为包含annotation（特殊的接口）声明的package-info.class的类。这个接口没有成员，它的名字package-info不是一个合法的java标识，所以它不能用在java源代码中。这个接口的存在只是简单的被看作一个为package annotation准备的占位符。<br /><br />用于修饰方法参数、catch参数、本地变量的annotation只是简单的出现在这些程序成员的修饰符位置。java类文件格式没有为本地变量或者catch参数存储annotation作准备，所以这些annotation总是保留在源代码级别（source retention）；方法参数annotation能够保存在类文件中，也可以在保留到运行时。<br /><br />最后，请注意，枚举类型定义中不允许任何的修饰符修饰其枚举值。<br /><br /><span style="COLOR: blue">6。Annotation和默认值：</span><br />在Annotation中，没有默认值的成员必须有一个成员值。而如何理解默认值是如何被处理就是一个很重要的细节：annotation类型所定义的成员默认值被存储在class文件中，不被编译到annotation里面。如果我们修改一个annotation类型使其成员的默认值发生了改变，这个改变对于所有此类型的annotation中没有明确提供成员值的成员产生影响（即修改了该成员的成员值）。即使在annotation类型使其成员的默认值被改变后annotation从没被重新编译过，该类型的annotation(改变前已经被编译的)也受到影响。<br /><br /><span style="COLOR: blue">三、Annotation工作原理：</span><br /><br /><span style="COLOR: blue">Annotation与反射</span><br />在java5.0中Java.lang.reflect提供的反射API被扩充了读取运行时annotation的能力。让我们回顾一下前面所讲的：一个annotation类型被定义为runtime retention后，它才是在运行时可见，当class文件被装载时被保存在class文件中的annotation才会被虚拟机读取。那么reflect是如何帮助我们访问class中的annotation呢？<br /><br />下文将在java.lang.reflect用于annotation的新特性，其中java.lang.reflect.AnnotatedElement是重要的接口，它代表了提供查询annotation能力的程序成员。这个接口被java.lang.Package、java.lang.Class实现，并间接地被Method类、Constructor类、java.lang.reflect的Field类实现。而annotation中的方法参数可以通过Method类、Constructor类的getParameterAnnotations()方法获得。<br /><br />下面的代码使用了AnnotatedElement类的isAnnotationPresent()方法判断某个方法是否具有@Unstable annotation，从而断言此方法是否稳定：<br /><pre class="overflow" title="pre code"><br />import java.lang.reflect.*;<br /><br />Class c = WhizzBangClass.class;                           <br />Method m = c.getMethod("whizzy", int.class, int.class);  <br />boolean unstable = m.isAnnotationPresent(Unstable.class);<br /></pre><br />isAnnotationPresent()方法对于检查marker annotation是十分有用的，因为marker annotation没有成员变量，所以我们只要知道class的方法是否使用了annotation修饰就可以了。而当处理具有成员的annotation时，我们通过使用getAnnotation()方法来获得annotation的成员信息（成员名称、成员值）。这里我们看到了一套优美的java annotation系统：如果annotation存在，那么实现了相应的annotation类型接口的对象将被getAnnotation()方法返回，接着调用定义在annotation类型中的成员方法可以方便地获得任何成员值。<br /><br />回想一下，前面介绍的@Reviews annotation，如果这个annotation类型被声明为runtime retention的话，我们通过下面的代码来访问@Reviews annotation的成员值：<br /><pre class="overflow" title="pre code"><br />AnnotatedElement target = WhizzBangClass.class; //获得被查询的AnnotatedElement<br />// 查询AnnotatedElement的@Reviews annotation信息<br />Reviews annotation = target.getAnnotation(Reviews.class);<br />// 因为@Reviews annotation类型的成员为@Review annotation类型的数组，<br />// 所以下面声明了Review[] reviews保存@Reviews annotation类型的value成员值。<br />Review[] reviews = annotation.value();<br />// 查询每个@Review annotation的成员信息<br />for(Review r : reviews) {<br />    Review.Grade grade = r.grade();<br />    String reviewer = r.reviewer();<br />    String comment = r.comment();<br />    System.out.printf("%s assigned a grade of %s and comment '%s'%n",<br />                      reviewer, grade, comment);<br />}<br /></pre><br /><br /><span style="COLOR: blue">四、如何自定义Annotation？</span><br /><br /><span style="COLOR: blue">1．详解annotation与接口的异同：</span><br />因为annotation类型是一个非凡的接口，所以两者之间存在着某些差异：<br /><br /><span style="COLOR: green">A.Annotation类型使用关键字@interface而不是interface。</span><br />这个关键字声明隐含了一个信息：它是继承了java.lang.annotation.Annotation接口，并非声明了一个interface。<br /><br /><span style="COLOR: green">B.Annotation类型、方法定义是独特的、受限制的。</span><br />Annotation类型的方法必须声明为无参数、无异常抛出的。这些方法定义了annotation的成员：方法名成为了成员名，而方法返回值成为了成员的类型。而方法返回值类型必须为primitive类型、Class类型、枚举类型、annotation类型或者由前面类型之一作为元素的一维数组。方法的后面可以使用default和一个默认数值来声明成员的默认值，null不能作为成员默认值，这与我们在非annotation类型中定义方法有很大不同。<br />Annotation类型和它的方法不能使用annotation类型的参数、成员不能是generic。只有返回值类型是Class的方法可以在annotation类型中使用generic，因为此方法能够用类转换将各种类型转换为Class。<br /><br /><span style="COLOR: green">C.Annotation类型又与接口有着近似之处。</span><br />它们可以定义常量、静态成员类型（比如枚举类型定义）。Annotation类型也可以如接口一般被实现或者继承。<br /><br /><span style="COLOR: blue">2．实例：</span><br />下面，我们将看到如何定义annotation类型的example。它展示了annotation类型声明以及@interface与interface之间的不同：<br /><pre class="overflow" title="pre code"><br />package com.davidflanagan.annotations;<br />import java.lang.annotation.*;<br /><br />/**<br /> * 使用annotation来描述那些被标注的成员是不稳定的，需要更改<br />*/<br />@Retention(RetentionPolicy.RUNTIME)<br />public @interface Unstable {}<br /></pre><br />下面的另一个example只定义了一个成员。并通过将这个成员命名为value，使我们可以方便的使用这种annotation的快捷声明方式：<br /><pre class="overflow" title="pre code"><br />/**<br /> * 使用Author这个annotation定义在程序中指出代码的作者<br /> */<br />public @interface Author {<br />    /** 返回作者名 */<br />    String value();<br />}<br /></pre><br />以下的example更加复杂。Reviews annotation类型只有一个成员，但是这个成员的类型是复杂的：由Review annotation组成的数组。Review annotation类型有3个成员：枚举类型成员grade、表示Review名称的字符串类型成员Reviewer、具有默认值的字符串类型成员Comment。<br /><pre class="overflow" title="pre code"><br />import java.lang.annotation.*;<br />        <br />/**<br /> * Reviews annotation类型只有一个成员，<br /> * 但是这个成员的类型是复杂的：由Review annotation组成的数组<br /> */<br />@Retention(RetentionPolicy.RUNTIME)<br />public @interface Reviews {<br />    Review[] value();<br />}<br /><br />/**<br />* Review annotation类型有3个成员： <br />* 枚举类型成员grade、<br />  * 表示Review名称的字符串类型成员Reviewer、<br />  * 具有默认值的字符串类型成员Comment。<br /> */<br />public @interface Review {<br />    // 内嵌的枚举类型<br />    public static enum Grade { EXCELLENT, SATISFACTORY, UNSATISFACTORY };<br /><br />    // 下面的方法定义了annotation的成员<br />    Grade grade();                <br />    String reviewer();          <br />    String comment() default "";  <br />}<br /></pre><br />最后，我们来定义一个annotation方法用于罗列出类运行中所有的unchecked异常（上文已经提到这种情况不一定是错误）。这个annotation类型将一个数组作为了唯一的成员。数组中的每个元素都是异常类。为了加强对未检查的异常（此类异常都是在运行时抛出）进行报告，我们可以在代码中对异常的类型进行限制：<br /><pre class="overflow" title="pre code"><br />public @interface UncheckedExceptions {<br />    Class&lt;? extends RuntimeException&gt;[] value();<br />}<br /></pre><br /><br /><span style="COLOR: blue">五、Meta-Annotation</span><br /><br />Annotation类型可以被它们自己所标注。Java5.0定义了4个标准的meta-annotation类型，它们被用来提供对其它annotation类型作说明。这些类型和它们所支持的类在java.lang.annotation包中可以找到。如果需要更详细的信息可以参考jdk5.0手册。<br /><br /><span style="COLOR: blue">1．再谈Target</span><br />作为meta-annotation类型的Target,它描述了annotation所修饰的程序成员的类型。当一个annotation类型没有Target时，它将被作为普通的annotation看待。当将它修饰一个特定的程序成员时，它将发挥其应用的作用，例如：Override用于修饰方法时，增加了@Target这个meta-annotation就使编译器对annotation作检查，从而去掉修饰错误类型的Override。<br /><br />Target meta-annotation类型有唯一的value作为成员。这个成员的类型是java.lang.annotation.ElementType[]类型的，ElementType类型是可以被标注的程序成员的枚举类型。<br /><br /><span style="COLOR: blue">2．Retention的用法</span><br />我们在文章的开头曾经提到过Retention，但是没有详细讲解。Retention描述了annotation是否被编译器丢弃或者保留在class文件；如果保留在class文件中，是否在class文件被装载时被虚拟机读取。默认情况下，annotation被保存在class文件中，但在运行时并不能被反射访问。Retention具有三个取值：source、class、runtime，这些取值来自java.lang.annotation.RetentionPolicy的枚举类型值。<br /><br />Retention meta-annotation类型有唯一的value作为成员，它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。<br /><br /><span style="COLOR: blue">3．Documented</span><br />Documented是一个meta-annotation类型，用于描述其它类型的annotation应该被作为被标注的程序成员的公共API，因此可以被例如javadoc此类的工具文档化。<br /><br />Documented是一个marker annotation，没有成员。<br /><br /><span style="COLOR: blue">4．Inherited</span><br />@Inherited meta-annotation也是一个marker annotation，它阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class，则这个annotation将被用于该class的子类。<br /><br />注意：@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation，方法并不从它所重载的方法继承annotation。<br /><br />值得思考的是，当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME，则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时，反射代码检查将展开工作：检查class和其父类，直到发现指定的annotation类型被发现，或者到达类继承结构的顶层。<br /><br /><br /><center>作者 cleverpig(http://blog.matrix.org.cn/page/cleverpig)</center><img src ="http://www.blogjava.net/zeroone0/aggbug/42171.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-20 15:30 <a href="http://www.blogjava.net/zeroone0/articles/42171.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>log4j日志管理</title><link>http://www.blogjava.net/zeroone0/articles/39694.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Thu, 06 Apr 2006 15:26:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/39694.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/39694.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/39694.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/39694.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/39694.html</trackback:ping><description><![CDATA[Index <br /><br />Log4j的类图 <br />Logger：日志写出器 <br />Logger的输出方法 <br />Logger的命名规则 <br />Log level <br />示例代码 <br />关于logger的两点说明 <br />Appender：日志目的地 <br />ConsoleAppender <br />FileAppender <br />RollingFileAppender <br />Layout：日志格式化器 <br />PatternLayout <br />patterns in PatternLayout <br />Configuration：配置 <br />默认的log4j初始化过程 <br />BasicConfigurator.configure() <br />xml格式的log4j配置文件概述 <br />在xml文件中配置appender和layout <br />我自己的一个使用xml文件配置log4j环境的很简单的例子 <br />Log4j的编码习惯 <br />参考资料 <br /><br />Log4j的类图 <br /><br />Logger - 日志写出器，供程序员输出日志信息 <br />Appender - 日志目的地，把格式化好的日志信息输出到指定的地方去 <br />ConsoleAppender - 目的地为控制台的Appender <br />FileAppender - 目的地为文件的Appender <br />RollingFileAppender - 目的地为大小受限的文件的Appender <br />Layout - 日志格式化器，用来把程序员的logging request格式化成字符串 <br />PatternLayout - 用指定的pattern格式化logging request的Layout <br /><br />Logger：日志写出器 <br />Logger对象是用来取代System.out或者System.err的日志写出器，用来供程序员输出日志信息。 <br /><br />Logger的输出方法 <br />Logger类对象提供一系列方法供程序员输出日志信息。 <br /><br />------ Log4j APIs : class Logger ------ <br /><br />// Printing methods : <br /><br />public void debug(Object msg); <br />public void debug(Object msg, Throwable t); <br /><br />public void info(Object msg); <br />public void info(Object msg, Throwable t); <br /><br />public void warn(Object msg); <br />public void warn(Object msg, Throwable t); <br /><br />public void error(Object msg); <br />public void error(Object msg, Throwable t); <br /><br />public void fatal(Object msg); <br />public void fatal(Object msg, Throwable t); <br /><br />// Generic printing method : <br /><br />public void log(Level l, Object msg); <br /><br />Logger的命名规则 <br />Logger由一个String类的名字识别，logger的名字是大小写敏感的，且名字之间具有继承的关系，子名有父名作为前缀，用点号.分隔。如：x.y是x.y.z的父亲。 <br /><br />根logger (root logger)是所有logger的祖先，它具有如下属性：1) 它总是存在的；2) 它不可以通过名字获得。 <br />通过调用public static Logger Logger.getRootLogger()获得root logger；通过调用public static Logger Logger.getLogger(String name)或者public static Logger Logger.getLogger(Class clazz)获得（或者创建）一个named logger。后者相当于调用Logger.getLogger(clazz.getName())。 <br /><br />在某对象中，用该对象所属的类为参数，调用Logger.getLogger(Class clazz)以获得logger被认为是目前所知的最理智的命名logger的方法。 <br /><br />Log level <br />每个logger都被分配了一个日志级别 (log level)，用来控制日志信息的输出。未被分配level的logger将继承它最近的父logger的level。 <br /><br />每条输出到logger的日志请求(logging request)也都有一个level，如果该request的level大于等于该logger的level，则该request将被处理（称为enabled）；否则该request将被忽略。故可得知： <br /><br />logger的level越低，表示该logger越详细 <br />logging request的level越高，表示该logging request越优先输出 <br /><br />Level类中预定义了五个level，它们的大小关系如下： <br /><br />Level.ALL &lt; Level.DEBUG &lt; Level.INFO &lt; Level.WARN &lt; Level.ERROR &lt; Level.FATAL &lt; Level.OFF <br /><br />示例代码 <br />以下代码将用自己所属的类为参数，创建一个logger，启用默认配置，设置其level并向其输出若干logging request。 <br /><br />import org.apache.log4j.Logger; <br />import org.apache.log4j.BasicConfigurator; <br />import org.apache.log4j.Level; <br /><br />public class Log4jTest { <br />public static void main(String argv[]) { <br /><br />// Create a logger by the name of class Log4jTest. <br /><br />Logger logger = Logger.getLogger(Log4jTest.class); <br /><br />// Use the default configuration. <br /><br />BasicConfigurator.configure(); <br /><br />// Set the logger level to Level.INFO <br /><br />logger.setLevel(Level.INFO); <br /><br />// This request will be disabled since Level.DEBUG &lt; Level.INFO. <br /><br />logger.debug("This is debug."); <br /><br />// These requests will be enabled. <br /><br />logger.info("This is an info."); <br />logger.warn("This is a warning."); <br />logger.error("This is an error."); <br />logger.fatal("This is a fatal error."); <br /><br />return; <br />} <br />} <br /><br />关于logger的两点说明 <br /><br />用同名参数调用Logger.getLogger(String name)将返回同一个logger的引用。故可以在一个地方配置logger，在另外一个地方获得配置好的logger，而无须相互间传递logger的引用。 <br />logger的创建可以按照任意的顺序，即，父logger可以后于子logger被创建。log4j将自动维护logger的继承树。 <br />Appender：日志目的地 <br />每个logger都可以拥有一个或者多个appender，每个appender表示一个日志的输出目的地，比如console或者某个文件。可以使用Logger.addAppender(Appender app)为logger增加一个appender；可以使用Logger.removeAppender(Appender app)为logger移除一个appender。 <br /><br />默认情况下，logger的additive标志被设置为true，表示子logger将继承父logger的所有appenders。该选项可以被重新设置，表示子logger将不再继承父logger的appenders。 <br /><br />root logger拥有目标为system.out的consoleAppender，故默认情况下，所有的logger都将继承该appender。 <br /><br />------ Log4j APIs : class Logger ------ <br /><br />// 为logger对象增加或者移除一个Appender对象 :. <br /><br />public void appAppender(Appender app); <br />public void removeAppender(Appender app); <br /><br />// 获得和设置additive标志：是否继承父logger的appenders :. <br />// 注意：在设置additive标志为false时，必须保证已经为该logger设置了新的appender， :. <br />// 否则log4j将报错：log4j:WARN No appenders could be found for logger (x.y.z). :. <br /><br />public boolean getAdditivity(); <br />public void setAdditivity(boolean additive); <br /><br />ConsoleAppender <br />可以使用ConsoleAppender对象把日志输出到控制台。每个ConsoleAppender都有一个target，表示它的输出目的地。它可以是System.out，标准输出设备（缓冲显示屏）；或者是System.err，标准错误设备（不缓冲显示屏）。ConsoleAppender的使用方法参考如下API :. <br /><br />------ Log4j APIs : class ConsoleAppender extends WriterAppender ------ <br /><br />// 构造方法，使用一个Layout对象构造一个ConsoleAppender对象 :. <br />// 默认情况下，ConsoleAppender的target是System.out :. <br /><br />public ConsoleAppender(Layout layout); <br /><br />// 构造方法，使用一个Layout对象和一个target字符串构造ConsoleAppender对象 :. <br />// target的可能取值为ConsoleAppender.SYSTEM_OUT和ConsoleAppender.SYSTEM_ERR :. <br /><br />public ConsoleAppender(Layout layout, String target); <br /><br />FileAppender <br />可以使用FileAppender对象把日志输出到一个指定的日志文件中去。使用方法可以参考如下的API :. <br /><br />------ Log4j APIs : class FileAppender extends WriterAppender ------ <br /><br />// 构造方法，使用一个Layout对象和日志文件名构造一个FileAppender对象 :. <br /><br />public FileAppender(Layout layout, String filename) <br />throws IOException; <br />public FileAppender(Layout layout, String filename, boolean append) <br />throws IOException; <br /><br />RollingFileAppender <br />可以使用FileAppender的子类RollingFileAppender对象，把日志输出到一个指定的日志文件中。不同的是该日志文件的大小受到限制，当日志内容超出最大的尺寸时，该文件将向上滚动（最老的日志被擦除）。还可以在该类对象中指定为日志文件做多少个备份。具体使用方法参考如下API :. <br /><br />------ Log4j APIs : class RollingFileAppender extends FileAppender ------ <br /><br />// 构造方法，使用一个Layout对象和日志文件名构造一个RollingFileAppender对象 :. <br /><br />public RollingFileAppender(Layout layout, String filename) <br />throws IOException; <br />public RollingFileAppender(Layout layout, String filename, boolean append) <br />throws IOException; <br /><br />// 获得和设置日志备份文件的个数 :. <br /><br />public int getMaxBackupIndex(); <br />public void setMaxBackupIndex(int index); <br /><br />// 获得和设置滚动日志文件的最大尺寸 :. <br /><br />public long getMaximumFileSize(); <br />public void setMaximumFileSize(long size); <br /><br />Layout：日志格式化器 <br />每个appender都和一个layout相联系；layout的任务是格式化用户的logging request，appender的任务是把layout格式化好的输出内容送往指定的目的地。 <br /><br />PatternLayout <br />PatternLayout是Layout的一个子类，用来使用类似C语言的printf函数中使用的格式控制字符串来控制日志的输出格式。使用方法参考如下API :. <br /><br />------ Log4j APIs : class PatternLayout extends Layout ------ <br /><br />// 无参数构造方法，使用DEFAULT_CONVERSION_PATTERN构造一个PatternLayout :. <br />// 注意：DEFAULT_CONVERSION_PATTERN为"%m%n"，只打印消息信息 :. <br /><br />public PatternLayout(); <br /><br />// 构造方法，使用自定义的pattern构造一个PatternLayout :. <br /><br />public PatternLayout(String pattern); <br /><br />// 获得和设置PatternLayout对象的日志pattern :. <br /><br />public String getConversionPattern(); <br />public void setConversionPattern(String pattern); <br /><br />patterns in PatternLayout <br /><br />Configuration：配置 <br />对log4j环境的配置就是对root logger的配置，包括把root logger设置为哪个级别(level)；为它增加哪些appender，等等。这些可以通过设置系统属性的方法来隐式地完成，也可以在程序里调用XXXConfigurator.configure()方法来显式地完成。 <br /><br />默认的log4j初始化过程 <br />Logger类的静态初始化块(static initialization block)中对log4j的环境做默认的初始化。注意：如果程序员已经通过设置系统属性的方法来配置了log4j环境，则不需要再显式地调用XXXConfigurator.configure()方法来配置log4j环境了。 <br /><br />Logger的静态初始化块在完成初始化过程时将检查一系列log4j定义的系统属性。它所做的事情如下： <br /><br />检查系统属性log4j.defaultInitOverride，如果该属性被设置为false，则执行初始化；否则（只要不是false，无论是什么值，甚至没有值，都是否则），跳过初始化。 <br />把系统属性log4j.configuration的值赋给变量resource。如果该系统变量没有被定义，则把resource赋值为"log4j.properties"。注意：在apache的log4j文档中建议使用定义log4j.configuration系统属性的方法来设置默认的初始化文件是一个好方法。 <br />试图把resource变量转化成为一个URL对象url。如果一般的转化方法行不通，就调用org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)方法来完成转化。 <br />如果url以".html"结尾，则调用方法DOMConfigurator.configure(url)来完成初始化；否则，则调用方法PropertyConfigurator.configure(url)来完成初始化。如果url指定的资源不能被获得，则跳出初始化过程。 <br /><br />BasicConfigurator.configure() <br />BasicConfigurator.configure()方法使用最简的方法配置log4j环境。注：所谓配置log4j环境，就是指配置root logger，因为所有其它的logger都是root logger的后代，所以它们（默认情况下）都将继承root logger的性质。<br /><br />BasicConfigurator.configure()完成的任务是： <br /><br />用默认pattern创建PatternLayout对象p： <br />PatternLayout p = new PatternLayout("%-4r[%t]%-5p%c%x - %m%n"); <br />用p创建ConsoleAppender对象a，目标是system.out，标准输出设备： <br />ConsoleAppender a = new ConsoleAppender(p,ConsoleAppender.SYSTEM_OUT); <br />为root logger增加一个ConsoleAppender p： <br />rootLogger.addAppender(p); <br />把root logger的log level设置为DEBUG级别： <br />rootLogger.setLevel(Level.DEBUG); <br /><br />xml格式的log4j配置文件概述 <br />xml格式的log4j配置文件需要使用org.apache.log4j.html.DOMConfigurator.configure()方法来读入。对xml文件的语法定义可以在log4j的发布包中找到：org/apache/log4j/xml/log4j.dtd。 <br /><br />log4j的xml配置文件的树状结构 <br />log4j的xml配置文件的树状结构如下所示，注意下图只显示了常用的部分。 :. <br /><br />xml declaration and dtd <br />| <br />log4j:configuration <br />| <br />+-- appender (name, class) <br />| | <br />| +-- param (name, value) <br />| +-- layout (class) <br />| | <br />| +-- param (name, value) <br />+-- logger (name, additivity) <br />| | <br />| +-- level (class, value) <br />| | | <br />| | +-- param (name, value) <br />| +-- appender-ref (ref) <br />+-- root <br />| <br />+-- param (name, class) <br />+-- level <br />| | <br />| +-- param (name, value) <br />+-- appender-ref (ref) <br /><br /><br />xml declaration and dtd <br />xml配置文件的头部包括两个部分：xml声明和dtd声明。头部的格式如下： :. <br /><br /><br />&lt;?xml version="1.0" encoding="UTF-8" ?&gt; <br />&lt;!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"&gt; <br /><br />log4j:configuration (root element) <br /><br />xmlns:log4j [#FIXED attribute] : 定义log4j的名字空间，取定值"http://jakarta.apache.org/log4j/" <br />appender [* child] : 一个appender子元素定义一个日志输出目的地 <br />logger [* child] : 一个logger子元素定义一个日志写出器 <br />root [? child] : root子元素定义了root logger <br /><br />appender <br />appender元素定义一个日志输出目的地。 <br /><br />name [#REQUIRED attribute] : 定义appender的名字，以便被后文引用 <br />class [#REQUIRED attribute] : 定义appender对象所属的类的全名 <br />param [* child] : 创建appender对象时传递给类构造方法的参数 <br />layout [? child] : 该appender使用的layout对象 <br /><br />layout <br />layout元素定义与某一个appender相联系的日志格式化器。 <br /><br />class [#REQUIRED attribute] : 定义layout对象所属的类的全名 <br />param [* child] : 创建layout对象时传递给类构造方法的参数 <br /><br /><br />logger <br />logger元素定义一个日志输出器。 <br /><br />name [#REQUIRED attribute] : 定义logger的名字，以便被后文引用 <br />additivity [#ENUM attribute] : 取值为"true"（默认）或者"false"，是否继承父logger的属性 <br />level [? child] : 定义该logger的日志级别 <br />appender-ref [* child] : 定义该logger的输出目的地 <br /><br />root <br />root元素定义根日志输出器root logger。 <br /><br />param [* child] : 创建root logger对象时传递给类构造方法的参数 <br />level [? child] : 定义root logger的日志级别 <br />appender-ref [* child] : 定义root logger的输出目的地 <br /><br />level <br />level元素定义logger对象的日志级别。 <br /><br />class [#IMPLIED attribute] : 定义level对象所属的类，默认情况下是"org.apache.log4j.Level类 <br />value [#REQUIRED attribute] : 为level对象赋值。可能的取值从小到大依次为"all"、"debug"、"info"、"warn"、"error"、"fatal"和"off"。当值为"off"时表示没有任何日志信息被输出 <br />param [* child] : 创建level对象时传递给类构造方法的参数 <br /><br />appender-ref <br />appender-ref元素引用一个appender元素的名字，为logger对象增加一个appender。 <br /><br />ref [#REQUIRED attribute] : 一个appender元素的名字的引用 <br />appender-ref元素没有子元素 <br /><br />param <br />param元素在创建对象时为类的构造方法提供参数。它可以成为appender、layout、filter、errorHandler、level、categoryFactory和root等元素的子元素。 <br /><br />name and value [#REQUIRED attributes] : 提供参数的一组名值对 <br />param元素没有子元素 <br /><br />在xml文件中配置appender和layout <br />创建不同的Appender对象或者不同的Layout对象要调用不同的构造方法。可以使用param子元素来设定不同的参数值。 <br /><br />创建ConsoleAppender对象 <br />ConsoleAppender的构造方法不接受其它的参数。 :. <br /><br />... ... ... ... <br />&lt;appender name="console.log" class="org.apache.log4j.ConsoleAppender"&gt; <br />&lt;layout ... &gt; <br />... ... <br />&lt;/layout&gt; <br />&lt;/appender&gt; <br />... ... ... ... <br /><br />创建FileAppender对象 <br />可以为FileAppender类的构造方法传递两个参数：File表示日志文件名；Append表示如文件已存在，是否把日志追加到文件尾部，可能取值为"true"和"false"（默认）。 :. <br /><br />... ... ... ... <br />&lt;appender name="file.log" class="org.apache.log4j.FileAppender"&gt; <br />&lt;param name="File" value="/tmp/log.txt" /&gt; <br />&lt;param name="Append" value="false" /&gt; <br />&lt;layout ... &gt; <br />... ... <br />&lt;/layout&gt; <br />&lt;/appender&gt; <br />... ... ... ... <br /><br />创建RollingFileAppender对象 <br />除了File和Append以外，还可以为RollingFileAppender类的构造方法传递两个参数：MaxBackupIndex备份日志文件的个数（默认是1个）；MaxFileSize表示日志文件允许的最大字节数（默认是10M）。 :. <br /><br />... ... ... ...<br />&lt;appender name="rollingFile.log" class="org.apache.log4j.RollingFileAppender"&gt; <br />&lt;param name="File" value="/tmp/rollingLog.txt" /&gt; <br />&lt;param name="Append" value="false" /&gt; <br />&lt;param name="MaxBackupIndex" value="2" /&gt; <br />&lt;param name="MaxFileSize" value="1024" /&gt; <br />&lt;layout ... &gt; <br />... ... <br />&lt;/layout&gt; <br />&lt;/appender&gt; <br />... ... ... ... <br /><br />创建PatternLayout对象 <br />可以为PatternLayout类的构造方法传递参数ConversionPattern。 :. <br /><br />... ... ... ... <br />&lt;layout class="org.apache.log4j.PatternLayout&gt; <br />&lt;param name="Conversion" value="%d [%t] %p - %m%n" /&gt; <br />&lt;/layout&gt; <br />... ... ... ... <br /><br />我自己的一个使用xml文件配置log4j环境的很简单的例子 <br />为WSOTA项目开发java web start的胖客户端时，使用了如下的xml文件配置log4j环境（文件名为wsota-rc.log4j.html）：:. <br /><br />&lt;?xml version="1.0" encoding="UTF-8" ?&gt; <br />&lt;!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"&gt; <br /><br />&lt;log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"&gt; <br /><br />&lt;!-- ================================================================= --&gt; <br />&lt;!-- a rolling file appender --&gt; <br />&lt;!-- ================================================================= --&gt; <br /><br />&lt;appender name="wsota-rc.file.log" class="org.apache.log4j.RollingFileAppender"&gt; <br />&lt;param name="File" value="/tmp/wsota-rc.log" /&gt; <br />&lt;param name="Append" value="false" /&gt; <br />&lt;layout class="org.apache.log4j.PatternLayout"&gt; <br />&lt;param name="ConversionPattern" value="%d [%t] %p - %m%n" /&gt; <br />&lt;/layout&gt; <br />&lt;/appender&gt; <br /><br />&lt;!-- ================================================================= --&gt; <br />&lt;!-- a console appender --&gt; <br />&lt;!-- debug can be turned off by setting level of root to "off" --&gt; <br />&lt;!-- ================================================================= --&gt; <br /><br />&lt;appender name="wsota-rc.console.log" class="org.apache.log4j.ConsoleAppender"&gt; <br />&lt;layout class="org.apache.log4j.PatternLayout"&gt; <br />&lt;param name="ConversionPattern" value="%d [%t] %p - %m%n" /&gt; <br />&lt;/layout&gt; <br />&lt;/appender&gt; <br /><br />&lt;!-- use this to turn on debug to a rolling file. --&gt; <br /><br />&lt;root&gt; <br />&lt;level value="debug" /&gt; <br />&lt;appender-ref ref="wsota-rc.file.log" /&gt; <br />&lt;/root&gt; <br /><br />&lt;!-- use this to turn on debug to console. --&gt; <br />&lt;!-- <br />&lt;root&gt; <br />&lt;level value="off" /&gt; <br />&lt;appender-ref ref="wsota-rc.console.log" /&gt; <br />&lt;/root&gt; <br />--&gt; <br /><br />&lt;!-- use this to turn off debug. --&gt; <br />&lt;!-- <br />&lt;root&gt; <br />&lt;level value="off" /&gt; <br />&lt;appender-ref ref="wsota-rc.console.log" /&gt; <br />&lt;/root&gt; <br />--&gt; <br /><br />&lt;/log4j:configuration&gt; <br /><br /><br />在胖客户程序中使用了如下代码来使用外部xml文件配置log4j环境，注意该代码段位于程序的main class的静态初始化块中，含有以下代码的类和xml配置文件在同一个目录下：:. <br /><br /><br />import org.apache.log4j.html.DOMConfigurator; <br /><br />public class SapFrame extends JFrame { <br />static { <br />DOMConfigurator.configure(SapFrame.class.getResource("wsota-rc.log4j.html")); <br />} <br />... ... ... ... <br />} <br /><br />Log4j的编码习惯 <br /><br />让每个类都拥有一个private static的Logger对象，用来输出该类中的全部日志信息 <br />使用xml文件来完成对log4j环境的配置。在项目的main class中的静态初始化块里放log4j环境的配置代码。注意：在一个项目中，log4j环境只需要被配置一次，而不是在每个使用了logger的类里都需要调用一次 <br />用MyClass.class作为参数创建该类的静态Logger对象 <br />补充中... <br /><br />from: http://zooo.51.net/heavyz_cs/notebook/log4j.html<img src ="http://www.blogjava.net/zeroone0/aggbug/39694.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-06 23:26 <a href="http://www.blogjava.net/zeroone0/articles/39694.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Log4j简明手册</title><link>http://www.blogjava.net/zeroone0/articles/39692.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Thu, 06 Apr 2006 15:18:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/39692.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/39692.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/39692.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/39692.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/39692.html</trackback:ping><description><![CDATA[Log4j简明手册<br /><br />1. 概述<br />本文主要描述Log4j的API的唯一特性和它的设计原理。Log4j是一个基于许多作者的开放源码的项目。它允许开发员以任意的间隔来控制日志的输出。它通过设在外部的配置文件而达到运行时灵活的设置。最重要的是，Log4j有一个平稳的学习曲线。注意：根据来自用户的反馈判断，它很容易使人上瘾。<br /><br />2. 导言<br /><br />几乎所有的大型应用程序都包括它的自己的日志和跟踪API。顺应这个规则，E.U. SEMPER 项目决定写它自己的跟踪PAI。这是1996年初。在无数次加强，几次变形和许多工作后，那个API变成了如今的Log4j，一个流行的java日志包。这个包以Apache Software License协议发布，一个成熟的开放源吗协议。最新的Log4j版本，包括全部的源码，class文件和文档，你可以在http://jakarta.apache.org/Log4j/上找到。顺便，Log4j已经给C, C++, C#, Python, Ruby, and Eiffel 语言都提供了接口。<br /><br />为了调试而插入日志输出到代码里是一个低技术成分的方法，但它可能也是唯一的方法，因为调试器并不是一直可用或者可以适应的，尤其对于多线程的分布使式的大型程序而言。<br /><br />经验指出调试是软件开发周期中一个重要的组成部分。<br /><br />Log4j拥有几个优点：<br /><br />首先，它提供关于运行程序的准确的环境。一旦代码被插入，不需要人工干预就可以产生调试信息。<br /><br />其次，日志输出可以被有计划的保存在永久媒体中以便日后研究。<br /><br />另外，除了在开发周期中，一个充分详尽的日志包可以被用来作为以后的统计工具。<br /><br />Log4j当然还有它的缺点，它可能减慢程序。如果太详细，它可能导致屏幕盲目滚动。排除这些情况，Log4j是可靠的，快速的，可以扩展的。因为日志很少是一个应用程序的主要目的, 设计者们正努力使得Log4j API学习和使用简单化。<br /><br />3. 日志类别、输出源和布局<br /><br />Log4j有三个主要的组件：日志类别（Loggers）、输出源（ Appenders）和布局（Layouts）。这三种类型的组件一起工作使得开发员可以根据信息的类型和级别记录它们，并且在运行时控制这些信息的输出格式和位置。<br /><br />3.1 日志类别的层次结构(Loggers)<br /><br />Log4j首要的相对于简单的使用System.out.println()方法的优点是基于它的在禁止一些特定的信息输出的同时不妨碍其它信息的输出的能力。这个能力源自于日志命名空间，也就是说，所有日志声明的空间，它根据一些开发员选择的公式而分类。以前的观察引导我们选择类别作为包的中心概念。然而，自从Log4j的1.2版本，Logger类被Catalog类所取代，对于那些熟悉Log4j以前版本的人来说，Logger类可以被想象成仅仅是Category 类的别名。<br /><br />Loggers 被指定为实体，Logger的名字是大小写敏感的，它们遵循以下的命名<br /><br />规则：<br /><br />2 命名继承<br /><br />如果类别的名称（后面加一个点）是其子类别名称的前缀，则它就是另一个类别的祖辈。<br /><br />如果一个类别(Logger)和它的子类别之间没有其它的继承关系，我们就称之为parent与child的关系。<br /><br />例如，类别"com.foo"是类别"com.foo.Bar"的parent。相似的，"java"是"java.util"的parent，是"java.util.Vector"的父辈。.这个命名规则应该被大多数的开发员所熟悉。<br /><br />根(root) 类别位于logger继承结构的最上层。它有两种例外：<br /><br />1.它一直存在<br /><br />2.它不能根据名称而获得。 <br /><br />调用类的静态方法Logger.getRootLogger可以得到它。其它所有的Logger可以通过静态方法Logger.getLogger而得到它们自己的实例。这个方法取希望的Logger名作为参数。Logger的一些基本的方法示例如下：<br /><br />package org.apache.Log4j;<br /><br />public Logger class {<br /><br />// Creation &amp; retrieval methods:<br /><br />public static Logger getRootLogger();<br /><br />public static Logger getLogger(String name);<br /><br />// printing methods:<br /><br />public void debug(Object message);<br /><br />public void info(Object message);<br /><br />public void warn(Object message);<br /><br />public void error(Object message);<br /><br />// generic printing method:<br /><br />public void log(Level l, Object message);<br /><br />}<br /><br />Loggers可以被分配的级别。所有级别的集合包括：<br /><br />DEBUG<br /><br />INFO<br /><br />WARN<br /><br />ERROR<br /><br />FATAL<br /><br />它们被定义于org.apache.Log4j.Level 类。虽然我们不鼓励，但是你们可以通过继承Level类来定义你们自己的级别。我们随后将介绍一个比较好的方法。<br /><br />如果一个Logger没有被分配一个级别，那么它将从一个被分配了级别的最接近它的ancestor哪里继承。<br /><br />正规的说：<br /><br />2 级别继承<br /><br />对于一个给定的Logger C，它的继承的级别等于从C开始上溯到的第一个拥有非空级别的Logger的级别。<br /><br />为了保证所有的Logger最终能够继承到一个级别，根Logger通常有一个已经定义了的级别。<br /><br />以下四个表中的数据演示了根据以上规则得到的结果。<br /><br />类别名<br />分配的级别<br />继承的级别<br /><br />root<br />Proot<br />Proot<br /><br />X <br />none<br />Proot<br /><br />X.Y <br />none<br />Proot<br /><br />X.Y.Z<br />none<br />Proot<br /><br />Example 1<br /><br /><br /><br />在例子1中，只有根Logger定义了一个级别，它的级别的值--"Proot"被所有其它的Loggers X, X.Y, 和X.Y.Z所继承。<br /><br />类别名<br />分配的级别<br />继承的级别<br /><br />root<br />Proot<br />Proot<br /><br />X <br />Px<br />Px<br /><br />X.Y <br />Pxy<br />Pxy<br /><br />X.Y.Z<br />Pxyz<br />Pxyz<br /><br />Example 2<br /><br /><br /><br />在例子2中，所有的Logger都有一个被分配的级别值，所以它们不需要级别继承。<br /><br />类别名<br />分配的级别<br />继承的级别<br /><br />root<br />Proot<br />Proot<br /><br />X <br />Px<br />Px<br /><br />X.Y <br />none<br />Px<br /><br />X.Y.Z<br />Pxyz<br />Pxyz<br /><br />Example 3<br /><br /><br /><br />在例子3中，根Logger，以及X和X.Y.Z被分别分配了级别Proot，Px和Pxyz。Logger X.Y从它的parent X继承了级别值Px。<br /><br />类别名<br />分配的级别<br />继承的级别<br /><br />root<br />Proot<br />Proot<br /><br />X <br />Px<br />Px<br /><br />X.Y <br />none<br />Px<br /><br />X.Y.Z<br />none<br />Px<br /><br />Example 4<br /><br /><br /><br />在例子4中，根Logger和X被分别分配了级别"Proot"和"Px"，Logger X.Y 和 X.Y.Z从被分配了级别的最接近它们的ancestor X那里得到继承。<br /><br />我们需要通过调用Logger的输出的实例方法之一来实现日志请求。这些输出的方法是debug, info, warn, error, fatal 和 log.<br /><br />通过定义输出方法来区分日志的请求的级别。例如，如果c是一个Logger的实例，那么声明 c.info 就是一个INFO级别的日志请求。<br /><br />如果一个日志的请求的级别高于或等于日志的级别那么它就能被启用。反之，将被禁用。一个没有被安排级别的Logger将从它的父辈中得到继承。这个规则总结如下。<br /><br />2 基本的选择规则<br /><br />假如在一个级别为q的Logger中发生一个级别为p的日志请求，如果p&gt;=q,那么请求将被启用。<br /><br />这是Log4j的核心原则。它假设级别是有序的。对于标准级别，我们定义DEBUG &lt; INFO &lt; WARN &lt; ERROR &lt; FATAL. <br /><br />以下是关于这条规则的一个例子。<br /><br />// get a logger instance named "com.foo"<br /><br />Logger logger = Logger.getLogger("com.foo");<br /><br />// Now set its level. Normally you do not need to set the <br /><br />// level of a logger progamitcally. This is usually done <br /><br />// in configuration files.<br /><br />cat.setLevel(Level.INFO);<br /><br />Logger barlogger = Logger.getLogger("com.foo.Bar");<br /><br />// This request is enabled, because WARN &gt;= INFO.<br /><br />logger.warn("Low fuel level.");<br /><br />// This request is disabled, because DEBUG &lt; INFO.<br /><br />logger.debug("Starting search for nearest gas station."); <br /><br />// The logger instance barlogger, named "com.foo.Bar",<br /><br />// will inherit its level from the logger named <br /><br />// "com.foo" Thus, the following request is enabled <br /><br />// because INFO &gt;= INFO. <br /><br />barlogger.info("Located nearest gas station."); <br /><br />// This request is disabled, because DEBUG &lt; INFO.<br /><br />barlogger.debug("Exiting gas station search"); <br /><br />调用getLogger方法将返回一个同名的Logger对象的实例。<br /><br />例如，<br /><br />Categoty x = Logger.getLogger("wombat");<br /><br />Categoty y = Logger.getLogger("wombat");<br /><br />x和y参照的是同一个Logger对象。<br /><br />这样我们就可以先定义一个Logger，然后在代码的其它地方不需传参就可以重新得到我们已经定义了的Logger的实例.<br /><br />同基本的生物学理论--父先于子相反，Log4j 的loggers可以以任何顺序创造和配置。特别是，一个后实例化的"parent"logger能够找到并且连接它的子logger。<br /><br />配置Log4j的环境通常在一个应用程序被初始化的时候进行，最好的方法是通过读一个配置文件。这个方法我们将简短介绍。<br /><br />Log4j使得通过软件组件命名logger很容易。我们可以通过Logger的静态的初始化方法在每一个类里定义一个logger，令logger的名字等于类名的全局名，而实现logger的命名。这是一个实效的简单的定义一个logger的方法。因为日志输出带有产生日志的类的名字，这个命名策略使得我们更容易定位到一个日志信息的来源。虽然普通，但却是命名logger的常用策略之一。Log4j没有限制定义logger的可能。开发员可以自由的按照它们的意愿定义logger的名称。<br /><br />然而，以类的所在位置来命名Logger好象是目前已知的最好方法。<br /><br />3.2 输出源（Appenders）和布局（Layouts）<br /><br />有选择的能用或者禁用日志请求仅仅是Log4j的一部分功能。Log4j允许日志请求被输出到多个输出源。用Log4j的话说，一个输出源被称做一个Appender. 。Appender包括console（控制台）, files（文件）, GUI components（图形的组件）, remote socket servers（socket 服务）, JMS（java信息服务）, NT Event Loggers（NT的事件日志）, and remote UNIX Syslog daemons（远程UNIX的后台日志服务）。它也可以做到异步记录。<br /><br />一个logger可以设置超过一个的appender。<br /><br />用addAppender 方法添加一个appender到一个给定的logger。对于一个给定的logger它每个生效的日志请求都被转发到该logger所有的appender上和该logger的父辈logger的appender上。换句话说，appender自动从它的父辈获得继承。举例来说，如果一个根logger拥有一个console appender，那么所有生效的日志请求至少会被输出到console上。如果一个名为C的logger有一个file类型的appender，那么它就会对它自己以及所有它的子logger生效。我们也可以通过设置appender的additivity flag 为false，来重载appender的默认行为，以便继承的属性不在生效。<br /><br />调节输出源（appender）添加性的规则如下。<br /><br />输出源的可添加性（Appender Additivity ）<br /><br />一个名为C的logger的日志定义的输出将延续到它自身以及它的ancestor logger的appenders。这就是术语"appender additivity"的含义。 <br /><br />然而，logger C的一个ancestor logger P，它的附加标志被设为false，那么C的输出将被定位到所有C的appender，以及从它开始上溯到P的所有ancestor logger的appender。<br /><br />Loggers的附加标记（additivity flag）默认为true。<br /><br />下表是一个例子。<br /><br />Logger<br />Name <br />Added<br />Appenders <br />Additivity<br />Flag <br />Output Targets <br />Comment <br /><br />root <br />A1 <br />not applicable <br />A1 <br />The root logger is anonymous but can be accessed with the Logger.getRootLogger() method. There is no default appender attached to root. <br /><br />x <br />A-x1, A-x2 <br />true <br />A1, A-x1, A-x2 <br />Appenders of "x" and root. <br /><br />x.y <br />none <br />true <br />A1, A-x1, A-x2 <br />Appenders of "x" and root. <br /><br />x.y.z <br />A-xyz1 <br />true <br />A1, A-x1, A-x2, A-xyz1 <br />Appenders in "x.y.z", "x" and root. <br /><br />security <br />A-sec <br />false <br />A-sec <br />No appender accumulation since the additivity flag is set to false. <br /><br />security.access <br />none <br />true <br />A-sec <br />Only appenders of "security" because the additivity flag in "security" is set to false. <br /><br /><br />经常，用户希望自定义不但输出源，而且定义输出格式。这个是通过在一个appender上附加一个layout来完成的。layout是负责根据用户的希望来格式化日志请求。而appender是负责发送格式化的输出到它的目的地。PatternLayout，作为Log4j标准版中的一部分，让用户指以类似C语言的printf方法的格式来指定日志的输出格式。<br /><br />例如，转化模式为"%r [%t] %-5p %c - %m%n" 的PatternLayout 将输出类似如下的信息：<br /><br />176 [main] INFO org.foo.Bar - Located nearest gas station.<br /><br />第一个栏位是自从程序开始后消逝的毫秒数。<br /><br />第二个栏位是做出日志的线程。<br /><br />第三个栏位是log的级别。<br /><br />第四个栏位是日志请求相关的logger的名字。而"-"后的文字是信息的表述。<br /><br />Log4j将根据用户定义的公式来修饰日志信息的内容。例如，如果你经常需要记录Oranges，一个在你当前的项目被用到的对象类型，那么你可以注册一个OrangeRenderer ，它将在一个orange需要被记录时被调用。<br /><br />对象渲染类似的类的结构继承。例如，假设oranges是fruits，如果你注册了一个FruitRenderer，所有的水果包括oranges将被FruitRenderer所渲染。除非你注册了一个orange。<br /><br />对象渲染必须实现ObjectRenderer接口。<br /><br />4. 配置<br /><br />插入日志请求到应用程序的代码中需要大量的预先计划和最终努力。观察显示大约4%的代码是用来输出的。<br /><br />因此，大小适度的程序都被嵌入有成千个日志输出语句。为了以无需手工的方式管理这些日志的输出状态，给日志输出以编号和规范变得势在必行。 <br /><br />Log4j在程序中有充分的可配置性。然而，用配置文件配置Log4j具有更大的弹性。目前，它的配置文件支持xml和java properties（key=value）文件两种格式。<br /><br />让我们以一个例子来演示它是如何做的。假定有一个用了Log4j的程序MyApp。<br /><br /><br />import com.foo.Bar;<br /><br />// Import Log4j classes.<br /><br />import org.apache.Log4j.Logger;<br /><br />import org.apache.Log4j.BasicConfigurator;<br /><br />public class MyApp {<br /><br />// Define a static logger variable so that it references the<br /><br />// Logger instance named "MyApp".<br /><br />static Logger logger = Logger.getLogger(MyApp.class);<br /><br />public static void main(String[] args) {<br /><br />// Set up a simple configuration that logs on the console.<br /><br />BasicConfigurator.configure();<br /><br />logger.info("Entering application.");<br /><br />Bar bar = new Bar();<br /><br />bar.doIt();<br /><br />logger.info("Exiting application.");<br /><br />}<br /><br />}<br /><br />MyApp以引入Log4j的相关类开始，接着它定义了一个静态logger变量，并给予值为"MyApp"类的全路径名称。<br /><br />MYApp用了定义在包com.foo中的类Bar.<br /><br />package com.foo;<br /><br />import org.apache.Log4j.Logger;<br /><br />public class Bar {<br /><br />static Logger logger = Logger.getLogger(Bar.class);<br /><br />public void doIt() {<br /><br />logger.debug("Did it again!");<br /><br />}<br /><br />}<br /><br />调用BasicConfigurator.configure()方法创建了一个相当简单的Log4j的设置。它加入一<br /><br />个ConsoleAppender到根logger。输出将被采用了"%-4r [%t] %-5p %c %x - %m%n"模式<br /><br />的PatternLayout所格式化。<br /><br />注意，根logger默认被分配了Level.DEBUG的级别。<br /><br />MyApp的输出为：<br /><br />0 [main] INFO MyApp - Entering application.<br /><br />36 [main] DEBUG com.foo.Bar - Did it again!<br /><br />51 [main] INFO MyApp - Exiting application.<br /><br />随后的图形描述了在调用BasicConfigurator.configure()方法后MyApp的对象图。<br /><br /><br /><br /><br /><br />一边要提醒的是，Log4j的子logger只连接到已经存在的它们的父代。特别的是，名为<br /><br />com.foo.bar的logger是直接连接到根logger，而不是围绕着没用的com或com.foo<br /><br />logger。这显著的提高了程序性能并且减少的内存占用。<br /><br />MyApp类配置Log4j是通过调用BasicConfigurator.configure 方法。其它的类仅仅<br /><br />需要引入org.apache.Log4j.Logger 类，找到它们希望用的logger，并且用它就行。<br /><br />以前的例子通常输出同样的日志信息。幸运的是，修改MyApp是容易的，以便日志输<br /><br />出可以在运行时刻被控制。这里是一个小小修改的版本。<br /><br /><br /><br />import com.foo.Bar;<br /><br />import org.apache.Log4j.Logger;<br /><br />import org.apache.Log4j.PropertyConfigurator;<br /><br />public class MyApp {<br /><br />static Logger logger = Logger.getLogger(MyApp.class.getName());<br /><br />public static void main(String[] args) {<br /><br />// BasicConfigurator replaced with PropertyConfigurator.<br /><br />PropertyConfigurator.configure(args[0]);<br /><br />logger.info("Entering application.");<br /><br />Bar bar = new Bar();<br /><br />bar.doIt();<br /><br />logger.info("Exiting application.");<br /><br />}<br /><br />}<br /><br />修改后的 MyApp通知程序调用PropertyConfigurator()方法解析一个配置文件，并且根<br /><br />据这个配置文件来设置日志。<br /><br />这里是一个配置文件的例子，它将产生同以前BasicConfigurator 基本例子一样<br /><br />的输出结果。<br /><br /># Set root logger level to DEBUG and its only appender to A1.<br /><br />Log4j.rootLogger=DEBUG, A1<br /><br /># A1 is set to be a ConsoleAppender.<br /><br />Log4j.appender.A1=org.apache.Log4j.ConsoleAppender<br /><br /># A1 uses PatternLayout.<br /><br />Log4j.appender.A1.layout=org.apache.Log4j.PatternLayout<br /><br />Log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n<br /><br />假设我们不在对com.foo包的任何类的输出感兴趣的话，随后的配置文件向我们展示<br /><br />了实现这个目的的方法之一。<br /><br />Log4j.rootLogger=DEBUG, A1<br /><br />Log4j.appender.A1=org.apache.Log4j.ConsoleAppender<br /><br />Log4j.appender.A1.layout=org.apache.Log4j.PatternLayout<br /><br /># Print the date in ISO 8601 format<br /><br />Log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n<br /><br /># Print only messages of level WARN or above in the package com.foo.<br /><br />Log4j.logger.com.foo=WARN<br /><br />以这个配置文件配置好的MyApp将输出如下：<br /><br />2000-09-07 14:07:41,508 [main] INFO MyApp - Entering application.<br /><br />2000-09-07 14:07:41,529 [main] INFO MyApp - Exiting application.<br /><br />当logger com.foo.bar没有被分配一个级别，它将从com.foo继承，在配置文件中<br /><br />它被设置了WARN的级别。在Bar.doIt方法中定义的log为DEBUG级别，低于WARN，<br /><br />因此doIt() 方法的日志请求被禁用。<br /><br />这里是另外一个配置文件，它使用了多个appenders.<br /><br />Log4j.rootLogger=debug, stdout, R<br /><br />Log4j.appender.stdout=org.apache.Log4j.ConsoleAppender<br /><br />Log4j.appender.stdout.layout=org.apache.Log4j.PatternLayout<br /><br /># Pattern to output the caller´s file name and line number.<br /><br />Log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n<br /><br />Log4j.appender.R=org.apache.Log4j.RollingFileAppender<br /><br />Log4j.appender.R.File=example.log<br /><br />Log4j.appender.R.MaxFileSize=100KB<br /><br /># Keep one backup file<br /><br />Log4j.appender.R.MaxBackupIndex=1<br /><br />Log4j.appender.R.layout=org.apache.Log4j.PatternLayout<br /><br />Log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n<br /><br />以这个配置文件调用加强了的MyApp类将输出如下信息.<br /><br />INFO [main] (MyApp2.java:12) - Entering application.<br /><br />DEBUG [main] (Bar.java:8) - Doing it again!<br /><br />INFO [main] (MyApp2.java:15) - Exiting application.<br /><br />另外,因为根logger有被分配第二个appender,所以输出也将被定向到example.log文件。<br /><br />这个文件大小达到100kb时将自动备份。备份时老版本的example.log文件自动被移到<br /><br />文件example.log.1中。<br /><br />注意我们不需要重新编译代码就可以获得这些不同的日志行为。我们一样可以容易<br /><br />的使日志输出到UNIX Syslog daemon, 重定向所有的com.foo到NT Event logger,<br /><br />或者转发日志到一个远程的Log4j服务器,它根据本地server的策略来进行日志输出。例<br /><br />如转发日志事件到第二个Log4j服务器.<br /><br /><br /><br />5. 默认的初始化过程<br />Log4j类库不对它的环境做任何假设。特别是没有默认的Log4j appender。在一些特别<br /><br />的有着良好定义的环境下，logger的静态inializer将尝试自动的配置Log4j。<br /><br />java语言的特性保证类的静态initializer当且仅当装载类到内存之时只会被调用一次。<br /><br />要记住的重要一点是，不同的类装载器可能装载同一个类的完全不同的拷贝。<br /><br />这些同样类的拷贝被虚拟机认为是完全不相干的。<br /><br />默认的initialization是非常有用的，特别是在一些应用程序所依靠的运行环境被准确的<br /><br />定位的情况下。例如，同一样的应用程序可以被用做一个标准的应用程序，或一个<br /><br />applet，或一个在web-server控制下的servlet。<br /><br />准确的默认的initialization原理被定义如下：<br /><br />1.设置系统属性Log4j.defaultInitOverride为"false"以外的其它值，那么Log4j将<br /><br />跳过默认的initialization过程。<br /><br />2.设置资源变量字符串给系统属性Log4j.configuration。定义默认initialization<br /><br />文件的最好的方法是通过系统属性Log4j.configuration。万一系统属性<br /><br />Log4j.configuration没有被定义，那么设置字符串变量resource 给它的默认值<br /><br />Log4j.properties。<br /><br />3.尝试转换resource 变量为一个URL。<br /><br />4.如果变量resource的值不能被转换为一个URL，例如由于MalformedURLException违<br /><br />例，那么通过调用<br /><br />org.apache.Log4j.helpers.Loader.getResource(resource, Logger.class) 方法从<br /><br />classpath中搜索resource，它将返回一个URL，并通知"Log4j.properties"的值是一个错<br /><br />误的URL。<br /><br />看See Loader.getResource(java.lang.String) 查看搜索位置的列表。<br /><br />5.如果没有URL被发现，那么放弃默认的initialization。否则用URL配置Log4j。<br /><br />PropertyConfigurator将用来解析URL，配置Log4j，除非URL以".xml"为结尾。<br /><br />在这种情况下的话DOMConfigurator将被调用。你可以有机会定义一个自定义的<br /><br />configurator。<br /><br />系统属性Log4j.configuratorClass 的值取自你的自定义的类名的全路径。<br /><br />你自定义的configurator必须实现configurator接口。<br /><br /><br /><br />6. 配置范例<br />6.1 Tomcat下的初始化<br />默认的Log4j initialization典型的应用是在web-server 环境下。在tomcat3.x和tomcat4.x<br /><br />下，你应该将配置文件Log4j.properties放在你的web应用程序的WEB-INF/classes 目录<br /><br />下。<br /><br />Log4j将发现属性文件，并且以此初始化。这是使它工作的最容易的方法。<br /><br />你也可以选择在运行tomcat前设置系统属性Log4j.configuration 。对于tomcat 3.x，<br /><br />TOMCAT_OPTS 系统变量是用来设置命令行的选项。对于tomcat4.0，用系统环境变<br /><br />量CATALINA_OPTS 代替了TOMCAT_OPTS。<br /><br />Example 1 <br /><br />UNIX 命令行<br /><br />export TOMCAT_OPTS="-DLog4j.configuration=foobar.txt"<br /><br />告诉Log4j用文件foobar.txt作为默认的配置文件。这个文件应该放在WEB-INF/classes <br /><br />目录下。这个文件将被PropertyConfigurator所读。每个web-application将用不同的默认<br /><br />配置文件，因为每个文件是和它的web-application 相关的。<br /><br />Example 2 <br /><br />UNIX 命令行<br /><br />export TOMCAT_OPTS="-DLog4j.debug -DLog4j.configuration=foobar.xml"<br /><br />告诉Log4j输出Log4j-internal的调试信息，并且用foobar.xml作为默认的配置文件。<br /><br />这个文件应该放在你的web-application的WEB-INF/classes 目录下。因为有.xml的<br /><br />扩展名，它将被DOMConfigurator所读。每个web-application将用不同的默认<br /><br />配置文件。因为每个文件都和它所在的web-application 相关的。<br /><br />Example 3 <br /><br />UNIX 命令行<br /><br />set TOMCAT_OPTS=-DLog4j.configuration=foobar.lcf -DLog4j.configuratorClass=com.foo.BarConfigurator<br /><br />告诉Log4j用文件foobar.lcf作为默认的配置文件。这个文件应该放在你的<br /><br />web-application的WEB-INF/classes 目录下。因为定义了Log4j.configuratorClass 系统属<br /><br />性，文件将用自定义的com.foo.barconfigurator类来解析。每个web-application将用不<br /><br />同的默认配置文件。因为每个文件都和它所在的web-application 相关的。<br /><br />Example 4 <br /><br />UNIX 命令行<br /><br />set TOMCAT_OPTS=-DLog4j.configuration=file:/c:/foobar.lcf<br /><br />告诉Log4j用文件foobar.lcf作为默认的配置文件。这个配置文件用URL file:/c:/foobar.lcf<br /><br />定义了全路径名。这样同样的配置文件将被所有的web-application所用。<br /><br />不同的web-application将通过它们自己的类装载器来装载Log4j。这样，每个Log4j的环<br /><br />境将独立的运作，而没有任何的相互同步。例如：在多个web-application中定义了<br /><br />完全相同的输出源的FileAppenders将尝试写同样的文件。结果好象是缺乏安全性的。<br /><br />你必须确保每个不同的web-application的Log4j配置没有用到同样的系统资源。<br /><br /><br /><br />6.2 Servlet 的初始化<br />用一个特别的servlet来做Log4j的初始化也是可以的。如下是一个例子：<br /><br />package com.foo;<br /><br />import org.apache.Log4j.PropertyConfigurator;<br /><br />import javax.servlet.http.HttpServlet;<br /><br />import javax.servlet.http.HttpServletRequest;<br /><br />import javax.servlet.http.HttpServletResponse;<br /><br />import java.io.PrintWriter;<br /><br />import java.io.IOException;<br /><br />public class Log4jInit extends HttpServlet {<br /><br />public void init() {<br /><br />String prefix = getServletContext().getRealPath("/");<br /><br />String file = getInitParameter("Log4j-init-file");<br /><br />// if the Log4j-init-file is not set, then no point in trying<br /><br />if(file != null) {<br /><br />PropertyConfigurator.configure(prefix+file);<br /><br />}<br /><br />}<br /><br />public void doGet(HttpServletRequest req, HttpServletResponse res) {<br /><br />}<br /><br />}<br /><br />在web.xml中定义随后的servlet为你的web-application。<br /><br />&lt;servlet&gt;<br /><br />&lt;servlet-name&gt;Log4j-init&lt;/servlet-name&gt;<br /><br />&lt;servlet-class&gt;com.foo.Log4jInit&lt;/servlet-class&gt;<br /><br />&lt;init-param&gt;<br /><br />&lt;param-name&gt;Log4j-init-file&lt;/param-name&gt;<br /><br />&lt;param-value&gt;WEB-INF/classes/Log4j.lcf&lt;/param-value&gt;<br /><br />&lt;/init-param&gt;<br /><br />&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;<br /><br />&lt;/servlet&gt;<br /><br />写一个初始化的servlet是最有弹性的初始化Log4j的方法。代码中没有任何限制，你可<br /><br />以在servlet的init方法中定义它。<br /><br />7. Nested Diagnostic Contexts<br /><br />在现实世界中的系统经常不得不同时处理多个客户端请求。在这样的一个典型的多线程的系统中，不同的线程将处理不同的客户端。Logging特别能够适应这种复杂的分布式的应用程序的调试和跟踪。一个常见的区分每个客户端所输出的Logging的方法是为每个客户端实例化一个新的独立的Logger。这导致Logger的大量产生，管理的成本也超过了logging本身。 <br /><br />唯一标识每个log请求是一个轻量级的技术。Neil Harrison 在名为“Patterns for Logging Diagnostic Messages”的书中描述了这个方法in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997). <br /><br />为了唯一标识每个请求，用户把上下文信息推入NDC(Nested Diagnostic Context)中。<br /><br />NDC类示例如下：<br /><br />public class NDC {<br /><br />// Used when printing the diagnostic<br /><br />public static String get();<br /><br />// Remove the top of the context from the NDC.<br /><br />public static String pop();<br /><br />// Add diagnostic context for the current thread.<br /><br />public static void push(String message);<br /><br />// Remove the diagnostic context for this thread.<br /><br />public static void remove();<br /><br />}<br /><br />NDC如同一个堆栈样管理每个线程。注意所有the org.apache.log4j.NDC 类的方法都是静态的。假设NDC输出被开启，每次一个log 请求被生成时，适当的log4j组件为将要输出log的线程包含完整的NDC堆栈。这是在没有用户的干预的情况下做到的，用户只负责在NDC中定位正确的信息，通过在代码中正确位置插入很少的push和pop方法就行了。相反的，在代码中per-client实现方法有着很大变化。<br /><br />为了演示这个点，让我们以一个发送内容到匿名客户端的servlet为例。这个servlet可以在开始执行每个其他代码前的初始化时建立NDC。上下文信息可以是客户主机的名字和其他的请求中固有的信息。<br /><br />典型的信息包括cookies。因此，即使servlet同时为多个客户同时提供服务，log 被同样的代码初始化，例如属于同一个logger，依然可以被区别，因为每个客户请求将有不同的NDC堆栈。与之相比，Contrast this with the complexity of passing a freshly instantiated logger to all code exercised during the client´s request。<br /><br />不过，一些诡异的程序，例如虚拟主机的web server记录日志，不是一般的依靠虚拟主机的上下文，还要依靠软件的组件发出请求。近来log4j的发布版本支持多层的树形结构。这个增强允许每个虚拟主机可以处理在树型结构中属于它自己的logger。<br /><br />8. 优化<br />一个经常引用的依靠于logging的参数是可以计算的花费。这是一个合理的概念，一个适度的应用程序可能产生成千上万个日志请求。许多努力花在测量和调试logging的优化上。Log4j要求快速和弹性：速度最重要，弹性是其次。<br /><br />用户应该注意随后的优化建议。<br /><br />1.日志为禁用时，日志的优化。<br /><br />当日志被彻底的关闭，一个日志请求的花费等于一个方法的调用加上整数的比较时间。<br /><br />在233mhz的Pentium II 机器上这个花费通常在5-50纳秒之间。<br /><br />然而，方法调用包括参数构建的隐藏花费。<br /><br />例如，对于logger cat，<br /><br />logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));<br /><br />引起了构建信息参数的花费，例如，转化整数i和entry[i]到一个string，并且连接中间字符串，不管信息是否被输出。这个参数的构建花费可能是很高，它主要决定于被调用的参数的大小。<br /><br />避免参数构建的花费应如下，<br /><br />if(logger.isDebugEnabled() {<br /><br />logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));<br /><br />}<br /><br />如果logger的debug被关闭这将不会招致参数构建的花费。另一方面，如果logger是debug的话，它将产生两次判断 logger是否能用的花费。一次是在debugenabled，一次是debug。这是无关紧要的，因为判断日志的能用 只占日志实际花费时间的约1%。<br /><br />在Log4j里，日志请求在Logger 类的实例里。Logger 是一个类，而不是一个接口。这大量的减少了在方法调用上的弹性化的花费。<br /><br />当然用户采用预处理或编译时间技术去编译出所有的日志声明。这将导致完美的执行成效。然而因为二进制应用程序不包括任何的日志声明的结果，日志不可能对那个二进制程序开启。以我的观点，以这种较大的代价来换取较小的性能优化是不值得的。<br /><br />2。当日志状态为启用时，日志的优化。<br /><br />这是本质上的优化logger的层次。当日志状态为开，Log4j依然需要比较请求的级别与logger的级别。然而， logger可能没有被安排一个级别；它们将从它们的father继承。这样，在继承之前，logger可能需要搜索它的ancestor。<br /><br />这里有一个认真的努力使层次的搜索尽可能的快。例如，子logger仅仅连接到它的存在的father logger。<br /><br />在先前展示的BasicConfigurator 例子中，名为com.foo.bar 的logger是连接到跟根logger，因此绕过 了不存在的logger com和com.foo。这将显著的改善执行的速度，特别是解析logger的层结构时。<br /><br />典型的层次结构的解析的花费是logger彻底关闭时的三倍。<br /><br />3.日志信息的输出时，日志的优化。<br /><br />这是主要花费在日志输出的格式化和发送它到它的输出源上。这里我们再一次的付出努力以使格式化执行的尽可能快。同appender一样。实际上典型的花费大约是100-300毫秒。<br /><br />详情看org.apache.log4.performance.Logging。<br /><br />虽然Log4j有许多特点，但是它的第一个设计目标还是速度。一些Log4j的组件已经被重写过很多次以改善性能。不过，投稿者经常提出了新的优化。你应该满意的知道，以SimpleLayout的配置执行测试已经展示了Log4j的输出同System.out.println一样快。<br /><br />9. 总结<br />Log4j是一个用java写成的流行的日志包。一个它与众不同的特点是在logger中的继承的概念。用logger的继承可以以任意的间隔控制日志的状态输出。这个减少了体积和最小化日志的代价。<br /><br />易管理性是Log4j API的优点之一。只要日志定义被加入到代码中，它们就可以用配置文件来控制。它们可以有选择的被禁用，并且发送到不同的多个输出源上，以用户选择好的格式。<br /><br />Log4j的包被设计成，不需花费沉重的执行代价就可以保留它们在产品中。<br /><br /><img src ="http://www.blogjava.net/zeroone0/aggbug/39692.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-06 23:18 <a href="http://www.blogjava.net/zeroone0/articles/39692.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java 1.5</title><link>http://www.blogjava.net/zeroone0/articles/39291.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Tue, 04 Apr 2006 23:51:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/39291.html</guid><description><![CDATA[
		<p>
				<b>增强的for循环</b>
				<br />　　为了迭代集合和数组，增强的for循环提供了一个简单、兼容的语法。有两点值得一提： </p>
		<b>Init表达式</b>
		<br />　　在循环中，初始化表达式只计算一次。这意味着您通常可以移除一个变量声明。在这个例子中，我们必须创建一个整型数组来保存computeNumbers()的结果，以防止每一次循环都重新计算该方法。您可以看到，下面的代码要比上面的代码整洁一些，并且没有泄露变量numbers： <br /><pre class="code">未增强的For：
int sum = 0;
Integer[] numbers = computeNumbers();
for (int i=0; i &lt; numbers.length ; i++)
    sum += numbers[i];
增强后的For： 
int sum = 0;

for ( int number: computeNumbers() )
    sum += number;</pre><b>局限性</b><br />有时需要在迭代期间访问迭代器或下标，看起来增强的for循环应该允许该操作，但事实上不是这样，请看下面的例子： <pre class="code">for (int i=0; i &lt; numbers.length ; i++) {
    if (i != 0) System.out.print(",");
    System.out.print(numbers[i]);
}</pre><p>　　我们希望将数组中的值打印为一个用逗号分隔的清单。我们需要知道目前是否是第一项，以便确定是否应该打印逗号。使用增强的for循环是无法获知这种信息的。我们需要自己保留一个下标或一个布尔值来指示是否经过了第一项。 　　这是另一个例子： </p><pre class="code">for (Iterator&lt;integer&gt; it = n.iterator() ; it.hasNext() ; )
    if (it.next() &lt; 0)
        it.remove();</pre><p>　　在此例中，我们想从整数集合中删除负数项。为此，需要对迭代器调用一个方法，但是当使用增强的for 循环时，迭代器对我们来说是看不到的。因此，我们只能使用Java 5之前版本的迭代方法。 　　顺便说一下，这里需要注意的是，由于Iterator是泛型，所以其声明是Iterator&lt;Integer&gt;。许多人都忘记了这一点而使用了Iterator的原始格式。 </p><b>注释</b><br />　　注释处理是一个很大的话题。因为本文只关注核心的语言特性，所以我们不打算涵盖它所有的可能形式和陷阱。　　我们将讨论内置的注释（SuppressWarnings，Deprecated和Override）以及一般注释处理的局限性。 
<p><b>Suppress Warnings</b><br />　　该注释关闭了类或方法级别的编译器警告。有时候您比编译器更清楚地知道，代码必须使用一个被否决的方法或执行一些无法静态确定是否类型安全的动作，而使用：</p><pre class="code">@SuppressWarnings("deprecation")
public static void selfDestruct() {
    Thread.currentThread().stop();
}</pre><p>　　这可能是内置注释最有用的地方。遗憾的是，1.5.0_04的javac不支持它。但是1.6支持它，并且Sun正在努力将其向后移植到1.5中。 <br />Eclipse 3.1中支持该注释，其他IDE也可能支持它。这允许您把代码彻底地从警告中解脱出来。如果在编译时出现警告，可以确定是您刚刚把它添加进来——以帮助查看那些可能不安全的代码。随着泛型的添加，它使用起来将更趁手。 </p><p><b>Deprecated</b><br />　　遗憾的是，Deprecated没那么有用。它本来旨在替换@deprecated javadoc标签，但是由于它不包含任何字段，所以也就没有方法来建议deprecated类或方法的用户应该使用什么做为替代品。大多数用法都同时需要javadoc标签和这个注释。 
</p><p><b>Override</b><br />　　Override表示，它所注释的方法应该重写超类中具有相同签名的方法： </p><pre class="code">@Override
public int hashCode() {
    ...
}</pre><p>　　看上面的例子，如果没有在hashCode中将“C”大写，在编译时不会出现错误，但是在运行时将无法像期望的那样调用该方法。通过添加Override标签，编译器会提示它是否真正地执行了重写。 <br />　　在超类发生改变的情况中，这也很有帮助。如果向该方法中添加一个新参数，而且方法本身也被重命名了，那么子类将突然不能编译，因为它不再重写超类的任何东西。 </p><p><b>其它注释<br /></b>　　注释在其他场景中非常有用。当不是直接修改行为而是增强行为时，特别是在添加样板代码的情况下，注释在诸如EJB和<a href="http://dev2dev.bea.com/pub/a/2004/10/Anil_WServices.html" target="_blank">Web services</a>这样的框架中运行得非常好。 <br />注释不能用做预处理器。Sun的设计特别预防了完全因为注释而修改类的字节码。这样可以正确地理解该语言的成果，而且IDE之类的工具也可以执行深入的代码分析和重构之类的功能。 <br />注释不是银弹。第一次遇到的时候，人们试图尝试各种技巧。请看下面这个从别人那里获得的建议： </p><pre class="code">public class Foo {
 
    @Property
    private int bar;
 
}</pre><p>　　其思想是为私有字段bar自动创建getter和setter方法。遗憾的是，这个想法有两个失败之处：1)它不能运行，2)它使代码难以阅读和处理。 　　它是无法实现的，因为前面已经提到了，Sun特别阻止了对出现注释的类进行修改。 <br />　　即使是可能的，它也不是一个好主意，因为它使代码可读性差。第一次看到这段代码的人会不知道该注释创建了方法。此外，如果将来您需要在这些方法内部执行一些操作，注释也是没用的。 　　总之，不要试图用注释去做那些常规代码可以完成的事情。 </p><p><strong>枚举 </strong><br />　　enum非常像public static final int声明，后者作为枚举值已经使用了很多年。对int所做的最大也是最明显的改进是类型安全——您不能错误地用枚举的一种类型代替另一种类型，这一点和int不同，所有的int对编译器来说都是一样的。除去极少数例外的情况，通常都应该用enum实例替换全部的枚举风格的int结构。 <br />　　枚举提供了一些附加的特性。EnumMap和EnumSet这两个实用类是专门为枚举优化的标准集合实现。如果知道集合只包含枚举类型，那么应该使用这些专门的集合来代替HashMap或HashSet。 <br />　　大部分情况下，可以使用enum对代码中的所有public static final int做插入替换。它们是可比的，并且可以静态导入，所以对它们的引用看起来是等同的，即使是对于内部类（或内部枚举类型）。注意，比较枚举类型的时候，声明它们的指令表明了它们的顺序值。 <br /><br /><strong>“隐藏的”静态方法 </strong><br />　　两个静态方法出现在所有枚举类型声明中。因为它们是枚举子类上的静态方法，而不是Enum本身的方法，所以它们在java.lang.Enum的javadoc中没有出现。 <br />　　第一个是values()，返回一个枚举类型所有可能值的数组。 <br />　　第二个是valueOf()，为提供的字符串返回一个枚举类型，该枚举类型必须精确地匹配源代码声明。 <br /><strong>方法<br /></strong>　　关于枚举类型，我们最喜欢的一个方面是它可以有方法。过去您可能需要编写一些代码，对public static final int进行转换，把它从数据库类型转换为JDBC URL。而现在则可以让枚举类型本身带一个整理代码的方法。下面就是一个例子，包括DatabaseType枚举类型的抽象方法以及每个枚举实例中提供的实现： <br /></p><pre class="code">  public enum  DatabaseType {
  ORACLE {
  public String getJdbcUrl() {...}
  },
  MYSQL {
  public String getJdbcUrl() {...}
  };
  public abstract String getJdbcUrl();
  }</pre>　　现在枚举类型可以直接提供它的实用方法。例如：<br /><br />DatabaseType dbType = ...;<br />String jdbcURL = dbType.getJdbcUrl();<br /><br />　　要获取URL，必须预先知道该实用方法在哪里。 
<p><br /><strong>可变参数(Vararg)</strong><br />　　正确地使用可变参数确实可以清理一些垃圾代码。典型的例子是一个带有可变的String参数个数的log方法： <br /></p><pre class="code">    Log.log(String code)
    Log.log(String code,  String arg)
    Log.log(String code,  String arg1, String arg2)
    Log.log(String code,  String[] args)</pre>　　当讨论可变参数时，比较有趣的是，如果用新的可变参数替换前四个例子，将是兼容的： <br />Log.log(String code, String... args)<br />　　所有的可变参数都是源兼容的——那就是说，如果重新编译log()方法的所有调用程序，可以直接替换全部的四个方法。然而，如果需要向后的二进制兼容性，那么就需要舍去前三个方法。只有最后那个带一个字符串数组参数的方法等效于可变参数版本，因此可以被可变参数版本替换。 <br /><strong><br />类型强制转换 </strong><br />　　如果希望调用程序了解应该使用哪种类型的参数，那么应该避免用可变参数进行类型强制转换。看下面这个例子，第一项希望是String，第二项希望是Exception： <br /><pre class="code">    Log.log(Object...  objects) {
    String message = (String)objects[0];
    if (objects.length &gt; 1) {
    Exception e = (Exception)objects[1];
    // Do something with the exception
    }
    }</pre>　　方法签名应该如下所示，相应的可变参数分别使用String和Exception声明： <br /><br />Log.log(String message, Exception e, Object... objects) {...}<br /><br />　　不要使用可变参数破坏类型系统。需要强类型化时才可以使用它。对于这个规则，PrintStream.printf()是一个有趣的例外：它提供类型信息作为自己的第一个参数，以便稍后可以接受那些类型。 <br /><strong><br />协变返回 </strong><br />　　协变返回的基本用法是用于在已知一个实现的返回类型比API更具体的时候避免进行类型强制转换。在下面这个例子中，有一个返回Animal对象的Zoo接口。我们的实现返回一个AnimalImpl对象，但是在JDK 1.5之前，要返回一个Animal对象就必须声明。:<br /><pre class="code">    public interface Zoo  {
    public Animal getAnimal();
    }
  public class ZooImpl  implements Zoo {
  public Animal getAnimal(){
  return new AnimalImpl();
  }
  }</pre>　　协变返回的使用替换了三个反模式： 
<p></p><ul><li>直接字段访问。为了规避API限制，一些实现把子类直接暴露为字段： </li></ul><p align="left">ZooImpl._animal</p><ul><li>另一种形式是，在知道实现的实际上是特定的子类的情况下，在调用程序中执行向下转换： </li></ul><p align="left">((AnimalImpl)ZooImpl.getAnimal()).implMethod();</p><ul><li>我看到的最后一种形式是一个具体的方法，该方法用来避免由一个完全不同的签名所引发的问题： </li></ul><p align="left">ZooImpl._getAnimal();<br /><br />　　这三种模式都有它们的问题和局限性。要么是不够整洁，要么就是暴露了不必要的实现细节。 <br /><strong><br />协变 </strong><br />　　协变返回模式就比较整洁、安全并且易于维护，它也不需要类型强制转换或特定的方法或字段： <br />public AnimalImpl getAnimal(){<br />return new AnimalImpl();<br />}<br />　　使用结果： <br />ZooImpl.getAnimal().implMethod();<br /><br /><strong>使用泛型<br />　　</strong>我们将从两个角度来了解泛型：使用泛型和构造泛型。我们不讨论List、Set和Map的显而易见的用法。知道泛型集合是强大的并且应该经常使用就足够了。 <br />　　我们将讨论泛型方法的使用以及编译器推断类型的方法。通常这些都不会出问题，但是当出问题时，错误信息会非常令人费解，所以需要了解如何修复这些问题。 </p><b>泛型方法<br />　　</b>除了泛型类型，Java 5还引入了泛型方法。在这个来自java.util.Collections的例子中，构造了一个单元素列表。新的List的元素类型是根据传入方法的对象的类型来推断的： <pre class="code">static &lt;T&gt; List&lt;T&gt; Collections.singletonList(T o)
示例用法：
public List&lt;Integer&gt; getListOfOne() {
    return Collections.singletonList(1);
}</pre><p>　　在示例用法中，我们传入了一个int。所以方法的返回类型就是List&lt;Integer&gt;。编译器把T推断为Integer。这和泛型类型是不同的，因为您通常不需要显式地指定类型参数。 <br />这也显示了自动装箱和泛型的相互作用。类型参数必须是引用类型：这就是为什么我们得到的是List&lt;Integer&gt;而不是List&lt;int&gt;。 </p><b>不带参数的泛型方法<br />　　</b>emptyList()方法与泛型一起引入，作为java.util.Collections中EMPTY_LIST字段的类型安全置换： <pre class="code">static &lt;T&gt; List&lt;T&gt; Collections.emptyList()
示例用法： 
public List&lt;Integer&gt; getNoIntegers() {
    return Collections.emptyList();
}</pre><p>　　与先前的例子不同，这个方法没有参数，那么编译器如何推断T的类型呢？基本上，它将尝试使用一次参数。如果没有起作用，它再次尝试使用返回或赋值类型。在本例中，返回的是List&lt;Integer&gt;，所以T被推断为Integer。 <br />　　如果在返回语句或赋值语句之外的位置调用泛型方法会怎么样呢？那么编译器将无法执行类型推断的第二次传送。在下面这个例子中，emptyList()是从条件运算符内部调用的： </p><pre class="code">public List&lt;Integer&gt; getNoIntegers() {
    return x ? Collections.emptyList() : null;
}</pre><p>　　因为编译器看不到返回上下文，也不能推断T，所以它放弃并采用Object。您将看到一个错误消息，比如：“无法将List&lt;Object&gt;转换为List&lt;Integer&gt;。” <br />为了修复这个错误，应显式地向方法调用传递类型参数。这样，编译器就不会试图推断类型参数，就可以获得正确的结果： </p><pre class="code">return x ? Collections.&lt;Integer&gt;emptyList() : null;</pre><p>　　这种情况经常发生的另一个地方是在方法调用中。如果一个方法带一个List&lt;String&gt;参数，并且需要为那个参数调用这个传递的emptyList()，那么也需要使用这个语法。 </p><b>集合之外<br /></b>　　这里有三个泛型类型的例子，它们不是集合，而是以一种新颖的方式使用泛型。这三个例子都来自标准的Java库： 
<ul><li>Class&lt;T&gt;<br />Class在类的类型上被参数化了。这就使无需类型强制转换而构造一个newInstance成为可能。 
</li><li>Comparable&lt;T&gt;<br />Comparable被实际的比较类型参数化。这就在compareTo()调用时提供了更强的类型化。例如，String实现Comparable&lt;String&gt;。对除String之外的任何东西调用compareTo()，都会在编译时失败。 
</li><li>Enum&lt;E extends Enum&lt;E&gt;&gt;<br />Enum被枚举类型参数化。一个名为Color的枚举类型将扩展Enum&lt;Color&gt;。getDeclaringClass()方法返回枚举类型的类对象，在这个例子中就是一个Color对象。它与getClass()不同，后者可能返回一个无名类。 </li></ul><b>通配符<br />　　</b>泛型最复杂的部分是对通配符的理解。我们将讨论三种类型的通配符以及它们的用途。 <br />　　首先让我们了解一下数组是如何工作的。可以从一个Integer[]为一个Number[]赋值。如果尝试把一个Float写到Number[]中，那么可以编译，但在运行时会失败，出现一个ArrayStoreException： <pre class="code">Integer[] ia = new Integer[5];
Number[] na = ia;
na[0] = 0.5; // compiles, but fails at runtime
如果试图把该例直接转换成泛型，那么会在编译时失败，因为赋值是不被允许的：
List&lt;Integer&gt; iList = new ArrayList&lt;Integer&gt;();
List&lt;Number&gt; nList = iList; // not allowed
nList.add(0.5);</pre><p>　　如果使用泛型，只要代码在编译时没有出现警告，就不会遇到运行时ClassCastException。 </p><p><b>上限通配符</b><br />　　我们想要的是一个确切元素类型未知的列表，这一点与数组是不同的。 <br />List&lt;Number&gt;是一个列表，其元素类型是具体类型Number。 <br />List&lt;? extends Number&gt;是一个确切元素类型未知的列表。它是Number或其子类型。 </p><p><b>上限<br />　　</b>如果我们更新初始的例子，并赋值给List&lt;? extends Number&gt;，那么现在赋值就会成功了： </p><pre class="code">List&lt;Integer&gt; iList = new ArrayList&lt;Integer&gt;();
List&lt;? extends Number&gt; nList = iList;
Number n = nList.get(0);
nList.add(0.5); // Not allowed</pre><p>　　我们可以从列表中得到Number，因为无论列表的确切元素类型是什么（Float、Integer或Number），我们都可以把它赋值给Number。 <br />　　我们仍然不能把浮点类型插入列表中。这会在编译时失败，因为我们不能证明这是安全的。如果我们想要向列表中添加浮点类型，它将破坏iList的初始类型安全——它只存储Integer。 <br />　　通配符给了我们比数组更多的表达能力。 </p><b>为什么使用通配符<br /></b>　　在下面这个例子中，通配符用于向API的用户隐藏类型信息。在内部，Set被存储为CustomerImpl。而API的用户只知道他们正在获取一个Set，从中可以读取Customer。 <br />此处通配符是必需的，因为无法从Set&lt;CustomerImpl&gt;向Set&lt;Customer&gt;赋值： <pre class="code">public class CustomerFactory {
    private Set&lt;CustomerImpl&gt; _customers;
    public Set&lt;? extends Customer&gt; getCustomers() {
        return _customers;
    }
}</pre><p><b>通配符和协变返回</b><br />　　通配符的另一种常见用法是和协变返回一起使用。与赋值相同的规则可以应用到协变返回上。如果希望在重写的方法中返回一个更具体的泛型类型，声明的方法必须使用通配符： </p><pre class="code">public interface NumberGenerator {
    public List&lt;? extends Number&gt; generate();
}
public class FibonacciGenerator extends NumberGenerator {
    public List&lt;Integer&gt; generate() {
        ...
    }
}</pre><p>　　如果要使用数组，接口可以返回Number[]，而实现可以返回Integer[]。 </p><p><b>下限 </b><br />　　我们所谈的主要是关于上限通配符的。还有一个下限通配符。List&lt;? super Number&gt;是一个确切“元素类型”未知的列表，但是可能是Mnumber，或者Number的超类型。所以它可能是一个List&lt;Number&gt;或一个List&lt;Object&gt;。 <br />　　下限通配符远没有上限通配符那样常见，但是当需要它们的时候，它们就是必需的。 </p><b>下限与上限<br /></b><pre class="code">List&lt;? extends Number&gt; readList = new ArrayList&lt;Integer&gt;();
Number n = readList.get(0);

List&lt;? super Number&gt; writeList = new ArrayList&lt;Object&gt;();
writeList.add(new Integer(5));</pre><p>　　第一个是可以从中读数的列表。 <br />　　第二个是可以向其写数的列表。 </p><p><b>无界通配符</b><br />　　最后，List&lt;?&gt;列表的内容可以是任何类型，而且它与List&lt;? extends Object&gt;几乎相同。可以随时读取Object，但是不能向列表中写入内容。 </p><p><b>公共API中的通配符 </b><br />　　总之，正如前面所说，通配符在向调用程序隐藏实现细节方面是非常重要的，但即使下限通配符看起来是提供只读访问，由于remove(int position)之类的非泛型方法，它们也并非如此。如果您想要一个真正不变的集合，可以使用java.util.Collection上的方法，比如unmodifiableList()。 <br />　　编写API的时候要记得通配符。通常，在传递泛型类型时，应该尝试使用通配符。它使更多的调用程序可以访问API。 <br />　　通过接收List&lt;? extends Number&gt;而不是List&lt;Number&gt;，下面的方法可以由许多不同类型的列表调用： </p><pre class="code">void removeNegatives(List&lt;? extends Number&gt; list);</pre><p><b>构造泛型类型</b><br />　　现在我们将讨论构造自己的泛型类型。我们将展示一些例子，其中通过使用泛型可以提高类型安全性，我们还将讨论一些实现泛型类型时的常见问题。</p><p><b>集合风格(Collection-like)的函数</b><br />　　第一个泛型类的例子是一个集合风格的例子。Pair有两个类型参数，而且字段是类型的实例： </p><pre class="code">public final class Pair&lt;A,B&gt; {
    public final A first;
    public final B second;

    public Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }
}</pre><p>　　这使从方法返回两个项而无需为每个两种类型的组合编写专用的类成为可能。另一种方法是返回Object[]，而这样是类型不安全或者不整洁的。 <br />在下面的用法中，我们从方法返回一个File和一个Boolean。方法的客户端可以直接使用字段而无需类型强制转换： </p><pre class="code">public Pair&lt;File,Boolean&gt; getFileAndWriteStatus(String path){
    // create file and status
    return new Pair&lt;File,Boolean&gt;(file, status);
}

Pair&lt;File,Boolean&gt; result = getFileAndWriteStatus("...");
File f = result.first;
boolean writeable = result.second;</pre><p><b>集合之外 </b><br />　　在下面这个例子中，泛型被用于附加的编译时安全性。通过把DBFactory类参数化为所创建的Peer类型，您实际上是在强制Factory子类返回一个Peer的特定子类型： </p><pre class="code">public abstract class DBFactory&lt;T extends DBPeer&gt; {
    protected abstract T createEmptyPeer();
    public List&lt;T&gt; get(String constraint) {
        List&lt;T&gt; peers = new ArrayList&lt;T&gt;();
        // database magic
        return peers;
    }
}
通过实现DBFactory&lt;Customer&gt;，CustomerFactory必须从createEmptyPeer()返回一个Customer：
public class CustomerFactory extends DBFactory&lt;Customer&gt;{

    public Customer createEmptyPeer() {
        return new Customer();
    }
}</pre><p><b>泛型方法<br /></b>　　不管想要对参数之间还是参数与返回类型之间的泛型类型施加约束，都可以使用泛型方法： <br />　　例如，如果编写的反转函数是在位置上反转，那么可能不需要泛型方法。然而，如果希望反转返回一个新的List，那么可能会希望新List的元素类型与传入的List的类型相同。在这种情况下，就需要一个泛型方法： </p><br />&lt;T&gt; List&lt;T&gt; reverse(List&lt;T&gt; list) 
<p><b>具体化<br /></b>　　当实现一个泛型类时，您可能想要构造一个数组T[]。因为泛型是通过擦除(erasure)实现的，所以这是不允许的。 <br />　　您可以尝试把Object[]强制转换为T[]。但这是不安全的。 </p><b>具体化解决方案<br />　　</b>按照泛型教程的惯例，解决方案使用的是“类型令牌”，通过向构造函数添加一个Class&lt;T&gt;参数，可以强制客户端为类的类型参数提供正确的类对象： <pre class="code">public class ArrayExample&lt;T&gt; {
    private Class&lt;T&gt; clazz;

    public ArrayExample(Class&lt;T&gt; clazz) {
        this.clazz = clazz;
    }

    public T[] getArray(int size) {
        return (T[])Array.newInstance(clazz, size);
    }
}</pre><p>　　为了构造ArrayExample&lt;String&gt;，客户端必须把String.class传递给构造函数，因为String.class的类型是Class&lt;String&gt;。 <br />拥有类对象使构造一个具有正确元素类型的数组成为可能。 <br /><br /><br />引用　<a href="http://dev2dev.bea.com.cn/techdoc/20060105718.html">http://dev2dev.bea.com.cn/techdoc/20060105718.html</a></p><img src ="http://www.blogjava.net/zeroone0/aggbug/39291.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-05 07:51 <a href="http://www.blogjava.net/zeroone0/articles/39291.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>jsp页面中的下载功能实现</title><link>http://www.blogjava.net/zeroone0/articles/38831.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Mon, 03 Apr 2006 02:02:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/38831.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/38831.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/38831.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/38831.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/38831.html</trackback:ping><description><![CDATA[
		<div>&lt;% 
<p>String filename="test.jpg"; <br />String dirName=application.getRealPath("/WEB-INF/upload"); <br />java.io.File ff=null; <br />String dd=dirName+System.getProperties().getProperty("file.separator")+filename; <br />try{ <br />ff=new java.io.File(dd); <br />} <br />catch(Exception e){ <br />e.printStackTrace(); <br />} <br />if (ff!=null&amp;&amp;ff.exists()&amp;&amp;ff.isFile()) <br />{ <br />long filelength = ff.length(); <br />InputStream inStream=new FileInputStream(dd); <br />//设置输出的格式 <br />response.reset(); <br />response.setContentType("application/x-msdownload"); <br />response.setContentLength((int)filelength); <br />response.addHeader("Content-Disposition","attachment; filename=\"" + toUtf8String(filename) + "\""); <br />//循环取出流中的数据 <br />byte[] b = new byte[100]; <br />int len; <br />while((len=inStream.read(b)) &gt;0) <br />response.getOutputStream().write(b,0,len); <br />inStream.close(); </p><p>} <br />%&gt; </p></div>
<img src ="http://www.blogjava.net/zeroone0/aggbug/38831.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-03 10:02 <a href="http://www.blogjava.net/zeroone0/articles/38831.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Web文件的ContentType类型</title><link>http://www.blogjava.net/zeroone0/articles/38830.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Mon, 03 Apr 2006 01:59:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/38830.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/38830.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/38830.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/38830.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/38830.html</trackback:ping><description><![CDATA[
		<div class="itemTitleStyle">Web文件的ContentType类型</div>
		<div class="itemTitleStyle"> </div>
		<div class="itemTitleStyle"> </div>
		<div class="itemTitleStyle">".*"="application/octet-stream"<br />".001"="application/x-001"<br />".301"="application/x-301"<br />".323"="text/h323"<br />".906"="application/x-906"<br />".907"="drawing/907"<br />".a11"="application/x-a11"<br />".acp"="audio/x-mei-aac"<br />".ai"="application/postscript"<br />".aif"="audio/aiff"<br />".aifc"="audio/aiff"<br />".aiff"="audio/aiff"<br />".anv"="application/x-anv"<br />".asa"="text/asa"<br />".asf"="video/x-ms-asf"<br />".asp"="text/asp"<br />".asx"="video/x-ms-asf"<br />".au"="audio/basic"<br />".avi"="video/avi"<br />".awf"="application/vnd.adobe.workflow"<br />".biz"="text/xml"<br />".bmp"="application/x-bmp"<br />".bot"="application/x-bot"<br />".c4t"="application/x-c4t"<br />".c90"="application/x-c90"<br />".cal"="application/x-cals"<br />".cat"="application/vnd.ms-pki.seccat"<br />".cdf"="application/x-netcdf"<br />".cdr"="application/x-cdr"<br />".cel"="application/x-cel"<br />".cer"="application/x-x509-ca-cert"<br />".cg4"="application/x-g4"<br />".cgm"="application/x-cgm"<br />".cit"="application/x-cit"<br />".class"="java/*"<br />".cml"="text/xml"<br />".cmp"="application/x-cmp"<br />".cmx"="application/x-cmx"<br />".cot"="application/x-cot"<br />".crl"="application/pkix-crl"<br />".crt"="application/x-x509-ca-cert"<br />".csi"="application/x-csi"<br />".css"="text/css"<br />".cut"="application/x-cut"<br />".dbf"="application/x-dbf"<br />".dbm"="application/x-dbm"<br />".dbx"="application/x-dbx"<br />".dcd"="text/xml"<br />".dcx"="application/x-dcx"<br />".der"="application/x-x509-ca-cert"<br />".dgn"="application/x-dgn"<br />".dib"="application/x-dib"<br />".dll"="<span class="searchword">application/x-msdownload</span>"<br />".doc"="application/msword"<br />".dot"="application/msword"<br />".drw"="application/x-drw"<br />".dtd"="text/xml"<br />".dwf"="Model/vnd.dwf"<br />".dwf"="application/x-dwf"<br />".dwg"="application/x-dwg"<br />".dxb"="application/x-dxb"<br />".dxf"="application/x-dxf"<br />".edn"="application/vnd.adobe.edn"<br />".emf"="application/x-emf"<br />".eml"="message/rfc822"<br />".ent"="text/xml"<br />".epi"="application/x-epi"<br />".eps"="application/x-ps"<br />".eps"="application/postscript"<br />".etd"="application/x-ebx"<br />".exe"="<span class="searchword">application/x-msdownload</span>"<br />".fax"="image/fax"<br />".fdf"="application/vnd.fdf"<br />".fif"="application/fractals"<br />".fo"="text/xml"<br />".frm"="application/x-frm"<br />".g4"="application/x-g4"<br />".gbr"="application/x-gbr"<br />".gcd"="application/x-gcd"<br />".gif"="image/gif"<br />".gl2"="application/x-gl2"<br />".gp4"="application/x-gp4"<br />".hgl"="application/x-hgl"<br />".hmr"="application/x-hmr"<br />".hpg"="application/x-hpgl"<br />".hpl"="application/x-hpl"<br />".hqx"="application/mac-binhex40"<br />".hrf"="application/x-hrf"<br />".hta"="application/hta"<br />".htc"="text/x-component"<br />".htm"="text/html"<br />".html"="text/html"<br />".htt"="text/webviewhtml"<br />".htx"="text/html"<br />".icb"="application/x-icb"<br />".ico"="image/x-icon"<br />".ico"="application/x-ico"<br />".iff"="application/x-iff"<br />".ig4"="application/x-g4"<br />".igs"="application/x-igs"<br />".iii"="application/x-iphone"<br />".img"="application/x-img"<br />".ins"="application/x-internet-signup"<br />".isp"="application/x-internet-signup"<br />".IVF"="video/x-ivf"<br />".java"="java/*"<br />".jfif"="image/jpeg"<br />".jpe"="image/jpeg"<br />".jpe"="application/x-jpe"<br />".jpeg"="image/jpeg"<br />".jpg"="image/jpeg"<br />".jpg"="application/x-jpg"<br />".js"="application/x-javascript"<br />".jsp"="text/html"<br />".la1"="audio/x-liquid-file"<br />".lar"="application/x-laplayer-reg"<br />".latex"="application/x-latex"<br />".lavs"="audio/x-liquid-secure"<br />".lbm"="application/x-lbm"<br />".lmsff"="audio/x-la-lms"<br />".ls"="application/x-javascript"<br />".ltr"="application/x-ltr"<br />".m1v"="video/x-mpeg"<br />".m2v"="video/x-mpeg"<br />".m3u"="audio/mpegurl"<br />".m4e"="video/mpeg4"<br />".mac"="application/x-mac"<br />".man"="application/x-troff-man"<br />".math"="text/xml"<br />".mdb"="application/msaccess"<br />".mdb"="application/x-mdb"<br />".mfp"="application/x-shockwave-flash"<br />".mht"="message/rfc822"<br />".mhtml"="message/rfc822"<br />".mi"="application/x-mi"<br />".mid"="audio/mid"<br />".midi"="audio/mid"<br />".mil"="application/x-mil"<br />".mml"="text/xml"<br />".mnd"="audio/x-musicnet-download"<br />".mns"="audio/x-musicnet-stream"<br />".mocha"="application/x-javascript"<br />".movie"="video/x-sgi-movie"<br />".mp1"="audio/mp1"<br />".mp2"="audio/mp2"<br />".mp2v"="video/mpeg"<br />".mp3"="audio/mp3"<br />".mp4"="video/mpeg4"<br />".mpa"="video/x-mpg"<br />".mpd"="application/vnd.ms-project"<br />".mpe"="video/x-mpeg"<br />".mpeg"="video/mpg"<br />".mpg"="video/mpg"<br />".mpga"="audio/rn-mpeg"<br />".mpp"="application/vnd.ms-project"<br />".mps"="video/x-mpeg"<br />".mpt"="application/vnd.ms-project"<br />".mpv"="video/mpg"<br />".mpv2"="video/mpeg"<br />".mpw"="application/vnd.ms-project"<br />".mpx"="application/vnd.ms-project"<br />".mtx"="text/xml"<br />".mxp"="application/x-mmxp"<br />".net"="image/pnetvue"<br />".nrf"="application/x-nrf"<br />".nws"="message/rfc822"<br />".odc"="text/x-ms-odc"<br />".out"="application/x-out"<br />".p10"="application/pkcs10"<br />".p12"="application/x-pkcs12"<br />".p7b"="application/x-pkcs7-certificates"<br />".p7c"="application/pkcs7-mime"<br />".p7m"="application/pkcs7-mime"<br />".p7r"="application/x-pkcs7-certreqresp"<br />".p7s"="application/pkcs7-signature"<br />".pc5"="application/x-pc5"<br />".pci"="application/x-pci"<br />".pcl"="application/x-pcl"<br />".pcx"="application/x-pcx"<br />".pdf"="application/pdf"<br />".pdf"="application/pdf"<br />".pdx"="application/vnd.adobe.pdx"<br />".pfx"="application/x-pkcs12"<br />".pgl"="application/x-pgl"<br />".pic"="application/x-pic"<br />".pko"="application/vnd.ms-pki.pko"<br />".pl"="application/x-perl"<br />".plg"="text/html"<br />".pls"="audio/scpls"<br />".plt"="application/x-plt"<br />".png"="image/png"<br />".png"="application/x-png"<br />".pot"="application/vnd.ms-powerpoint"<br />".ppa"="application/vnd.ms-powerpoint"<br />".ppm"="application/x-ppm"<br />".pps"="application/vnd.ms-powerpoint"<br />".ppt"="application/vnd.ms-powerpoint"<br />".ppt"="application/x-ppt"<br />".pr"="application/x-pr"<br />".prf"="application/pics-rules"<br />".prn"="application/x-prn"<br />".prt"="application/x-prt"<br />".ps"="application/x-ps"<br />".ps"="application/postscript"<br />".ptn"="application/x-ptn"<br />".pwz"="application/vnd.ms-powerpoint"<br />".r3t"="text/vnd.rn-realtext3d"<br />".ra"="audio/vnd.rn-realaudio"<br />".ram"="audio/x-pn-realaudio"<br />".ras"="application/x-ras"<br />".rat"="application/rat-file"<br />".rdf"="text/xml"<br />".rec"="application/vnd.rn-recording"<br />".red"="application/x-red"<br />".rgb"="application/x-rgb"<br />".rjs"="application/vnd.rn-realsystem-rjs"<br />".rjt"="application/vnd.rn-realsystem-rjt"<br />".rlc"="application/x-rlc"<br />".rle"="application/x-rle"<br />".rm"="application/vnd.rn-realmedia"<br />".rmf"="application/vnd.adobe.rmf"<br />".rmi"="audio/mid"<br />".rmj"="application/vnd.rn-realsystem-rmj"<br />".rmm"="audio/x-pn-realaudio"<br />".rmp"="application/vnd.rn-rn_music_package"<br />".rms"="application/vnd.rn-realmedia-secure"<br />".rmvb"="application/vnd.rn-realmedia-vbr"<br />".rmx"="application/vnd.rn-realsystem-rmx"<br />".rnx"="application/vnd.rn-realplayer"<br />".rp"="image/vnd.rn-realpix"<br />".rpm"="audio/x-pn-realaudio-plugin"<br />".rsml"="application/vnd.rn-rsml"<br />".rt"="text/vnd.rn-realtext"<br />".rtf"="application/msword"<br />".rtf"="application/x-rtf"<br />".rv"="video/vnd.rn-realvideo"<br />".sam"="application/x-sam"<br />".sat"="application/x-sat"<br />".sdp"="application/sdp"<br />".sdw"="application/x-sdw"<br />".sit"="application/x-stuffit"<br />".slb"="application/x-slb"<br />".sld"="application/x-sld"<br />".slk"="drawing/x-slk"<br />".smi"="application/smil"<br />".smil"="application/smil"<br />".smk"="application/x-smk"<br />".snd"="audio/basic"<br />".sol"="text/plain"<br />".sor"="text/plain"<br />".spc"="application/x-pkcs7-certificates"<br />".spl"="application/futuresplash"<br />".spp"="text/xml"<br />".ssm"="application/streamingmedia"<br />".sst"="application/vnd.ms-pki.certstore"<br />".stl"="application/vnd.ms-pki.stl"<br />".stm"="text/html"<br />".sty"="application/x-sty"<br />".svg"="text/xml"<br />".swf"="application/x-shockwave-flash"<br />".tdf"="application/x-tdf"<br />".tg4"="application/x-tg4"<br />".tga"="application/x-tga"<br />".tif"="image/tiff"<br />".tif"="application/x-tif"<br />".tiff"="image/tiff"<br />".tld"="text/xml"<br />".top"="drawing/x-top"<br />".torrent"="application/x-bittorrent"<br />".tsd"="text/xml"<br />".txt"="text/plain"<br />".uin"="application/x-icq"<br />".uls"="text/iuls"<br />".vcf"="text/x-vcard"<br />".vda"="application/x-vda"<br />".vdx"="application/vnd.visio"<br />".vml"="text/xml"<br />".vpg"="application/x-vpeg005"<br />".vsd"="application/vnd.visio"<br />".vsd"="application/x-vsd"<br />".vss"="application/vnd.visio"<br />".vst"="application/vnd.visio"<br />".vst"="application/x-vst"<br />".vsw"="application/vnd.visio"<br />".vsx"="application/vnd.visio"<br />".vtx"="application/vnd.visio"<br />".vxml"="text/xml"<br />".wav"="audio/wav"<br />".wax"="audio/x-ms-wax"<br />".wb1"="application/x-wb1"<br />".wb2"="application/x-wb2"<br />".wb3"="application/x-wb3"<br />".wbmp"="image/vnd.wap.wbmp"<br />".wiz"="application/msword"<br />".wk3"="application/x-wk3"<br />".wk4"="application/x-wk4"<br />".wkq"="application/x-wkq"<br />".wks"="application/x-wks"<br />".wm"="video/x-ms-wm"<br />".wma"="audio/x-ms-wma"<br />".wmd"="application/x-ms-wmd"<br />".wmf"="application/x-wmf"<br />".wml"="text/vnd.wap.wml"<br />".wmv"="video/x-ms-wmv"<br />".wmx"="video/x-ms-wmx"<br />".wmz"="application/x-ms-wmz"<br />".wp6"="application/x-wp6"<br />".wpd"="application/x-wpd"<br />".wpg"="application/x-wpg"<br />".wpl"="application/vnd.ms-wpl"<br />".wq1"="application/x-wq1"<br />".wr1"="application/x-wr1"<br />".wri"="application/x-wri"<br />".wrk"="application/x-wrk"<br />".ws"="application/x-ws"<br />".ws2"="application/x-ws"<br />".wsc"="text/scriptlet"<br />".wsdl"="text/xml"<br />".wvx"="video/x-ms-wvx"<br />".xdp"="application/vnd.adobe.xdp"<br />".xdr"="text/xml"<br />".xfd"="application/vnd.adobe.xfd"<br />".xfdf"="application/vnd.adobe.xfdf"<br />".xhtml"="text/html"<br />".xls"="application/vnd.ms-excel"<br />".xls"="application/x-xls"<br />".xlw"="application/x-xlw"<br />".xml"="text/xml"<br />".xpl"="audio/scpls"<br />".xq"="text/xml"<br />".xql"="text/xml"<br />".xquery"="text/xml"<br />".xsd"="text/xml"<br />".xsl"="text/xml"<br />".xslt"="text/xml"<br />".xwd"="application/x-xwd"<br />".x_b"="application/x-x_b"<br />".x_t"="application/x-x_t"<br /></div>
<img src ="http://www.blogjava.net/zeroone0/aggbug/38830.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-03 09:59 <a href="http://www.blogjava.net/zeroone0/articles/38830.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java加密技术</title><link>http://www.blogjava.net/zeroone0/articles/38829.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Mon, 03 Apr 2006 01:55:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/38829.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/38829.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/38829.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/38829.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/38829.html</trackback:ping><description><![CDATA[
		<div>
				<p>
						<strong>第1章 基础知识</strong>
				</p>
				<p>1.1. 单钥密码体制 <br />单钥密码体制是一种传统的加密算法，是指信息的发送方和接收方共同使用同一把密钥进行加解密。</p>
				<p>通常,使用的加密算法 比较简便高效,密钥简短，加解密速度快，破译极其困难。但是加密的安全性依靠密钥保管的安全性,在公开的计算机网络上安全地传送和保管密钥是一个严峻的问题，并且如果在多用户的情况下密钥的保管安全性也是一个问题。</p>
				<p>单钥密码体制的代表是美国的DES</p>
				<p>1.2. 消息摘要 <br />一个消息摘要就是一个数据块的数字指纹。即对一个任意长度的一个数据块进行计算，产生一个唯一指印（对于SHA1是产生一个20字节的二进制数组）。</p>
				<p>消息摘要有两个基本属性： </p>
				<p>两个不同的报文难以生成相同的摘要 <br />难以对指定的摘要生成一个报文，而由该报文反推算出该指定的摘要<br />代表：美国国家标准技术研究所的SHA1和麻省理工学院Ronald Rivest提出的MD5</p>
				<p>1.3. Diffie-Hellman密钥一致协议 <br />密钥一致协议是由公开密钥密码体制的奠基人Diffie和Hellman所提出的一种思想。</p>
				<p>先决条件,允许两名用户在公开媒体上交换信息以生成"一致"的,可以共享的密钥</p>
				<p>代表：指数密钥一致协议(Exponential Key Agreement Protocol)</p>
				<p>1.4. 非对称算法与公钥体系 <br />1976年，Dittie和Hellman为解决密钥管理问题，在他们的奠基性的工作"密码学的新方向"一文中，提出一种密钥交换协议，允许在不安全的媒体上通过通讯双方交换信息，安全地传送秘密密钥。在此新思想的基础上，很快出现了非对称密钥密码体制，即公钥密码体制。在公钥体制中，加密密钥不同于解密密钥，加密密钥公之于众，谁都可以使用；解密密钥只有解密人自己知道。它们分别称为公开密钥（Public key）和秘密密钥（Private key）。 </p>
				<p>迄今为止的所有公钥密码体系中，RSA系统是最著名、最多使用的一种。RSA公开密钥密码系统是由R.Rivest、A.Shamir和L.Adleman俊教授于1977年提出的。RSA的取名就是来自于这三位发明者的姓的第一个字母</p>
				<p>1.5. 数字签名 <br />所谓数字签名就是信息发送者用其私钥对从所传报文中提取出的特征数据（或称数字指纹）进行RSA算法操作，以保证发信人无法抵赖曾发过该信息（即不可抵赖性），同时也确保信息报文在经签名后末被篡改（即完整性）。当信息接收者收到报文后，就可以用发送者的公钥对数字签名进行验证。　 </p>
				<p>在数字签名中有重要作用的数字指纹是通过一类特殊的散列函数（HASH函数）生成的，对这些HASH函数的特殊要求是： </p>
				<p>接受的输入报文数据没有长度限制； <br />对任何输入报文数据生成固定长度的摘要（数字指纹）输出 <br />从报文能方便地算出摘要； <br />难以对指定的摘要生成一个报文，而由该报文反推算出该指定的摘要； <br />两个不同的报文难以生成相同的摘要</p>
				<p>代表：DSA</p>
				<p>
						<strong>第2章 在JAVA中的实现</strong>
				</p>
				<p>2.1. 相关 <br />Diffie-Hellman密钥一致协议和DES程序需要JCE工具库的支持,可以到 <a href="http://java.sun.com/security/index.html"><font color="#000033">http://java.sun.com/security/index.html</font></a> 下载JCE,并进行安装。简易安装把 jce1.2.1\lib 下的所有内容复制到 %java_home%\lib\ext下,如果没有ext目录自行建立,再把jce1_2_1.jar和sunjce_provider.jar添加到CLASSPATH内,更详细说明请看相应用户手册</p>
				<p>2.2. 消息摘要MD5和SHA的使用 <br />使用方法:</p>
				<p>首先用生成一个MessageDigest类,确定计算方法</p>
				<p>java.security.MessageDigest alga=java.security.MessageDigest.getInstance("SHA-1");</p>
				<p>添加要进行计算摘要的信息</p>
				<p>alga.update(myinfo.getBytes());</p>
				<p>计算出摘要</p>
				<p>byte[] digesta=alga.digest();</p>
				<p>发送给其他人你的信息和摘要</p>
				<p>其他人用相同的方法初始化,添加信息,最后进行比较摘要是否相同</p>
				<p>algb.isEqual(digesta,algb.digest())</p>
				<p>相关AIP</p>
				<p>java.security.MessageDigest 类</p>
				<p>static getInstance(String algorithm)</p>
				<p>返回一个MessageDigest对象,它实现指定的算法</p>
				<p>参数:算法名,如 SHA-1 或MD5</p>
				<p>void update (byte input)</p>
				<p>void update (byte[] input)</p>
				<p>void update(byte[] input, int offset, int len)</p>
				<p>添加要进行计算摘要的信息</p>
				<p>byte[] digest()</p>
				<p>完成计算,返回计算得到的摘要(对于MD5是16位,SHA是20位)</p>
				<p>void reset()</p>
				<p>复位</p>
				<p>static boolean isEqual(byte[] digesta, byte[] digestb)</p>
				<p>比效两个摘要是否相同</p>
				<p>代码： <br />import java.security.*;<br />public class myDigest {<br />public static void main(String[] args) {</p>
				<p>myDigest my=new myDigest();<br />my.testDigest();</p>
				<p>}<br />public void testDigest()<br />{<br />try {<br />String myinfo="我的测试信息";</p>
				<p>//java.security.MessageDigest alg=java.security.MessageDigest.getInstance("MD5");<br />java.security.MessageDigest alga=java.security.MessageDigest.getInstance("SHA-1");<br />alga.update(myinfo.getBytes());<br />byte[] digesta=alga.digest();<br />System.out.println("本信息摘要是:"+byte2hex(digesta));<br />//通过某中方式传给其他人你的信息(myinfo)和摘要(digesta) 对方可以判断是否更改或传输正常<br />java.security.MessageDigest algb=java.security.MessageDigest.getInstance("SHA-1");<br />algb.update(myinfo.getBytes());<br />if (algb.isEqual(digesta,algb.digest())) {<br />System.out.println("信息检查正常");<br />}<br />else<br />{<br />System.out.println("摘要不相同");<br />}</p>
				<p>}<br />catch (java.security.NoSuchAlgorithmException ex) {<br />System.out.println("非法摘要算法");<br />}</p>
				<p>}<br />public String byte2hex(byte[] b) //二行制转字符串<br />{<br />String hs="";<br />String stmp="";<br />for (int n=0;n {<br />stmp=(java.lang.Integer.toHexString(b[n] &amp; 0XFF));<br />if (stmp.length()==1) hs=hs+"0"+stmp;<br />else hs=hs+stmp;<br />if (n }<br />return hs.toUpperCase();<br />}</p>
				<p>}</p>
				<p> </p>
				<p>2.3. 数字签名DSA</p>
				<p>对于一个用户来讲首先要生成他的密钥对,并且分别保存 <br />生成一个KeyPairGenerator实例 <br />java.security.KeyPairGenerator keygen=java.security.KeyPairGenerator.getInstance("DSA");<br />如果设定随机产生器就用如相代码初始化<br />SecureRandom secrand=new SecureRandom(); </p>
				<p>secrand.setSeed("tttt".getBytes()); //初始化随机产生器<br />keygen.initialize(512,secrand); //初始化密钥生成器<br />否则<br />keygen.initialize(512);<br />生成密钥公钥pubkey和私钥prikey<br />KeyPair keys=keygen.generateKeyPair(); //生成密钥组<br />PublicKey pubkey=keys.getPublic();<br />PrivateKey prikey=keys.getPrivate();<br />分别保存在myprikey.dat和mypubkey.dat中,以便下次不在生成<br />(生成密钥对的时间比较长<br />java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myprikey.dat"));<br />out.writeObject(prikey);<br />out.close();<br />out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("mypubkey.dat"));<br />out.writeObject(pubkey);<br />out.close();</p>
				<p> </p>
				<p>
						<br />用他私人密钥(prikey)对他所确认的信息(info)进行数字签名产生一个签名数组 <br />从文件中读入私人密钥(prikey) <br />java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("myprikey.dat"));<br />PrivateKey myprikey=(PrivateKey)in.readObject();<br />in.close();<br />初始一个Signature对象,并用私钥对信息签名<br />java.security.Signature signet=java.security.Signature.getInstance("DSA");<br />signet.initSign(myprikey);<br />signet.update(myinfo.getBytes());<br />byte[] signed=signet.sign();<br />把信息和签名保存在一个文件中(myinfo.dat)<br />java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myinfo.dat"));<br />out.writeObject(myinfo);<br />out.writeObject(signed);<br />out.close();<br />把他的公钥的信息及签名发给其它用户</p>
				<p> </p>
				<p>
						<br />其他用户用他的公共密钥(pubkey)和签名(signed)和信息(info)进行验证是否由他签名的信息 <br />读入公钥<br />java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("mypubkey.dat")); <br />PublicKey pubkey=(PublicKey)in.readObject(); <br />in.close();</p>
				<p>读入签名和信息<br />in=new java.io.ObjectInputStream(new java.io.FileInputStream("myinfo.dat")); <br />String info=(String)in.readObject(); <br />byte[] signed=(byte[])in.readObject(); <br />in.close();</p>
				<p>初始一个Signature对象,并用公钥和签名进行验证<br />java.security.Signature signetcheck=java.security.Signature.getInstance("DSA"); <br />signetcheck.initVerify(pubkey); <br />signetcheck.update(info.getBytes()); <br />if (signetcheck.verify(signed)) { System.out.println("签名正常");}</p>
				<p>对于密钥的保存本文是用对象流的方式保存和传送的,也可可以用编码的方式保存.注意要<br />import java.security.spec.* <br />import java.security.*</p>
				<p>具休说明如下</p>
				<p>public key是用X.509编码的,例码如下: <br />byte[] bobEncodedPubKey=mypublic.getEncoded(); //生成编码<br />//传送二进制编码<br />//以下代码转换编码为相应key对象<br />X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);<br />KeyFactory keyFactory = KeyFactory.getInstance("DSA");<br />PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);</p>
				<p> </p>
				<p>对于Private key是用PKCS#8编码,例码如下: <br />byte[] bPKCS=myprikey.getEncoded();<br />//传送二进制编码<br />//以下代码转换编码为相应key对象<br />PKCS8EncodedKeySpec priPKCS8=new PKCS8EncodedKeySpec(bPKCS);<br />KeyFactory keyf=KeyFactory.getInstance("DSA");<br />PrivateKey otherprikey=keyf.generatePrivate(priPKCS8);</p>
				<p> </p>
				<p>
						<br />常用API <br />java.security.KeyPairGenerator 密钥生成器类 <br />public static KeyPairGenerator getInstance(String algorithm) throws NoSuchAlgorithmException <br />以指定的算法返回一个KeyPairGenerator 对象 <br />参数: algorithm 算法名.如:"DSA","RSA"</p>
				<p>public void initialize(int keysize)</p>
				<p>
						<br />以指定的长度初始化KeyPairGenerator对象,如果没有初始化系统以1024长度默认设置</p>
				<p>
						<br />参数:keysize 算法位长.其范围必须在 512 到 1024 之间，且必须为 64 的倍数</p>
				<p>public void initialize(int keysize, SecureRandom random)<br />以指定的长度初始化和随机发生器初始化KeyPairGenerator对象<br />参数:keysize 算法位长.其范围必须在 512 到 1024 之间，且必须为 64 的倍数<br />random 一个随机位的来源(对于initialize(int keysize)使用了默认随机器</p>
				<p>public abstract KeyPair generateKeyPair()<br />产生新密钥对</p>
				<p>java.security.KeyPair 密钥对类<br />public PrivateKey getPrivate()<br />返回私钥</p>
				<p>public PublicKey getPublic()<br />返回公钥</p>
				<p>java.security.Signature 签名类<br />public static Signature getInstance(String algorithm) throws NoSuchAlgorithmException<br />返回一个指定算法的Signature对象<br />参数 algorithm 如:"DSA"</p>
				<p>public final void initSign(PrivateKey privateKey)<br />throws InvalidKeyException<br />用指定的私钥初始化<br />参数rivateKey 所进行签名时用的私钥</p>
				<p>public final void update(byte data)<br />throws SignatureException<br />public final void update(byte[] data)<br />throws SignatureException<br />public final void update(byte[] data, int off, int len)<br />throws SignatureException </p>
				<p>添加要签名的信息</p>
				<p>public final byte[] sign()<br />throws SignatureException<br />返回签名的数组,前提是initSign和update</p>
				<p>public final void initVerify(PublicKey publicKey)<br />throws InvalidKeyException<br />用指定的公钥初始化<br />参数ublicKey 验证时用的公钥</p>
				<p>public final boolean verify(byte[] signature)<br />throws SignatureException<br />验证签名是否有效,前提是已经initVerify初始化<br />参数: signature 签名数组 <br />*/<br />import java.security.*;<br />import java.security.spec.*;<br />public class testdsa {<br />public static void main(String[] args) throws java.security.NoSuchAlgorithmException,java.lang.Exception {<br />testdsa my=new testdsa();<br />my.run();<br />}<br />public void run()<br />{</p>
				<p>//数字签名生成密钥<br />//第一步生成密钥对,如果已经生成过,本过程就可以跳过,对用户来讲myprikey.dat要保存在本地<br />//而mypubkey.dat给发布给其它用户<br />if ((new java.io.File("myprikey.dat")).exists()==false) {<br />if (generatekey()==false) {<br />System.out.println("生成密钥对败");<br />return;<br />};<br />}<br />//第二步,此用户<br />//从文件中读入私钥,对一个字符串进行签名后保存在一个文件(myinfo.dat)中<br />//并且再把myinfo.dat发送出去<br />//为了方便数字签名也放进了myifno.dat文件中,当然也可分别发送<br />try {<br />java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("myprikey.dat"));<br />PrivateKey myprikey=(PrivateKey)in.readObject();<br />in.close();</p>
				<p>// java.security.spec.X509EncodedKeySpec pubX509=new java.security.spec.X509EncodedKeySpec(bX509);</p>
				<p>//java.security.spec.X509EncodedKeySpec pubkeyEncode=java.security.spec.X509EncodedKeySpec<br />String myinfo="这是我的信息"; //要签名的信息<br />//用私钥对信息生成数字签名<br />java.security.Signature signet=java.security.Signature.getInstance("DSA");<br />signet.initSign(myprikey);<br />signet.update(myinfo.getBytes());<br />byte[] signed=signet.sign(); //对信息的数字签名<br />System.out.println("signed(签名内容)="+byte2hex(signed));<br />//把信息和数字签名保存在一个文件中<br />java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myinfo.dat"));<br />out.writeObject(myinfo);<br />out.writeObject(signed);<br />out.close();<br />System.out.println("签名并生成文件成功");<br />}<br />catch (java.lang.Exception e) {<br />e.printStackTrace();<br />System.out.println("签名并生成文件失败");<br />};</p>
				<p>//第三步<br />//其他人通过公共方式得到此户的公钥和文件<br />//其他人用此户的公钥,对文件进行检查,如果成功说明是此用户发布的信息.<br />//<br />try {</p>
				<p>java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("mypubkey.dat"));<br />PublicKey pubkey=(PublicKey)in.readObject();<br />in.close();<br />System.out.println(pubkey.getFormat());</p>
				<p>in=new java.io.ObjectInputStream(new java.io.FileInputStream("myinfo.dat"));<br />String info=(String)in.readObject();<br />byte[] signed=(byte[])in.readObject();<br />in.close();</p>
				<p>java.security.Signature signetcheck=java.security.Signature.getInstance("DSA");<br />signetcheck.initVerify(pubkey);<br />signetcheck.update(info.getBytes());<br />if (signetcheck.verify(signed)) {<br />System.out.println("info="+info);<br />System.out.println("签名正常");<br />}<br />else System.out.println("非签名正常");<br />}<br />catch (java.lang.Exception e) {e.printStackTrace();};</p>
				<p>
						<br />}</p>
				<p>//生成一对文件myprikey.dat和mypubkey.dat---私钥和公钥,<br />//公钥要用户发送(文件,网络等方法)给其它用户,私钥保存在本地<br />public boolean generatekey()<br />{<br />try {<br />java.security.KeyPairGenerator keygen=java.security.KeyPairGenerator.getInstance("DSA");<br />// SecureRandom secrand=new SecureRandom();<br />// secrand.setSeed("tttt".getBytes()); //初始化随机产生器<br />// keygen.initialize(576,secrand); //初始化密钥生成器<br />keygen.initialize(512);<br />KeyPair keys=keygen.genKeyPair();<br />// KeyPair keys=keygen.generateKeyPair(); //生成密钥组<br />PublicKey pubkey=keys.getPublic();<br />PrivateKey prikey=keys.getPrivate();</p>
				<p>java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myprikey.dat"));<br />out.writeObject(prikey);<br />out.close();<br />System.out.println("写入对象 prikeys ok");<br />out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("mypubkey.dat"));<br />out.writeObject(pubkey);<br />out.close();<br />System.out.println("写入对象 pubkeys ok");<br />System.out.println("生成密钥对成功");<br />return true;<br />}<br />catch (java.lang.Exception e) {<br />e.printStackTrace();<br />System.out.println("生成密钥对失败");</p>
				<p>return false;<br />};</p>
				<p>}</p>
				<p>public String byte2hex(byte[] b)<br />{<br />String hs="";<br />String stmp="";<br />for (int n=0;n {<br />stmp=(java.lang.Integer.toHexString(b[n] &amp; 0XFF));<br />if (stmp.length()==1) hs=hs+"0"+stmp;<br />else hs=hs+stmp;<br />if (n }<br />return hs.toUpperCase();<br />}</p>
				<p>}</p>
		</div>
		<div>
				<div>
						<p>2.4. DESede/DES对称算法 <br />首先生成密钥,并保存(这里并没的保存的代码,可参考DSA中的方法)</p>
						<p>KeyGenerator keygen = KeyGenerator.getInstance(Algorithm);</p>
						<p>SecretKey deskey = keygen.generateKey();</p>
						<p>用密钥加密明文(myinfo),生成密文(cipherByte)</p>
						<p>Cipher c1 = Cipher.getInstance(Algorithm);</p>
						<p>c1.init(Cipher.ENCRYPT_MODE,deskey);</p>
						<p>byte[] cipherByte=c1.doFinal(myinfo.getBytes());</p>
						<p>传送密文和密钥,本文没有相应代码可参考DSA</p>
						<p>.............</p>
						<p>用密钥解密密文</p>
						<p>c1 = Cipher.getInstance(Algorithm);</p>
						<p>c1.init(Cipher.DECRYPT_MODE,deskey);</p>
						<p>byte[] clearByte=c1.doFinal(cipherByte);</p>
						<p>相对来说对称密钥的使用是很简单的,对于JCE来讲支技DES,DESede,Blowfish三种加密术</p>
						<p>对于密钥的保存各传送可使用对象流或者用二进制编码,相关参考代码如下 <br />SecretKey deskey = keygen.generateKey();<br />byte[] desEncode=deskey.getEncoded();<br />javax.crypto.spec.SecretKeySpec destmp=new javax.crypto.spec.SecretKeySpec(desEncode,Algorithm);<br />SecretKey mydeskey=destmp;</p>
						<p> </p>
						<p>相关API</p>
						<p>KeyGenerator 在DSA中已经说明,在添加JCE后在instance进可以如下参数</p>
						<p>DES,DESede,Blowfish,HmacMD5,HmacSHA1</p>
						<p>javax.crypto.Cipher 加/解密器 public static final Cipher getInstance(java.lang.String transformation)<br />throws java.security.NoSuchAlgorithmException,<br />NoSuchPaddingException<br />返回一个指定方法的Cipher对象</p>
						<p>参数:transformation 方法名(可用 DES,DESede,Blowfish)</p>
						<p>public final void init(int opmode, java.security.Key key)<br />throws java.security.InvalidKeyException</p>
						<p>用指定的密钥和模式初始化Cipher对象</p>
						<p>参数pmode 方式(ENCRYPT_MODE, DECRYPT_MODE, WRAP_MODE,UNWRAP_MODE)</p>
						<p>key 密钥</p>
						<p>
								<br />public final byte[] doFinal(byte[] input)<br />throws java.lang.IllegalStateException,<br />IllegalBlockSizeException,<br />BadPaddingException</p>
						<p> </p>
						<p>
								<br />对input内的串,进行编码处理,返回处理后二进制串,是返回解密文还是加解文由init时的opmode决定</p>
						<p>注意:本方法的执行前如果有update,是对updat和本次input全部处理,否则是本inout的内容</p>
						<p>/*<br />安全程序 DESede/DES测试<br />*/<br />import java.security.*;<br />import javax.crypto.*;<br />public class testdes {<br />public static void main(String[] args){<br />testdes my=new testdes();<br />my.run();<br />}<br />public void run() {<br />//添加新安全算法,如果用JCE就要把它添加进去<br />Security.addProvider(new com.sun.crypto.provider.SunJCE());<br />String Algorithm="DES"; //定义 加密算法,可用 DES,DESede,Blowfish<br />String myinfo="要加密的信息";<br />try {<br />//生成密钥<br />KeyGenerator keygen = KeyGenerator.getInstance(Algorithm);<br />SecretKey deskey = keygen.generateKey();</p>
						<p>//加密<br />System.out.println("加密前的二进串:"+byte2hex(myinfo.getBytes()));<br />System.out.println("加密前的信息:"+myinfo);<br />Cipher c1 = Cipher.getInstance(Algorithm);<br />c1.init(Cipher.ENCRYPT_MODE,deskey);<br />byte[] cipherByte=c1.doFinal(myinfo.getBytes());<br />System.out.println("加密后的二进串:"+byte2hex(cipherByte));<br />//解密<br />c1 = Cipher.getInstance(Algorithm);<br />c1.init(Cipher.DECRYPT_MODE,deskey);<br />byte[] clearByte=c1.doFinal(cipherByte);<br />System.out.println("解密后的二进串:"+byte2hex(clearByte));<br />System.out.println("解密后的信息:"+(new String(clearByte)));</p>
						<p>}<br />catch (java.security.NoSuchAlgorithmException e1) {e1.printStackTrace();}<br />catch (javax.crypto.NoSuchPaddingException e2) {e2.printStackTrace();}<br />catch (java.lang.Exception e3) {e3.printStackTrace();}<br />}<br />public String byte2hex(byte[] b) //二行制转字符串<br />{<br />String hs="";<br />String stmp="";<br />for (int n=0;n {<br />stmp=(java.lang.Integer.toHexString(b[n] &amp; 0XFF));<br />if (stmp.length()==1) hs=hs+"0"+stmp;<br />else hs=hs+stmp;<br />if (n }<br />return hs.toUpperCase();<br />}</p>
						<p>}</p>
						<p> </p>
						<p>2.5. Diffie-Hellman密钥一致协议 <br />公开密钥密码体制的奠基人Diffie和Hellman所提出的 "指数密钥一致协议"(Exponential Key Agreement Protocol),该协议不要求别的安全性 先决条件,允许两名用户在公开媒体上交换信息以生成"一致"的,可以共享的密钥。在JCE的中实现用户alice生成DH类型的密钥对,如果长度用1024生成的时间请,推荐第一次生成后保存DHParameterSpec,以便下次使用直接初始化.使其速度加快 </p>
						<p>System.out.println("ALICE: 产生 DH 对 ...");<br />KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");<br />aliceKpairGen.initialize(512);<br />KeyPair aliceKpair = aliceKpairGen.generateKeyPair();</p>
						<p> </p>
						<p>alice生成公钥发送组bob byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();</p>
						<p> </p>
						<p>bob从alice发送来的公钥中读出DH密钥对的初始参数生成bob的DH密钥对</p>
						<p>注意这一步一定要做,要保证每个用户用相同的初始参数生成的 <br />DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();<br />KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");<br />bobKpairGen.initialize(dhParamSpec);<br />KeyPair bobKpair = bobKpairGen.generateKeyPair();</p>
						<p> </p>
						<p>bob根据alice的公钥生成本地的DES密钥<br />KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");<br />bobKeyAgree.init(bobKpair.getPrivate());<br />bobKeyAgree.doPhase(alicePubKey, true);<br />SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");</p>
						<p> </p>
						<p>bob已经生成了他的DES密钥,他现把他的公钥发给alice,<br />byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();</p>
						<p> </p>
						<p>alice根据bob的公钥生成本地的DES密钥<br />,,,,,,解码<br />KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");<br />aliceKeyAgree.init(aliceKpair.getPrivate());<br />aliceKeyAgree.doPhase(bobPubKey, true);<br />SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");</p>
						<p> </p>
						<p>bob和alice能过这个过程就生成了相同的DES密钥,在这种基础就可进行安全能信</p>
						<p>常用API</p>
						<p>java.security.KeyPairGenerator 密钥生成器类<br />public static KeyPairGenerator getInstance(String algorithm)<br />throws NoSuchAlgorithmException<br />以指定的算法返回一个KeyPairGenerator 对象<br />参数: algorithm 算法名.如:原来是DSA,现在添加了 DiffieHellman(DH)</p>
						<p>public void initialize(int keysize)<br />以指定的长度初始化KeyPairGenerator对象,如果没有初始化系统以1024长度默认设置<br />参数:keysize 算法位长.其范围必须在 512 到 1024 之间，且必须为 64 的倍数<br />注意:如果用1024生长的时间很长,最好生成一次后就保存,下次就不用生成了</p>
						<p>public void initialize(AlgorithmParameterSpec params)<br />throws InvalidAlgorithmParameterException<br />以指定参数初始化</p>
						<p>javax.crypto.interfaces.DHPublicKey<br />public DHParameterSpec getParams()<br />返回<br />java.security.KeyFactory</p>
						<p>public static KeyFactory getInstance(String algorithm)<br />throws NoSuchAlgorithmException<br />以指定的算法返回一个KeyFactory<br />参数: algorithm 算法名SH,DH</p>
						<p>public final PublicKey generatePublic(KeySpec keySpec)<br />throws InvalidKeySpecException<br />根据指定的key说明,返回一个PublicKey对象</p>
						<p>java.security.spec.X509EncodedKeySpec<br />public X509EncodedKeySpec(byte[] encodedKey)<br />根据指定的二进制编码的字串生成一个key的说明<br />参数:encodedKey 二进制编码的字串(一般能过PublicKey.getEncoded()生成)<br />javax.crypto.KeyAgreement 密码一至类</p>
						<p>public static final KeyAgreement getInstance(java.lang.String algorithm)<br />throws java.security.NoSuchAlgorithmException<br />返回一个指定算法的KeyAgreement对象<br />参数:algorithm 算法名,现在只能是DiffieHellman(DH)</p>
						<p>public final void init(java.security.Key key)<br />throws java.security.InvalidKeyException<br />用指定的私钥初始化<br />参数:key 一个私钥</p>
						<p>public final java.security.Key doPhase(java.security.Key key,<br />boolean lastPhase)<br />throws java.security.InvalidKeyException,<br />java.lang.IllegalStateException<br />用指定的公钥进行定位,lastPhase确定这是否是最后一个公钥,对于两个用户的<br />情况下就可以多次定次,最后确定<br />参数:key 公钥<br />lastPhase 是否最后公钥</p>
						<p>public final SecretKey generateSecret(java.lang.String algorithm)<br />throws java.lang.IllegalStateException,<br />java.security.NoSuchAlgorithmException,<br />java.security.InvalidKeyException<br />根据指定的算法生成密钥<br />参数:algorithm 加密算法(可用 DES,DESede,Blowfish)</p>
						<p>
								<br />*/<br />import java.io.*;<br />import java.math.BigInteger;<br />import java.security.*;<br />import java.security.spec.*;<br />import java.security.interfaces.*;<br />import javax.crypto.*;<br />import javax.crypto.spec.*;<br />import javax.crypto.interfaces.*;<br />import com.sun.crypto.provider.SunJCE;</p>
						<p>public class testDHKey {</p>
						<p>
								<br />public static void main(String argv[]) {<br />try {<br />testDHKey my= new testDHKey();<br />my.run();<br />} catch (Exception e) {<br />System.err.println(e);</p>
						<p>}<br />}</p>
						<p>private void run() throws Exception {<br />Security.addProvider(new com.sun.crypto.provider.SunJCE());</p>
						<p>System.out.println("ALICE: 产生 DH 对 ...");<br />KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");<br />aliceKpairGen.initialize(512);<br />KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); //生成时间长 </p>
						<p>// 张三(Alice)生成公共密钥 alicePubKeyEnc 并发送给李四(Bob) ,<br />//比如用文件方式,socket.....<br />byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();</p>
						<p>//bob接收到alice的编码后的公钥,将其解码<br />KeyFactory bobKeyFac = KeyFactory.getInstance("DH");<br />X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec (alicePubKeyEnc);<br />PublicKey alicePubKey = bobKeyFac.generatePublic(x509KeySpec);<br />System.out.println("alice公钥bob解码成功");<br />// bob必须用相同的参数初始化的他的DH KEY对,所以要从Alice发给他的公开密钥,<br />//中读出参数,再用这个参数初始化他的 DH key对</p>
						<p>//从alicePubKye中取alice初始化时用的参数<br />DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();<br />KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");<br />bobKpairGen.initialize(dhParamSpec);<br />KeyPair bobKpair = bobKpairGen.generateKeyPair();<br />System.out.println("BOB: 生成 DH key 对成功");<br />KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");<br />bobKeyAgree.init(bobKpair.getPrivate());<br />System.out.println("BOB: 初始化本地key成功");<br />//李四(bob) 生成本地的密钥 bobDesKey<br />bobKeyAgree.doPhase(alicePubKey, true);<br />SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");<br />System.out.println("BOB: 用alice的公钥定位本地key,生成本地DES密钥成功");<br />// Bob生成公共密钥 bobPubKeyEnc 并发送给Alice,<br />//比如用文件方式,socket.....,使其生成本地密钥<br />byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();<br />System.out.println("BOB向ALICE发送公钥");</p>
						<p>// alice接收到 bobPubKeyEnc后生成bobPubKey<br />// 再进行定位,使aliceKeyAgree定位在bobPubKey<br />KeyFactory aliceKeyFac = KeyFactory.getInstance("DH");<br />x509KeySpec = new X509EncodedKeySpec(bobPubKeyEnc);<br />PublicKey bobPubKey = aliceKeyFac.generatePublic(x509KeySpec);<br />System.out.println("ALICE接收BOB公钥并解码成功");<br />;<br />KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");<br />aliceKeyAgree.init(aliceKpair.getPrivate());<br />System.out.println("ALICE: 初始化本地key成功");</p>
						<p>aliceKeyAgree.doPhase(bobPubKey, true);<br />// 张三(alice) 生成本地的密钥 aliceDesKey<br />SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");<br />System.out.println("ALICE: 用bob的公钥定位本地key,并生成本地DES密钥");</p>
						<p>if (aliceDesKey.equals(bobDesKey)) System.out.println("张三和李四的密钥相同");<br />//现在张三和李四的本地的deskey是相同的所以,完全可以进行发送加密,接收后解密,达到<br />//安全通道的的目的</p>
						<p>/*<br />* bob用bobDesKey密钥加密信息<br />*/<br />Cipher bobCipher = Cipher.getInstance("DES");<br />bobCipher.init(Cipher.ENCRYPT_MODE, bobDesKey);<br />String bobinfo= "这是李四的机密信息";<br />System.out.println("李四加密前原文:"+bobinfo);<br />byte[] cleartext =bobinfo.getBytes();<br />byte[] ciphertext = bobCipher.doFinal(cleartext);</p>
						<p>/*<br />* alice用aliceDesKey密钥解密<br />*/<br />Cipher aliceCipher = Cipher.getInstance("DES");<br />aliceCipher.init(Cipher.DECRYPT_MODE, aliceDesKey);<br />byte[] recovered = aliceCipher.doFinal(ciphertext);<br />System.out.println("alice解密bob的信息:"+(new String(recovered)));<br />if (!java.util.Arrays.equals(cleartext, recovered))<br />throw new Exception("解密后与原文信息不同");<br />System.out.println("解密后相同");</p>
						<p>}</p>
						<p>}</p>
						<p> </p>
						<p>
								<strong>第3章 小结 </strong>
						</p>
						<p>
								<strong>
										<br />
								</strong>在加密术中生成密钥对时，密钥对的当然是越长越好，但费时也越多，请从中从实际出发选取合适的长度，大部分例码中的密钥是每次运行就从新生成，在实际的情况中是生成后在一段时间保存在文件中，再次运行直接从文件中读入，从而加快速度。当然定时更新和加强密钥保管的安全性也是必须的。<br /></p>
				</div>
				<br />
				<br />
				<div class="dashed">　</div>
				<br />文章引用自: <a href="http://www.nihaoblog.com/1_1751.html" target="_blank"><font color="#000033">http://www.nihaoblog.com/1_1751.html</font></a></div>
<img src ="http://www.blogjava.net/zeroone0/aggbug/38829.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-03 09:55 <a href="http://www.blogjava.net/zeroone0/articles/38829.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAR文件包及jar命令详解</title><link>http://www.blogjava.net/zeroone0/articles/38826.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Mon, 03 Apr 2006 01:51:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/38826.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/38826.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/38826.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/38826.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/38826.html</trackback:ping><description><![CDATA[1. JAR 文件包 <br /><br />　　JAR 文件就是 Java Archive File，顾名思意，它的应用是与 Java 息息相关的，是 Java 的一种文档格式。JAR 文件非常类似 ZIP 文件??准确的说，它就是 ZIP 文件，所以叫它文件包。JAR 文件与 ZIP 文件唯一的区别就是在 JAR 文件的内容中，包含了一个 META-INF/MANIFEST.MF 文件，这个文件是在生成 JAR 文件的时候自动创建的。举个例子，如果我们具有如下目录结构的一些文件： <br /><br />　　== <br /><br />　　`-- test <br /><br />　　　 `-- Test.class <br /><br />　　把它压缩成 ZIP 文件 test.zip，则这个 ZIP 文件的内部目录结构为： <br /><br />　　test.zip <br /><br />　　`-- test <br /><br />　　　 `-- Test.class <br /><br />　　如果我们使用 JDK 的 jar 命令把它打成 JAR 文件包 test.jar，则这个 JAR 文件的内部目录结构为： <br /><br />　　test.jar <br /><br />　　|-- META-INF <br /><br />　　|　 `-- MANIFEST.MF <br /><br />　　`-- test <br /><br />　　　　`--Test.class <br /><br />　　2. 创建可执行的 JAR 文件包 <br /><br />　　制作一个可执行的 JAR 文件包来发布你的程序是 JAR 文件包最典型的用法。 <br /><br />　　Java 程序是由若干个 .class 文件组成的。这些 .class 文件必须根据它们所属的包不同而分级分目录存放；运行前需要把所有用到的包的根目录指定给 CLASSPATH 环境变量或者 java 命令的 -cp 参数；运行时还要到控制台下去使用 java 命令来运行，如果需要直接双击运行必须写 Windows 的批处理文件 (.bat) 或者 Linux 的 Shell 程序。因此，许多人说，Java 是一种方便开发者苦了用户的程序设计语言。 <br /><br />　　其实不然，如果开发者能够制作一个可执行的 JAR 文件包交给用户，那么用户使用起来就方便了。在 Windows 下安装 JRE (Java Runtime Environment) 的时候，安装文件会将 .jar 文件映射给 javaw.exe 打开。那么，对于一个可执行的 JAR 文件包，用户只需要双击它就可以运行程序了，和阅读 .chm 文档一样方便 (.chm 文档默认是由 hh.exe 打开的)。那么，现在的关键，就是如何来创建这个可执行的 JAR 文件包。 <br /><br />　　创建可执行的 JAR 文件包，需要使用带 cvfm 参数的 jar 命令，同样以上述 test 目录为例，命令如下： <br /><br />jar cvfm test.jar manifest.mf test <br /><br />　　这里 test.jar 和 manifest.mf 两个文件，分别是对应的参数 f 和 m，其重头戏在 manifest.mf。因为要创建可执行的 JAR 文件包，光靠指定一个 manifest.mf 文件是不够的，因为 MANIFEST 是 JAR 文件包的特征，可执行的 JAR 文件包和不可执行的 JAR 文件包都包含 MANIFEST。关键在于可执行 JAR 文件包的 MANIFEST，其内容包含了 Main-Class 一项。这在 MANIFEST 中书写格式如下： <br /><br />　　Main-Class: 可执行主类全名(包含包名) <br /><br />　　例如，假设上例中的 Test.class 是属于 test 包的，而且是可执行的类 (定义了 public static void main(String[]) 方法)，那么这个 manifest.mf 可以编辑如下： <br /><br />Main-Class: test.Test &lt;回车&gt; <br /><br />　　这个 manifest.mf 可以放在任何位置，也可以是其它的文件名，只需要有 Main-Class: test.Test 一行，且该行以一个回车符结束即可。创建了 manifest.mf 文件之后，我们的目录结构变为： <br /><br />　　== <br /><br />　　|-- test <br /><br />　　|　 `-- Test.class <br /><br />　　`-- manifest.mf <br /><br />　　这时候，需要到 test 目录的上级目录中去使用 jar 命令来创建 JAR 文件包。也就是在目录树中使用“==”表示的那个目录中，使用如下命令： <br /><br />jar cvfm test.jar manifest.mf test <br /><br />　　之后在“==”目录中创建了 test.jar，这个 test.jar 就是执行的 JAR 文件包。运行时只需要使用 java -jar test.jar 命令即可。 <br /><br />　　需要注意的是，创建的 JAR 文件包中需要包含完整的、与 Java 程序的包结构对应的目录结构，就像上例一样。而 Main-Class 指定的类，也必须是完整的、包含包路径的类名，如上例的 test.Test；而且在没有打成 JAR 文件包之前可以使用 java &lt;类名&gt; 来运行这个类，即在上例中 java test.Test 是可以正确运行的 (当然要在 CLASSPATH 正确的情况下)。<br /><br />　　3. jar 命令详解 <br /><br />　　jar 是随 JDK 安装的，在 JDK 安装目录下的 bin 目录中，Windows 下文件名为 jar.exe，Linux 下文件名为 jar。它的运行需要用到 JDK 安装目录下 lib 目录中的 tools.jar 文件。不过我们除了安装 JDK 什么也不需要做，因为 SUN 已经帮我们做好了。我们甚至不需要将 tools.jar 放到 CLASSPATH 中。 <br /><br />　　使用不带任何的 jar 命令我们可以看到 jar 命令的用法如下： <br /><br />　　jar {ctxu}[vfm0M] [jar-文件] [manifest-文件] [-C 目录] 文件名 ... <br /><br />　　其中 {ctxu} 是 jar 命令的子命令，每次 jar 命令只能包含 ctxu 中的一个，它们分别表示： <br /><br />　　　-c　创建新的 JAR 文件包 <br /><br />　　　-t　列出 JAR 文件包的内容列表 <br /><br />　　　-x　展开 JAR 文件包的指定文件或者所有文件 <br /><br />　　　-u　更新已存在的 JAR 文件包 (添加文件到 JAR 文件包中) <br /><br />　　　　　[vfm0M] 中的选项可以任选，也可以不选，它们是 jar 命令的选项参数 <br /><br />　　　-v　生成详细报告并打印到标准输出 <br /><br />　　　-f　指定 JAR 文件名，通常这个参数是必须的 <br /><br />　　　-m　指定需要包含的 MANIFEST 清单文件 <br /><br />　　　-0　只存储，不压缩，这样产生的 JAR 文件包会比不用该参数产生的体积大，但速度更快 <br /><br />　　　-M　不产生所有项的清单（MANIFEST〕文件，此参数会忽略 -m 参数 <br /><br />　　　　　[jar-文件] 即需要生成、查看、更新或者解开的 JAR 文件包，它是 -f 参数的附属参数 <br /><br />　　　　　[manifest-文件] 即 MANIFEST 清单文件，它是 -m 参数的附属参数 <br /><br />　　　　　[-C 目录] 表示转到指定目录下去执行这个 jar 命令的操作。它相当于先使用 cd 命令转该目录下再执行不带 -C 参数的 jar 命令，它只能在创建和更新 JAR 文件包的时候可用。　　 <br /><br />　　文件名 ... 指定一个文件/目录列表，这些文件/目录就是要添加到 JAR 文件包中的文件/目录。如果指定了目录，那么 jar 命令打包的时候会自动把该目录中的所有文件和子目录打入包中。 <br /><br />　　下面举一些例子来说明 jar 命令的用法： <br /><br />　　1) jar cf test.jar test <br /><br />　　该命令没有执行过程的显示，执行结果是在当前目录生成了 test.jar 文件。如果当前目录已经存在 test.jar，那么该文件将被覆盖。 <br /><br />　　2) jar cvf test.jar test <br /><br />　　该命令与上例中的结果相同，但是由于 v 参数的作用，显示出了打包过程，如下： <br /><br />　　标明清单(manifest) <br /><br />　　增加：test/(读入= 0) (写出= 0)(存储了 0%) <br /><br />　　增加：test/Test.class(读入= 7) (写出= 6)(压缩了 14%) <br /><br />　　3) jar cvfM test.jar test <br /><br />　　该命令与 2) 结果类似，但在生成的 test.jar 中没有包含 META-INF/MANIFEST 文件，打包过程的信息也略有差别： <br /><br />　　增加：test/(读入= 0) (写出= 0)(存储了 0%) <br /><br />　　增加：test/Test.class(读入= 7) (写出= 6)(压缩了 14%) <br /><br />　　4) jar cvfm test.jar manifest.mf test <br /><br />　　运行结果与 2) 相似，显示信息也相同，只是生成 JAR 包中的 META-INF/MANIFEST 内容不同，是包含了 manifest.mf 的内容 <br /><br />　　5) jar tf test.jar <br /><br />　　在 test.jar 已经存在的情况下，可以查看 test.jar 中的内容，如对于 2) 和 3) 生成的 test.jar 分别应该此命令，结果如下； <br /><br />　　对于 2) <br /><br />　　META-INF/ <br /><br />　　META-INF/MANIFEST.MF <br /><br />　　test/ <br /><br />　　test/Test.class <br /><br />　　对于 3) <br /><br />　　test/ <br /><br />　　test/Test.class <br /><br />　　6) jar tvf test.jar <br /><br />　　除显示 5) 中显示的内容外，还包括包内文件的详细信息，如： <br /><br />　　0 Wed Jun 19 15:39:06 GMT 2002 META-INF/ <br /><br />　　86 Wed Jun 19 15:39:06 GMT 2002 META-INF/MANIFEST.MF <br /><br />　　0 Wed Jun 19 15:33:04 GMT 2002 test/ <br /><br />　　7 Wed Jun 19 15:33:04 GMT 2002 test/Test.class <br /><br />　　7) jar xf test.jar <br /><br />　　解开 test.jar 到当前目录，不显示任何信息，对于 2) 生成的 test.jar，解开后的目录结构如下： <br /><br />　　== <br /><br />　　|-- META-INF <br /><br />　　|　 `-- MANIFEST <br /><br />　　`-- test <br /><br />　　　　`--Test.class <br /><br />jar xvf test.jar <br /><br />　　运行结果与 7) 相同，对于解压过程有详细信息显示，如： <br /><br />　　创建：META-INF/ <br /><br />　　展开：META-INF/MANIFEST.MF <br /><br />　　创建：test/ <br /><br />　　展开：test/Test.class <br /><br />　　9) jar uf test.jar manifest.mf <br /><br />　　在 test.jar 中添加了文件 manifest.mf，此使用 jar tf 来查看 test.jar 可以发现 test.jar 中比原来多了一个 manifest。这里顺便提一下，如果使用 -m 参数并指定 manifest.mf 文件，那么 manifest.mf 是作为清单文件 MANIFEST 来使用的，它的内容会被添加到 MANIFEST 中；但是，如果作为一般文件添加到 JAR 文件包中，它跟一般文件无异。 <br /><br />　　10) jar uvf test.jar manifest.mf <br /><br />　　与 9) 结果相同，同时有详细信息显示，如： <br /><br />　　增加：manifest.mf(读入= 17) (写出= 19)(压缩了 -11%) <br /><br />　　4. 关于 JAR 文件包的一些技巧 <br /><br />　　1) 使用 unzip 来解压 JAR 文件 <br /><br />　　在介绍 JAR 文件的时候就已经说过了，JAR 文件实际上就是 ZIP 文件，所以可以使用常见的一些解压 ZIP 文件的工具来解压 JAR 文件，如 Windows 下的 WinZip、WinRAR 等和 Linux 下的 unzip 等。使用 WinZip 和 WinRAR 等来解压是因为它们解压比较直观，方便。而使用 unzip，则是因为它解压时可以使用 -d 参数指定目标目录。 <br /><br />　　在解压一个 JAR 文件的时候是不能使用 jar 的 -C 参数来指定解压的目标的，因为 -C 参数只在创建或者更新包的时候可用。那么需要将文件解压到某个指定目录下的时候就需要先将这具 JAR 文件拷贝到目标目录下，再进行解压，比较麻烦。如果使用 unzip，就不需要这么麻烦了，只需要指定一个 -d 参数即可。如： <br /><br />　　unzip test.jar -d dest/ <br /><br />　　2) 使用 WinZip 或者 WinRAR 等工具创建 JAR 文件 <br /><br />　　上面提到 JAR 文件就是包含了 META-INF/MANIFEST 的 ZIP 文件，所以，只需要使用 WinZip、WinRAR 等工具创建所需要 ZIP 压缩包，再往这个 ZIP 压缩包中添加一个包含 MANIFEST 文件的 META-INF 目录即可。对于使用 jar 命令的 -m 参数指定清单文件的情况，只需要将这个 MANIFEST 按需要修改即可。 <br /><br />　　3) 使用 jar 命令创建 ZIP 文件 <br /><br />　　有些 Linux 下提供了 unzip 命令，但没有 zip 命令，所以需要可以对 ZIP 文件进行解压，即不能创建 ZIP 文件。如要创建一个 ZIP 文件，使用带 -M 参数的 jar 命令即可，因为 -M 参数表示制作 JAR 包的时候不添加 MANIFEST 清单，那么只需要在指定目标 JAR 文件的地方将 .jar 扩展名改为 .zip 扩展名，创建的就是一个不折不扣的 ZIP 文件了，如将上一节的第 3) 个例子略作改动： <br /><br />　　jar cvfM test.zip test  <br /><div><br /><br /><br />文章引用自: <a href="http://www.javaresearch.org/article/showarticle.jsp?column=1&amp;thread=32683" target="_blank"><font color="#000033">http://www.javaresearch.org/article/showarticle.jsp?column=1&amp;thread=32683</font></a></div><img src ="http://www.blogjava.net/zeroone0/aggbug/38826.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-03 09:51 <a href="http://www.blogjava.net/zeroone0/articles/38826.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java Mail编程</title><link>http://www.blogjava.net/zeroone0/articles/38822.html</link><dc:creator>zeroone0</dc:creator><author>zeroone0</author><pubDate>Mon, 03 Apr 2006 01:49:00 GMT</pubDate><guid>http://www.blogjava.net/zeroone0/articles/38822.html</guid><wfw:comment>http://www.blogjava.net/zeroone0/comments/38822.html</wfw:comment><comments>http://www.blogjava.net/zeroone0/articles/38822.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zeroone0/comments/commentRss/38822.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zeroone0/services/trackbacks/38822.html</trackback:ping><description><![CDATA[
		<div>1.介绍:<br /><br />Java Mail API的开发是SUN为Java开发者提供公用API框架的持续努力的良好例证。提倡公用框架，反对受限于供应商的解决方案，充分预示着一个日益开放的开发环境的建立。<br />　　Java Mail API的结构本身证明了它的开发者的基本目标之一--软件开发的工作量应该取决于应用程序本身的复杂程度以及开发者所要求的控制程度。换句话说，Java Mail API尽可能地保持简单。乍看起来，JavaMail API所拥有的类总数以及类之间的关系可能让人误解为要花费漫长的学习时间。实际上，一旦正式开始使用，你就会发现该API不失为在应用程序中加入健壮的邮件/通讯支持的简单工具。 <br /><br />2.安装:<br /><br />安装前要确保你的机子上安装得有标准版的JDK和Web服务器,并且已配置好,有关它们的安装方法,请参考其它文章(网上到处都有).<br /><br />(1).安装JavaMail API。现在最常用的 JavaMail API 版本是1.3.<br />　　要使用 JavaMail 1.3 API，请下载 JavaMail 1.3 实现，解开Javamail-1_3.zip 文件，并将 mail.jar 文件添加到 CLASSPATH 中。除了核心类，随版本 1.3 实现一起提供的还有 SMTP、IMAP4 和 POP3 供应商。<br />　　<br />(2).JavaBeans Activation Framework(1.0.2版) 的安装<br />JavaMail API 的所有版本都需要 JavaBeans Activation Framework 来支持任意数据块的输入及相应处理。功能似乎不多，但目前许多浏览器和邮件工具中都能找到这种基本的 MIME 型支持。下载完框架后，解开 jaf1_0_2.zip 文件，并将 activation.jar 文件添加到 CLASSPATH 中。<br />　　<br /><br />注: 如果您使用的JDK是J2EE，就没有什么特定的事非要用基本 JavaMail API来做不可；J2EE 的类就能处理了,因为它本身就包含有JavaMail API和JAF,您只需要确将 j2ee.jar 文件添加到您的CLASSPATH 中并已全部设置好。<br /><br />3.JavaMail的常用类介绍<br /><br />事先说明:<br /><br />没用过JavaMail的人可能看不懂这些介绍,不过不要紧,后面的文章有具体的实例,到时也可倒回来查看这些类的用法.<br /><br /><br />(1) javax.mail.Properties类<br />　　JavaMail需要Properties来创建一个session对象。它将寻找字符串"mail.smtp.host"，属性值就是发送邮件的主机.<br /><br />用法:<br />　Properties props = new Properties ();<br />　　　props.put("mail.smtp.host", "smtp.163.com");//可以换上你的smtp主机名。<br /><br /><br />(2) javax.mail.Session类<br />　　这个Session类代表JavaMail 中的一个邮件session. 每一个基于 JavaMail的应用程序至少有一个session但是可以有任意多的session。 在这个例子中, Session对象需要知道用来处理邮件的SMTP 服务器。<br /><br />用法:<br />　　 Session sendMailSession;<br />　　 sendMailSession = Session.getInstance(props, null);<br /><br /><br />(3) javax.mail.Transport类<br />　　邮件是既可以被发送也可以被受到。JavaMail使用了两个不同的类来完成这两个功能：Transport 和Store. Transport 是用来发送信息的，而Store用来收信。对于这的教程我们只需要用到Transport对象。<br /><br />用法：<br />Transport transport;<br />　 transport = sendMailSession.getTransport("smtp");<br /><br />　　用JavaMail Session对象的getTransport 方法来初始化Transport。传过去的字符串申明了对象所要使用的协议，如"smtp"。这将为我们省了很多时间。因为JavaMail以境内置了很多协议的实现方法。<br /><br />　注意: JavaMail并不是绝对支持每一个协议，目前支持IMAP、 SMTP和 POP3. <br /><br /><br />(4) javax.mail.MimeMessage类<br />　　Message对象将存储我们实际发送的电子邮件信息，Message对象被作为一个MimeMessage对象来创建并且需要知道应当选择哪一个JavaMail session。<br /><br />　　用法：<br />Message newMessage = new MimeMessage(sendMailSession);<br /><br /><br />(5) javax.mail.InternetAddress类<br />一旦您创建了 Session 和 Message，并将内容填入消息后，就可以用Address确定信件地址了。和 Message 一样，Address 也是个抽象类。您用的是Javax.mail.internet.InternetAddress 类.<br /><br />用法:<br />InternetAddress from=new InternetAddress("xxf@cafe.com");<br /><br /><br />(6) javax.mail.Store类<br />Store类实现特定邮件协议上的读、写、监视、查找等操作。通过Javax.mail.Store类可以访问Javax.mail.Folder类。<br /><br />用法:<br />Store store=s.getSorte("pop3");//s为一个邮件会话<br />store.connect(popserver,username,password);//通过你提供的pop地址,用户名和密码登录你的邮箱<br /><br /><br />(7) javax.mail.Folder类<br />Folder类用于分级组织邮件，并提供照Javax.mail.Message格式访问email的能力。 <br /><br />用法:<br />Folder folder=store.getFolder("INBOX");<br />folder.open(Folder.READ_ONLY);<br /><br /><br />(8) javax.mail.Internet.MimeMultpart<br />一般保存电子邮件内容的容器是Multipart抽象类,它定义了增加和删除及获得电子邮件不同部分内容的方法.由于Multipart是抽象类,我们必须为它使用一个具体的子类,JavaMail API提供javax.mail.Internet.MimeMultpart类来使用MimeMessage对象.<br /><br />用法:<br />MimeMultipart multipart=new MimeMultipart();<br /><br />注:我们使用MimeMultipart对象的一个方法是addBodyPart(),它在我们的电子邮件内容里添加BodyPart(BodyPart类在下面紧接着要介绍)对象.消息可以有很多部分,一个BodyPart可以代表一个部分.<br /><br /><br />(9) javax.mail.Internet.MimeBodyPart类<br /><br />MimeBodyPart是BodyPart具体用于mimeMessage的一个子类.<br />MimeBodyPart对象代表一个MimeMessage对象内容的一部分.每个MimeBodyPart被认为有两部分:<br />⊙一个MIME类型 <br />⊙匹配这个类型的内容<br /><br />用法:<br />MimeBodyPart mdp=new MimeBodyPart();<br />String text="Hello JavaMail!";<br />mdp.setContent(text,"text/plain");//定义MIME类型为text/plain,并设置MimeBodyPart的内容.<br /><br /><br />(10) javax.activation.DataHandler类(包含在JAF中)<br />JavaMail API不限制信息只为文本,任何形式的信息都可能作茧自缚MimeMessage的一部分.除了文本信息,作为文件附件包含在电子邮件信息的一部分是很普遍的.JavaMail API通过使用DataHandler对象,提供一个允许我们包含非文本BodyPart对象的简便方法.<br /><br />用法:<br />DataHandler dh=new DataHandler(text,type);<br />mdp.setDatahandler(dh);//mdp是一个MimeBodyPart对象<br /><br /><br />(11) javax.activation.FileDataSource类(包含在JAF中)<br />一个FileDataSource对象可以表示本地文件和服务器可以直接访问的资源.一个本地文件可以通过创建一个新的MimeBodyPart对象附在一个mimeMessage对象上.<br /><br />用法:<br />MimeMultipart mm=new MimeMultipart();<br />MimeBodyPart mdp=new MimeBodyPart();<br />FileDataSource fds=new FileDataSource("c:/exam.txt");<br />mdp.setDataHandler(new DataHandler(fds)); //设置数据源<br />mm.addBodyPart(mdp); //为当前消息MimeMultipart对象增加MimeBodyPart<br /><br /><br />(12) javax.activation.URLDataSource类(包含在JAF中)<br />远程资源,URL不会指向它们,由一个URLDataSource对象表示.一个远程资源可以通过创建一个新mimeBodyPart对象附在一个mimeMessage对象上(同FileDataSource差不多).<br /><br />用法:<br />与FileDataSource唯一不同的是数据源的设置:<br />URLDataSource uds=new URLDataSource(<a href="http://www.cnjsp.com/logo.gif">http://www.cnjsp.com/logo.gif</a>); <br /><br /><div>4.试着编写第一个发送程序</div><div>在前面我们已对JavaMail作了一些介绍,下面我们可试着写自己的程序了.</div><div>首先,我们先写一个撰写邮件的html程序index.htm,如下:<br />-------------------------------------------------------------------------------------------<br />&lt;html&gt;<br />&lt;head&gt;<br />&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312"&gt;<br />&lt;title&gt;撰写邮件&lt;/title&gt;<br />&lt;/head&gt;</div><div>&lt;body&gt;<br />&lt;form name="form1" method="post" action="testmail.jsp"&gt;<br />&lt;table width="75" border="0" align="center" cellspacing="1" bgcolor="#006600" class="black"&gt;<br />&lt;tr bgcolor="#FFFFFF"&gt; <br />&lt;td width="24%"&gt;收信人地址:&lt;/td&gt;<br />&lt;td width="76%"&gt; <br />&lt;input name="to" type="text" id="to"&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr bgcolor="#FFFFFF"&gt; <br />&lt;td&gt;主题:&lt;/td&gt;<br />&lt;td&gt; <br />&lt;input name="title" type="text" id="title"&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr&gt; <br />&lt;td height="107" colspan="2" bgcolor="#FFFFFF"&gt; <br />&lt;textarea name="content" cols="50" rows="5" id="content"&gt;&lt;/textarea&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr align="center"&gt; <br />&lt;td colspan="2" bgcolor="#FFFFFF"&gt; <br />&lt;input type="submit" name="Submit" value="发送"&gt;<br />&lt;input type="reset" name="Submit2" value="重置"&gt;<br />&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;/table&gt;<br />&lt;/form&gt;<br />&lt;/body&gt;<br />&lt;/html&gt;</div><div><br />接着,我们再写一个处理程序testmail.jsp,如下:<br />-----------------------------------------------------------------------------------------<br />&lt;%@ page contentType="text/html;charset=GB2312" %&gt;<br />&lt;%request.setCharacterEncoding("gb2312");%&gt;&lt;!--中文处理代码--&gt;</div><div>&lt;!--引入要用到的类库--&gt;<br />&lt;%@ page import="java.util.*,javax.mail.*"%&gt;<br />&lt;%@ page import="javax.mail.internet.*"%&gt;</div><div>&lt;html&gt;<br />&lt;head&gt;<br />&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312"&gt;<br />&lt;title&gt;发送成功&lt;/title&gt;<br />&lt;/head&gt;</div><div>&lt;body&gt;<br />&lt;%<br />try{</div><div>//从html表单中获取邮件信息<br />String tto=request.getParameter("to");<br />String ttitle=request.getParameter("title");<br />String tcontent=request.getParameter("content");</div><div>Properties props=new Properties();//也可用Properties props = System.getProperties(); <br />props.put("mail.smtp.host","smtp.163.net");//存储发送邮件服务器的信息<br />props.put("mail.smtp.auth","true");//同时通过验证<br />Session s=Session.getInstance(props);//根据属性新建一个邮件会话<br />s.setDebug(true);</div><div>MimeMessage message=new MimeMessage(s);//由邮件会话新建一个消息对象</div><div>//设置邮件<br />InternetAddress from=new InternetAddress("<a href="mailto:boy@163.net"><font color="#000033">boy@163.net</font></a>");<br />message.setFrom(from);//设置发件人<br />InternetAddress to=new InternetAddress(tto);<br />message.setRecipient(Message.RecipientType.TO,to);//设置收件人,并设置其接收类型为TO<br />message.setSubject(ttitle);//设置主题<br />message.setText(tcontent);//设置信件内容<br />message.setSentDate(new Date());//设置发信时间</div><div>//发送邮件<br />message.saveChanges();//存储邮件信息<br />Transport transport=s.getTransport("smtp");<br />transport.connect("smtp.163.net","boy","iloveyou");//以smtp方式登录邮箱<br />transport.sendMessage(message,message.getAllRecipients());//发送邮件,其中第二个参数是所有<br />//已设好的收件人地址<br />transport.close();</div><div>%&gt;<br />&lt;div align="center"&gt;<br />&lt;p&gt;&lt;font color="#FF6600"&gt;发送成功!&lt;/font&gt;&lt;/p&gt;<br />&lt;p&gt;&lt;a href="recmail.jsp"&gt;去看看我的信箱&lt;/a&gt;&lt;br&gt;<br />&lt;br&gt;<br />&lt;a href="index.htm"&gt;再发一封&lt;/a&gt; &lt;/p&gt;<br />&lt;/div&gt;<br />&lt;%<br />}catch(MessagingException e){<br />out.println(e.toString());<br />}<br />%&gt;<br />&lt;/body&gt;<br />&lt;/html&gt;</div><div>**********************************注意***************************************</div><div>有好多书上和网上的文章在关键部分都是这样写testmail.jsp的,如下:</div><div>String tto=request.getParameter("to");<br />String ttitle=request.getParameter("title");<br />String tcontent=request.getParameter("content");<br />Properties props=new Properties();<br />props.put("mail.smtp.host","smtp.163.net");<br />Session s=Session.getInstance(props);<br />MimeMessage message=new MimeMessage(s);</div><div>InternetAddress from=new InternetAddress("<a href="mailto:boy@163.net"><font color="#000033">boy@163.net</font></a>");<br />message.setFrom(from);<br />InternetAddress to=new InternetAddress(tto);<br />message.setRecipient(Message.RecipientType.TO,to);</div><div>message.setSubject(ttitle);<br />message.setText(tcontent);<br />message.setSentDate(new Date());</div><div>Store store=s.getStore("pop3");<br />store.connect("pop.163.net","boy","iloveyou");//以pop3的方式登录邮箱<br />Transport transport=s.getTransport("smtp");<br />transport.send(message);<br />store.close();</div><div>事实上,这种方式并不可靠,因为很多电子邮局的smtp服务器要求我们通过验证,所以用这种方式发邮件时,只能发给同类邮箱(即相同smtp的邮箱),甚至有时同类邮箱也发不出去.以上两种方式我试过很多次,结果证明第一种方式是最可靠的.</div><div><br />好了,我相信你应该会写最简单的Email发送程序了.OK,下一次我们将说说怎样写发送HTML格式的邮件.</div><div>5.发送HTML格式的邮件</div><div>所谓HTML格式,就是超文本格式.你的邮件可以用HTML代码编写,发给对方后,对方收到的将是信息将是超文本,超文本比纯文本好看多了.下以面是在以前例子的基础上修改的程序:</div><div>&lt;%@ page contentType="text/html;charset=GB2312" %&gt;<br />&lt;%request.setCharacterEncoding("gb2312");%&gt;<br />&lt;%@ page import="java.util.*,javax.mail.*"%&gt;<br />&lt;%@ page import="javax.mail.internet.*"%&gt;<br />&lt;html&gt;<br />&lt;head&gt;<br />&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312"&gt;<br />&lt;title&gt;发送成功&lt;/title&gt;<br />&lt;/head&gt;</div><div>&lt;body&gt;<br />&lt;%<br />try{<br />String tto=request.getParameter("to");<br />String ttitle=request.getParameter("title");<br />String tcontent=request.getParameter("content");<br />Properties props=new Properties();<br />props.put("mail.smtp.host","127.0.0.1");<br />props.put("mail.smtp.auth","true");<br />Session s=Session.getInstance(props);<br />s.setDebug(true);</div><div>MimeMessage message=new MimeMessage(s);</div><div>//给消息对象设置发件人/收件人/主题/发信时间<br />InternetAddress from=new InternetAddress("<a href="mailto:xxf@cafe.com"><font color="#000033">xxf@cafe.com</font></a>");<br />message.setFrom(from);<br />InternetAddress to=new InternetAddress(tto);<br />message.setRecipient(Message.RecipientType.TO,to);<br />message.setSubject(ttitle);<br />message.setSentDate(new Date());</div><div><br />//给消息对象设置内容<br />BodyPart mdp=new MimeBodyPart();//新建一个存放信件内容的BodyPart对象<br />mdp.setContent(tcontent,"text/html;charset=gb2312");//给BodyPart对象设置内容和格式/编码方式<br />Multipart mm=new MimeMultipart();//新建一个MimeMultipart对象用来存放BodyPart对<br />//象(事实上可以存放多个)<br />mm.addBodyPart(mdp);//将BodyPart加入到MimeMultipart对象中(可以加入多个BodyPart)<br />message.setContent(mm);//把mm作为消息对象的内容</div><div>message.saveChanges();<br />Transport transport=s.getTransport("smtp");<br />transport.connect("127.0.0.1","xxf","coffee");<br />transport.sendMessage(message,message.getAllRecipients());<br />transport.close();<br />%&gt;<br />&lt;div align="center"&gt;<br />&lt;p&gt;&lt;font color="#FF6600"&gt;发送成功!&lt;/font&gt;&lt;/p&gt;<br />&lt;p&gt;&lt;a href="recmail.jsp"&gt;去看看我的信箱&lt;/a&gt;&lt;br&gt;<br />&lt;br&gt;<br />&lt;a href="index.htm"&gt;再发一封&lt;/a&gt; &lt;/p&gt;<br />&lt;/div&gt;<br />&lt;%<br />}catch(MessagingException e){<br />out.println(e.toString());<br />}<br />%&gt;<br />&lt;/body&gt;<br />&lt;/html</div><div>6.发送三种类型的附件</div><div>前面我们已学会了发送一般文本邮件和超文本邮件,今天我们将让大家学会编写三种类型的附件的邮件 <br />发送程序.(注:撰写界面仍然用前面的)</div><div>&lt;%@ page contentType="text/html;charset=GB2312" %&gt;<br />&lt;%request.setCharacterEncoding("gb2312");%&gt;<br />&lt;%@ page import="java.util.*,javax.mail.*"%&gt;<br />&lt;%@ page import="javax.mail.internet.*"%&gt;<br />&lt;%@ page import="javax.activation.*"%&gt;&lt;!--要发送附件必须引入该库--&gt;<br />&lt;%@ page import="java.net.*"%&gt;&lt;!--要用到URL类--&gt;<br />&lt;html&gt;<br />&lt;head&gt;<br />&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312"&gt;<br />&lt;title&gt;发送成功&lt;/title&gt;<br />&lt;/head&gt;<br />&lt;body&gt;<br />&lt;%<br />try{<br />String tto=request.getParameter("to");<br />String ttitle=request.getParameter("title");<br />String tcontent=request.getParameter("content");<br />Properties props=new Properties();<br />props.put("mail.smtp.host","127.0.0.1");<br />props.put("mail.smtp.auth","true");<br />Session s=Session.getInstance(props);<br />s.setDebug(true);</div><div>MimeMessage message=new MimeMessage(s);</div><div>//给消息对象设置发件人/收件人/主题/发信时间<br />InternetAddress from=new InternetAddress("<a href="mailto:xxf@cafe.com"><font color="#000033">xxf@cafe.com</font></a>");<br />message.setFrom(from);<br />InternetAddress to=new InternetAddress(tto);<br />message.setRecipient(Message.RecipientType.TO,to);<br />message.setSubject(ttitle);<br />message.setSentDate(new Date());</div><div>Multipart mm=new MimeMultipart();//新建一个MimeMultipart对象用来存放多个BodyPart对象</div><div>//设置信件文本内容<br />BodyPart mdp=new MimeBodyPart();//新建一个存放信件内容的BodyPart对象<br />mdp.setContent(tcontent,"text/html;charset=gb2312");//给BodyPart对象设置内容和格式/编码方式<br />mm.addBodyPart(mdp);//将含有信件内容的BodyPart加入到MimeMultipart对象中</div><div>//设置信件的附件1(自定义附件:直接将所设文本内容加到自定义文件中作为附件发送)<br />mdp=new MimeBodyPart();//新建一个存放附件的BodyPart<br />DataHandler dh=new DataHandler("JavaMail附件测试","text/plain;charset=gb2312");<br />//新建一个DataHandler对象,并设置其内容和格式/编码方式<br />mdp.setFileName("xxf.txt");//加上这句将作为附件发送,否则将作为信件的文本内容<br />mdp.setDataHandler(dh);//给BodyPart对象设置内容为dh<br />mm.addBodyPart(mdp);//将含有附件的BodyPart加入到MimeMultipart对象中</div><div>//设置信件的附件2(用本地上的文件作为附件)<br />mdp=new MimeBodyPart();<br />FileDataSource fds=new FileDataSource("g:/xx.txt");<br />dh=new DataHandler(fds);<br />mdp.setFileName("dd.txt");//可以和原文件名不一致<br />mdp.setDataHandler(dh);<br />mm.addBodyPart(mdp);</div><div>//设置信件的附件3(用远程文件作为附件)<br />mdp=new MimeBodyPart();<br />URLDataSource ur=new URLDataSource(new URL("<a href="http://localhost:8080/jspstudy/email/xx.gif"><font color="#000033">http://localhost:8080/jspstudy/email/xx.gif</font></a>")); <br />//注:这里用的参数只能为URL对象,不能为URL字串,在前面类介绍时有误(请谅解),这里纠正一下.<br />dh=new DataHandler(ur);<br />mdp.setFileName("ss.txt");<br />mdp.setDataHandler(dh);<br />mm.addBodyPart(mdp);</div><div>message.setContent(mm);//把mm作为消息对象的内容</div><div>message.saveChanges();<br />Transport transport=s.getTransport("smtp");<br />transport.connect("127.0.0.1","xxf","coffee");<br />transport.sendMessage(message,message.getAllRecipients());<br />transport.close();<br />%&gt;<br />&lt;div align="center"&gt;<br />&lt;p&gt;&lt;font color="#FF6600"&gt;发送成功!&lt;/font&gt;&lt;/p&gt;<br />&lt;p&gt;&lt;a href="recmail.jsp"&gt;去看看我的信箱&lt;/a&gt;&lt;br&gt;<br />&lt;br&gt;<br />&lt;a href="index.htm"&gt;再发一封&lt;/a&gt; &lt;/p&gt;<br />&lt;/div&gt;<br />&lt;%<br />}catch(MessagingException e){<br />out.println(e.toString());<br />}<br />%&gt;<br />&lt;/body&gt;<br />&lt;/html&gt;</div><div>七.编写灵活的发送程序</div><div>本小节没加什么新鲜的东西,但是综合了以前的所有内容,可以让你灵活地发送你想要发的邮件.看了本小节之后对你会感觉到非常有用.</div><div>更改后的撰写界面程序如下:<br />-------------------------------------------------------------------------------------------<br />&lt;html&gt;<br />&lt;head&gt;<br />&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312"&gt;<br />&lt;title&gt;撰写邮件&lt;/title&gt;<br />&lt;/head&gt;<br />&lt;body&gt;<br />&lt;form action="testall.jsp" method="post" name="form1"&gt;<br />&lt;table width="75" border="0" align="center" cellspacing="1" bgcolor="#006600" class="black"&gt;<br />&lt;tr bgcolor="#FFFFFF"&gt; <br />&lt;td width="24%"&gt;收信人地址:&lt;/td&gt;<br />&lt;td width="76%"&gt; &lt;input name="to" type="text" id="to"&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr bgcolor="#FFFFFF"&gt; <br />&lt;td&gt;主题:&lt;/td&gt;<br />&lt;td&gt; &lt;input name="title" type="text" id="title"&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr&gt; <br />&lt;td height="18" colspan="2" bgcolor="#FFFFFF"&gt;信件类型<br />&lt;select name="emailtype" id="emailtype"&gt;<br />&lt;option value="text/plain" selected&gt;Text&lt;/option&gt;<br />&lt;option value="text/html"&gt;Html&lt;/option&gt;<br />&lt;/select&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr&gt; <br />&lt;td height="53" colspan="2" bgcolor="#FFFFFF"&gt;&lt;textarea name="content" cols="50" rows="5" id="content"&gt;&lt;/textarea&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr align="center"&gt; <br />&lt;td colspan="2" bgcolor="#FFFFFF"&gt;附件1(自定义): <br />&lt;input name="fj1" type="text" id="fj1"&gt;<br />(输入文本信息) &lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr align="center" valign="bottom"&gt; <br />&lt;td colspan="2" bgcolor="#FFFFFF"&gt;附件2(本地): <br />&lt;input name="fj2" type="file" id="fj2" size="10"&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr align="center"&gt; <br />&lt;td colspan="2" bgcolor="#FFFFFF"&gt;附件3(远程): <br />&lt;input name="fj3" type="text" id="fj3" value="http://"&gt;<br />(输入URL)&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;tr align="center"&gt; <br />&lt;td colspan="2" bgcolor="#FFFFFF"&gt; &lt;input type="submit" name="Submit" value="发送"&gt; <br />&lt;input type="reset" name="Submit2" value="重置"&gt;&lt;/td&gt;<br />&lt;/tr&gt;<br />&lt;/table&gt;<br />&lt;/form&gt;<br />&lt;/body&gt;<br />&lt;/html&gt;</div><div><br />处理邮件的JSP程序如下:<br />----------------------------------------------------------------------------------------<br />&lt;%@ page contentType="text/html;charset=GB2312" %&gt;<br />&lt;%request.setCharacterEncoding("gb2312");%&gt;<br />&lt;%@ page import="java.util.*,javax.mail.*"%&gt;<br />&lt;%@ page import="javax.mail.internet.*"%&gt;<br />&lt;%@ page import="javax.activation.*"%&gt;&lt;!--要发送附件必须引入该库--&gt;<br />&lt;%@ page import="java.net.*"%&gt;&lt;!--要用到URL类--&gt;<br />&lt;html&gt;<br />&lt;head&gt;<br />&lt;meta http-equiv="Content-Type" content="text/html; charset=gb2312"&gt;<br />&lt;title&gt;发送成功&lt;/title&gt;<br />&lt;/head&gt;<br />&lt;body&gt;<br />&lt;%<br />try{<br />String tto=request.getParameter("to");<br />String ttitle=request.getParameter("title");<br />String emailtype=request.getParameter("emailtype");//获取email类型<br />String tcontent=request.getParameter("content");<br />String tfj1=request.getParameter("fj1");<br />String tfj2=request.getParameter("fj2");<br />String tfj3=request.getParameter("fj3");</div><div>Properties props=new Properties();<br />props.put("mail.smtp.host","127.0.0.1");<br />props.put("mail.smtp.auth","true");<br />Session s=Session.getInstance(props);<br />s.setDebug(true);</div><div>MimeMessage message=new MimeMessage(s);</div><div>//给消息对象设置发件人/收件人/主题/发信时间<br />InternetAddress from=new InternetAddress("<a href="mailto:xxf@cafe.com"><font color="#000033">xxf@cafe.com</font></a>");<br />message.setFrom(from);<br />InternetAddress to=new InternetAddress(tto);<br />message.setRecipient(Message.RecipientType.TO,to);<br />message.setSubject(ttitle);<br />message.setSentDate(new Date());</div><div>Multipart mm=new MimeMultipart();//新建一个MimeMultipart对象用来存放多个BodyPart对象</div><div>//设置信件文本内容<br />BodyPart mdp=new MimeBodyPart();//新建一个存放信件内容的BodyPart对象<br />mdp.setContent(tcontent,emailtype+";charset=gb2312");//给BodyPart对象设置内容和格式/编码方式<br />mm.addBodyPart(mdp);//将含有信件内容的BodyPart加入到MimeMultipart对象中</div><div>//设置信件的附件1(自定义附件:直接将所设文本内容加到自定义文件中作为附件发送)<br />mdp=new MimeBodyPart();//新建一个存放附件的BodyPart<br />DataHandler dh=new DataHandler(tfj1,"text/plain;charset=gb2312");<br />//新建一个DataHandler对象,并设置其内容和格式/编码方式<br />mdp.setFileName("text.txt");//加上这句将作为附件发送,否则将作为信件的文本内容<br />mdp.setDataHandler(dh);//给BodyPart对象设置内容为dh<br />mm.addBodyPart(mdp);//将含有附件的BodyPart加入到MimeMultipart对象中</div><div>//设置信件的附件2(用本地上的文件作为附件)<br />mdp=new MimeBodyPart();<br />FileDataSource fds=new FileDataSource(tfj2);<br />dh=new DataHandler(fds);<br />int ddd=tfj2.lastIndexOf("\\");<br />String fname=tfj2.substring(ddd);//提取文件名<br />String ffname=new String(fname.getBytes("gb2312"),"ISO8859-1");//处理文件名是中文的情况<br />mdp.setFileName(ffname);//可以和原文件名不一致,但最好一样<br />mdp.setDataHandler(dh);<br />mm.addBodyPart(mdp);</div><div>//设置信件的附件3(用远程文件作为附件)<br />mdp=new MimeBodyPart();</div><div><br />URL urlfj=new URL(tfj3);<br />URLDataSource ur=new URLDataSource(urlfj); <br />//注:这里用的参数只能为URL对象,不能为URL字串,在前面类介绍时有误(请谅解),这里纠正一下.<br />dh=new DataHandler(ur);<br />int ttt=tfj3.lastIndexOf("/");<br />String urlname=tfj3.substring(ttt);<br />//String urlfname=new String(urlname.getBytes("gb2312"),"ISO8859-1");//不知怎么回事,这里不能处理中文问题<br />mdp.setFileName(urlname);<br />mdp.setDataHandler(dh);<br />mm.addBodyPart(mdp);</div><div>message.setContent(mm);//把mm作为消息对象的内容</div><div>message.saveChanges();<br />Transport transport=s.getTransport("smtp");<br />transport.connect("127.0.0.1","xxf","coffee");<br />transport.sendMessage(message,message.getAllRecipients());<br />transport.close();<br />%&gt;<br />&lt;div align="center"&gt;<br />&lt;p&gt;&lt;font color="#FF6600"&gt;发送成功!&lt;/font&gt;&lt;/p&gt;<br />&lt;p&gt;&lt;a href="recmail.jsp"&gt;去看看我的信箱&lt;/a&gt;&lt;br&gt;<br />&lt;br&gt;<br />&lt;a href="index.htm"&gt;再发一封&lt;/a&gt; &lt;/p&gt;<br />&lt;/div&gt;<br />&lt;%<br />}catch(MessagingException e){<br />out.println(e.toString());<br />}<br />%&gt;<br />&lt;/body&gt;<br />&lt;/html&gt;<br /></div><br /></div>
<img src ="http://www.blogjava.net/zeroone0/aggbug/38822.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zeroone0/" target="_blank">zeroone0</a> 2006-04-03 09:49 <a href="http://www.blogjava.net/zeroone0/articles/38822.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>