隨筆-314  評論-209  文章-0  trackbacks-0
           
          因為在PL/SQL 中并沒有數(shù)組. 這是偶查資料找的范例和自己寫的范例來解釋如何在PL/SQL 中使用數(shù)組. 也許很多人已知道, 不過就是讓不知道的朋友們了解一下吧。

          ---------------------- 單維數(shù)組------------------------
          DECLARE
          TYPE emp_ssn_array IS TABLE OF NUMBER
          INDEX BY BINARY_INTEGER;

          best_employees emp_ssn_array;
          worst_employees emp_ssn_array;

          BEGIN
          best_employees(1) := '123456';
          best_employees(2) := '888888';

          worst_employees(1) := '222222';
          worst_employees(2) := '666666';

          FOR i IN 1..best_employees.count LOOP
          DBMS_OUTPUT.PUT_LINE('i='|| i || ', best_employees= ' ||best_employees(i)
          || ', worst_employees= ' ||worst_employees(i));
          END LOOP;

          END;



          ---------------------- 多維數(shù)組------------------------
          DECLARE

          TYPE emp_type IS RECORD
          ( emp_id employee_table.emp_id%TYPE,
          emp_name employee_table.emp_name%TYPE,
          emp_gender employee_table.emp_gender%TYPE );

          TYPE emp_type_array IS TABLE OF
          emp_type INDEX BY BINARY_INTEGER;

          emp_rec_array emp_type_array;
          emp_rec emp_type;

          BEGIN
          emp_rec.emp_id := 300000000;
          emp_rec.emp_name := 'Barbara';
          emp_rec.emp_gender := 'Female';

          emp_rec_array(1) := emp_rec;

          emp_rec.emp_id := 300000008;
          emp_rec.emp_name := 'Rick';
          emp_rec.emp_gender := 'Male';

          emp_rec_array(2) := emp_rec;

          FOR i IN 1..emp_rec_array.count LOOP
          DBMS_OUTPUT.PUT_LINE('i='||i
          ||', emp_id ='||emp_rec_array(i).emp_id
          ||', emp_name ='||emp_rec_array(i).emp_name
          ||', emp_gender = '||emp_rec_array(i).emp_gender);
          END LOOP;

          END;
          -------------- Result --------------
          i=1, emp_id =300000000, emp_name =Barbara, emp_gender = Female
          i=2, emp_id =300000008, emp_name =Rick, emp_gender = Male



          :在PL/SQL 中是沒有數(shù)組(Array) 概念的. 但是如果程序員想用Array 的話, 就得變通一下, 用TYPE 和Table of Record 來代替多維數(shù)組, 一樣挺好用的。
          emp_type 就好象一個table 中的一條record 一樣, 里面有id, name,gender等。emp_type_array 象個table, 里面含有一條條這樣的record (emp_type),就象多維數(shù)組一樣。

          posted @ 2006-09-18 20:41 xzc 閱讀(249) | 評論 (0)編輯 收藏

          package com.xgll.util;                                

          /**
           * <p>Title: </p>
           * <p>Description: 主要提供文件和目錄操作的一些常用的方法。</p>
           * <p>Copyright: Copyright (c) 2006</p>
           * <p>Company: C-Platform</p>
           * @author wujiaqian
           * @version 1.0
           */
          import java.io.BufferedReader;
          import java.io.File;
          import java.io.FileInputStream;
          import java.io.FileOutputStream;
          import java.io.FileWriter;
          import java.io.IOException;
          import java.io.InputStream;
          import java.io.InputStreamReader;
          import java.io.PrintWriter;
          import java.util.StringTokenizer;

          public class TestUtil {
             
              public TestUtil() {
              }

                 /**
               * 新建目錄
               * @param folderPath 目錄
               * @return 返回目錄創(chuàng)建后的路徑
               */
              public String createFolder(String folderPath) {
                  String txt = folderPath;
                  try {
                      java.io.File myFilePath = new java.io.File(txt);
                      txt = folderPath;
                      if (!myFilePath.exists()) {
                          myFilePath.mkdir();
                      }
                  }
                  catch (Exception e) {
                     
                  }
                  return txt;
              }
             
              /**
               * 多級目錄創(chuàng)建
               * @param folderPath 準備要在本級目錄下創(chuàng)建新目錄的目錄路徑 例如 c:myf
               * @param paths 無限級目錄參數(shù),各級目錄以單數(shù)線區(qū)分 例如 a|b|c
               * @return 返回創(chuàng)建文件后的路徑 例如 c:myfac
               */
              public String createFolders(String folderPath, String paths){
                  String txts = folderPath;
                  try{
                      String txt;
                      txts = folderPath;
                      StringTokenizer st = new StringTokenizer(paths,"|");
                      for(int i=0; st.hasMoreTokens(); i++){
                              txt = st.nextToken().trim();
                              if(txts.lastIndexOf("/")!=-1){
                                  txts = createFolder(txts+txt);
                              }else{
                                  txts = createFolder(txts+txt+"/");   
                              }
                      }
                 }catch(Exception e){
                   
                 }
                  return txts;
              }

             
              /**
               * 新建文件
               * @param filePathAndName 文本文件完整絕對路徑及文件名
               * @param fileContent 文本文件內容
               * @return
               */
              public void createFile(String filePathAndName, String fileContent) {
              
                  try {
                      String filePath = filePathAndName;
                      filePath = filePath.toString();
                      File myFilePath = new File(filePath);
                      if (!myFilePath.exists()) {
                          myFilePath.createNewFile();
                      }
                      FileWriter resultFile = new FileWriter(myFilePath);
                      PrintWriter myFile = new PrintWriter(resultFile);
                      String strContent = fileContent;
                      myFile.println(strContent);
                      myFile.close();
                      resultFile.close();
                  }
                  catch (Exception e) {
                    
                  }
              }


              /**
               * 有編碼方式的文件創(chuàng)建
               * @param filePathAndName 文本文件完整絕對路徑及文件名
               * @param fileContent 文本文件內容
               * @param encoding 編碼方式 例如 GBK 或者 UTF-8
               * @return
               */
              public void createFile(String filePathAndName, String fileContent, String encoding) {
              
                  try {
                      String filePath = filePathAndName;
                      filePath = filePath.toString();
                      File myFilePath = new File(filePath);
                      if (!myFilePath.exists()) {
                          myFilePath.createNewFile();
                      }
                      PrintWriter myFile = new PrintWriter(myFilePath,encoding);
                      String strContent = fileContent;
                      myFile.println(strContent);
                      myFile.close();
                  }
                  catch (Exception e) {
                    
                  }
              }


              /**
               * 刪除文件
               * @param filePathAndName 文本文件完整絕對路徑及文件名
               * @return Boolean 成功刪除返回true遭遇異常返回false
               */
              public boolean delFile(String filePathAndName) {
               boolean flag = false;
                  try {
                      String filePath = filePathAndName;
                      File myDelFile = new File(filePath);
                      if(myDelFile.exists()){
                       myDelFile.delete();
                       flag = true;
                      }else{
                       flag = false;
             
                      }
                  }
                  catch (Exception e) {
             
                  }
                  return flag;
              }
             


              /**
               * 刪除文件夾
               * @param folderPath 文件夾完整絕對路徑
               * @return
               */
              public void delFolder(String folderPath) {
                  try {
                      delAllFile(folderPath); //刪除完里面所有內容
                      String filePath = folderPath;
                      filePath = filePath.toString();
                      java.io.File myFilePath = new java.io.File(filePath);
                      myFilePath.delete(); //刪除空文件夾
                  }
                  catch (Exception e) {
             
                  }
              }
             
             
              /**
               * 刪除指定文件夾下所有文件
               * @param path 文件夾完整絕對路徑
               * @return
               * @return
               */
              public boolean delAllFile(String path) {
               boolean flag = false;
                  File file = new File(path);
                  if (!file.exists()) {
                      return flag;
                  }
                  if (!file.isDirectory()) {
                      return flag;
                  }
                  String[] tempList = file.list();
                  File temp = null;
                  for (int i = 0; i < tempList.length; i++) {
                      if (path.endsWith(File.separator)) {
                          temp = new File(path + tempList[i]);
                      }else{
                          temp = new File(path + File.separator + tempList[i]);
                      }
                      if (temp.isFile()) {
                          temp.delete();
                      }
                      if (temp.isDirectory()) {
                          delAllFile(path+"/"+ tempList[i]);//先刪除文件夾里面的文件
                          delFolder(path+"/"+ tempList[i]);//再刪除空文件夾
                          flag = true;
                      }
                  }
                  return flag;
              }


              /**
               * 復制單個文件
               * @param oldPathFile 準備復制的文件源
               * @param newPathFile 拷貝到新絕對路徑帶文件名
               * @return
               */
              public void copyFile(String oldPathFile, String newPathFile) {
                  try {
                      int bytesum = 0;
                      int byteread = 0;
                      File oldfile = new File(oldPathFile);
                      if (oldfile.exists()) { //文件存在時
                          InputStream inStream = new FileInputStream(oldPathFile); //讀入原文件
                          FileOutputStream fs = new FileOutputStream(newPathFile);
                          byte[] buffer = new byte[1444];
                          while((byteread = inStream.read(buffer)) != -1){
                              bytesum += byteread; //字節(jié)數(shù) 文件大小
                              System.out.println(bytesum);
                              fs.write(buffer, 0, byteread);
                          }
                          inStream.close();
                      }
                  }catch (Exception e) {
             
                  }
              }
             

              /**
               * 復制整個文件夾的內容
               * @param oldPath 準備拷貝的目錄
               * @param newPath 指定絕對路徑的新目錄
               * @return
               */
              public void copyFolder(String oldPath, String newPath) {
                  try {
                      new File(newPath).mkdirs(); //如果文件夾不存在 則建立新文件夾
                      File a=new File(oldPath);
                      String[] file=a.list();
                      File temp=null;
                      for (int i = 0; i < file.length; i++) {
                          if(oldPath.endsWith(File.separator)){
                              temp=new File(oldPath+file[i]);
                          }else{
                              temp=new File(oldPath+File.separator+file[i]);
                          }
                          if(temp.isFile()){
                              FileInputStream input = new FileInputStream(temp);
                              FileOutputStream output = new FileOutputStream(newPath + "/" +
                              (temp.getName()).toString());
                              byte[] b = new byte[1024 * 5];
                              int len;
                              while ((len = input.read(b)) != -1) {
                                  output.write(b, 0, len);
                              }
                              output.flush();
                              output.close();
                              input.close();
                          }
                          if(temp.isDirectory()){//如果是子文件夾
                              copyFolder(oldPath+"/"+file[i],newPath+"/"+file[i]);
                          }
                      }
                  }catch (Exception e) {
             
                  }
              }


              /**
               * 移動文件
               * @param oldPath
               * @param newPath
               * @return
               */
              public void moveFile(String oldPath, String newPath) {
                  copyFile(oldPath, newPath);
                  delFile(oldPath);
              }
             

              /**
               * 移動目錄
               * @param oldPath
               * @param newPath
               * @return
               */
              public void moveFolder(String oldPath, String newPath) {
                  copyFolder(oldPath, newPath);
                  delFolder(oldPath);
              }

           /**
               * 讀取文本文件內容
               * @param filePathAndName 帶有完整絕對路徑的文件名
               * @param encoding 文本文件打開的編碼方式
               * @return 返回文本文件的內容
               */
              public String readTxt(String filePathAndName,String encoding) throws IOException{
               encoding = encoding.trim();
               StringBuffer str = new StringBuffer("");
               String st = "";
               try{
                FileInputStream fs = new FileInputStream(filePathAndName);
                InputStreamReader isr;
                if(encoding.equals("")){
                 isr = new InputStreamReader(fs);
                }else{
                 isr = new InputStreamReader(fs,encoding);
                }
                BufferedReader br = new BufferedReader(isr);
                try{
                 String data = "";
                 while((data = br.readLine())!=null){
                   str.append(data+" ");
                 }
                }catch(Exception e){
                 str.append(e.toString());
                }
                st = str.toString();
               }catch(IOException es){
                st = "";
               }
               return st;    
              }
            
          }

          posted @ 2006-09-14 16:21 xzc 閱讀(165) | 評論 (0)編輯 收藏
          主要就我所了解的J2EE開發(fā)的框架或開源項目做個介紹,可以根據(jù)需求選用適當?shù)拈_源組件進行開發(fā).主要還是以Spring為核心,也總結了一些以前web開發(fā)常用的開源工具和開源類庫
          ?
          1持久層:
          1)Hibernate
          這個不用介紹了,用的很頻繁,用的比較多的是映射,包括繼承映射和父子表映射
          對于DAO在這里介紹個在它基礎上開發(fā)的包bba96,目前最新版本是bba96 2.0它對Hibernate進行了封裝, 查詢功能包括執(zhí)行hsql或者sql查詢/更新的方法,如果你要多層次邏輯的條件查詢可以自己組裝QueryObject.可以參考它做HibernateDAO.也可以直接利用它
          2) iBATIS
          另一個ORM工具,沒有Hibernate那么集成,自由度比較大,所以使用時普遍性能上比Hibernate要快一些.
          2:SpringMVC
          ?????? 原理說明和快速入門:
          ?????? 配置文件為:
          Spring的配置文件默認為WEB-INF/xxxx-servelet.xm其中xxx為web.xml中org.springframework.web.servlet.DispatcherServlet的servlet-name。
          ?????? Action分發(fā):
          Spring將按照配置文件定義的URL,Mapping到具體Controller類,再根據(jù)URL里的action= xxx或其他參數(shù),利用反射調用Controller里對應的Action方法。
          輸入數(shù)據(jù)綁定:
          Spring提供Binder 通過名字的一一對應反射綁定Pojo,也可以直接從request.getParameter()取數(shù)據(jù)。
          輸入數(shù)據(jù)驗證
          Sping 提供了Validator接口當然還可以使用開源的Commons-Validaor支持最好
          Interceptor(攔截器)
          Spring的攔截器提供接口需要自己編寫,在這點不如WebWork做的好.全面
          ?????? (這里提一下WebWork和Struts的區(qū)別最主要的區(qū)別在于WebWork在建立一個Action時是新New一個對象而Struts是SingleMoule所有的都繼承它的一個Action,所以根據(jù)項目需要合適的選擇.)
          3:View層
          1) 標簽庫:JSP2.0/JSTL
          由于Webwork或Spring的標簽確實很有限,一般view層用JSTL標簽,而且據(jù)說JSTL設計很好速度是所有標簽中最快的使用起來也很簡單
          ?
          2) 富客戶端:DOJO Widgets, YUI(YahooUI),FCKEditor, Coolest日歷控件
          Dojo主要提供Tree, Tab等富客戶端控件,可以用其進行輔助客戶端開發(fā)
          YahooUI和DOJO一樣它有自己的一套javascript調試控制臺,主要支持ajax開發(fā)也有很多Tree,Table,Menu等富客戶端控件
          FCKEditor 最流行的文本編輯器
          Coolest日歷控件 目前很多日歷控件可用,集成在項目中也比較簡單,這個只是其中的一個,界面不錯的說..
          ?
          3) JavaScript:Prototype.js
          Prototype.js作為javascript的成功的開源框架,封裝了很多好用的功能,通過它很容易編寫AJAX應用,現(xiàn)在AJAX技術逐漸成熟,框架資源比較豐富,比如YUI,DWR等等,也是因為JavaScript沒有合適的調試工具,所以沒有必要從零開始編寫AJAX應用,個人認為多用一些成熟的Ajax框架實現(xiàn)無刷新更新頁面是不錯的選擇.
          ?
          4)表格控件:Display Tag ,Extreme Table
          這兩個的功能差不多,都是View層表格的生成,界面也比較相向,可以導出Excel,Pdf,對Spring支持很容易.
          相比較而言比較推薦ExtremeTable,它的設計很好功能上比DisplayTag多一些,支持Ajax,封裝了一些攔截器,而且最方面的是在主頁wiki中有詳細的中文使用文檔.
          ?
          5):OSCache
          OSCache是OpenSymphony組織提供的一個J2EE架構中Web應用層的緩存技術實現(xiàn)組件,Cache是一種用于提高系統(tǒng)響應速度、改善系統(tǒng)運行性能的技術。尤其是在Web應用中,通過緩存頁面的輸出結果,可以很顯著的改善系統(tǒng)的穩(wěn)定性和運行性能。
          它主要用在處理短時間或一定時間內一些數(shù)據(jù)或頁面不會發(fā)生變化,或將一些不變的統(tǒng)計報表,緩沖在內存,可以充分的減輕服務器的壓力,防治負載平衡,快速重啟服務器(通過硬盤緩存).
          ?
          6)SiteMesh
          sitemesh應用Decorator模式主要用于提高頁面的可維護性和復用性,其原理是用Filter截取request和response,把頁面組件head,content,banner結合為一個完整的視圖。通常我們都是用include標簽在每個jsp頁面中來不斷的包含各種header, stylesheet, scripts and footer,現(xiàn)在,在sitemesh的幫助下,我們刪掉他們輕松達到復合視圖模式.
          Sitemesh也是 OpenSymphony的一個項目現(xiàn)在最近的版本是2.2,目前OpenSymphony自從04年就沒有更新的版本了..感覺它還是比較有創(chuàng)新的一種頁面組裝方式, OpenSymphony開源組織的代碼一般寫的比較漂亮,可以改其源代碼對自己的項目進行適配.
          測試發(fā)現(xiàn)Sitemesh還存在一些問題,比如中文問題,它的默認編碼是iso-8859-1在使用時候需要做一些改動.
          ?
          7)CSS,XHTML
          這個不用說了,遵循W3C標準的web頁面開發(fā).
          ?
          8)分頁標簽: pager-taglib組件
          Pager-taglib?是一套分頁標簽庫,可以靈活地實現(xiàn)多種不同風格的分頁導航頁面,并且可以很好的與服務器分頁邏輯分離.使用起來也比較簡單.
          ?
          9)Form: Jodd Form taglib
          Jodd Form taglib使用比較簡單,只要把<form>的頭尾以<jodd:form bean= "mybean">包住
          就會自動綁定mybean, 自動綁定mybean的所有同名屬性到普通html標記input, selectbox, checkbox,radiobox.....在這些input框里不用再寫任何代碼…
          ??????
          10)Ajax:DWR
          ?????? J2EE應用最常用的ajax框架
          ??????
          ?????? 11)報表 圖表
          Eclipse BIRT功能比較強大,也很龐大..好幾十M,一般沒有特別需求或別的圖表設計軟件可以解決的不用它
          JasperReports+ iReport是一個基于Java的開源報表工具,它可以在Java環(huán)境下像其它IDE報表工具一樣來制作報表。JasperReports支持PDF、HTML、XLS、CSV和XML文件輸出格式。JasperReports是當前Java開發(fā)者最常用的報表工具。
          JFreeChart主要是用來制作各種各樣的圖表,這些圖表包括:餅圖、柱狀圖(普通柱狀圖以及堆棧柱狀圖)、線圖、區(qū)域圖、分布圖、混合圖、甘特圖以及一些儀表盤等等。
          ??????琴棋報表,國產的..重點推薦,適合中國的情況,開放源代碼,使用完全免費。純JAVA開發(fā),適用多種系統(tǒng)平臺。特別適合B/S結構的系統(tǒng)。官方網(wǎng)站有其優(yōu)點介紹,看來用它還是不錯的選擇,最重要的是支持國產呵呵
          ?
          4:權限控制: Acegi
          Acegi是Spring Framework 下最成熟的安全系統(tǒng),它提供了強大靈活的企業(yè)級安全服務,如完善的認證和授權機制,Http資源訪問控制,Method 調用訪問控制等等,支持CAS
          (耶魯大學的單點登陸技術,這個單點登陸方案比較出名.我也進行過配置使用,可以根據(jù)項目需要,如果用戶分布在不同的地方不同的系統(tǒng)通用一套登陸口令可以用它進行解決,一般注冊機登陸機就是這樣解決的)
          ?????? Acegi只是于Spring結合最好的安全框架,功能比較強大,當然還有一些其他的安全框架,這里列舉一些比較流行的是我從網(wǎng)上找到的,使用方法看其官方文檔把…
          JAAS, Seraph, jSai - Servlet Security, Gabriel, JOSSO, Kasai, jPAM, OpenSAML都是些安全控制的框架..真夠多的呵呵
          ?
          5:全文檢索
          ?????? 1) Lucene
          ?????? Lucene是一套全文索引接口,可以通過它將數(shù)據(jù)進行倒排文件處理加入索引文件,它的索引速度和查詢速度是相當快的,查詢百萬級數(shù)據(jù)毫秒級出結果,現(xiàn)在最火的Apache開源項目,版本更新速度很快現(xiàn)在已經到了2.0,每個版本更新的都比較大,目前用的最多的版本應該是1.4.3,但它有個不太方面的地方單個索引文件有2G文件限制,現(xiàn)在2.0版本沒有這個限制,我研究的比較多,它的擴展性比較好,可以很方面的擴充其分詞接口和查詢接口.
          ?????? 基于它的開發(fā)的系統(tǒng)很多,比如最常用的Eclipse的搜索功能,還有一些開源的軟件比如Compass,Nutch,Lius,還有我最近做的InSearch(企業(yè)級FTP文件網(wǎng)頁搜索)
          6:公共Util類
          ?????? 主要是Jakarta-Commons類庫,其中最常用得是以下幾個類庫
          1) Jakarta-Commons-Language
          ?????? 最常用得類是StringUtils類,提供了使用的字符串處理的常用方法效率比較高
          2) Jakarta-Commons-Beantuils
          ?????? 主要用Beantuils能夠獲得反射函數(shù)封裝及對嵌套屬性,map,array型屬性的讀取。
          3) Jakarta-Commons-Collections
          ?????? 里面有很多Utils方法
          ?
          7 日志管理
          ?????? Log4J
          ?????? 任務是日志記錄,分為Info,Warn,error幾個層次可以更好的調試程序
          ?
          8 開源的J2EE框架
          ?????? 1) Appfuse
          ????????????? Appfuse是Matt Raible 開發(fā)的一個指導性的入門級J2EE框架, 它對如何集成流行的Spring、Hibernate、iBatis、Struts、Xdcolet、JUnit等基礎框架給出了示范. 在持久層,AppFuse采用了Hibernate O/R映射工具;在容器方面,它采用了Spring,用戶可以自由選擇Struts、Spring/MVC,Webwork,JSF這幾個Web框架。
          ??????
          ?????? 2) SpringSide
          ?????? .SpringSide較完整的演示了企業(yè)應用的各個方面,是一個電子商務網(wǎng)站的應用 SpringSide也大量參考了Appfuse中的優(yōu)秀經驗。最重要的是它是國內的一個開源項目,可以了解到國內現(xiàn)在的一些實際技術動態(tài)和方向很有指導意義…
          ?
          9:模版 Template
          主要有Veloctiy和Freemarker
          模板用Servlet提供的數(shù)據(jù)動態(tài)地生成 HTML。編譯器速度快,輸出接近靜態(tài)HTML???????????? 頁面的速度。
          ?
          10:工作流
          ?????? 我所知道比較出名的主要有JBpm Shark Osworkflow,由于對它沒有過多的研究所以還不是很清楚之間有什么區(qū)別.
          ?
          項目管理軟件
          dotProject:是一個基于LAMP的開源項目管理軟件。最出名的項目管理軟件
          JIRA: 項目計劃,任務安排,錯誤管理
          Bugzilla:提交和管理bug,和eclipse集成,可以通過安裝MyEclipse配置一下即可使用
          BugFree借鑒微軟公司軟件研發(fā)理念、免費開放源代碼、基于Web的精簡版Bug管理
          CVS:這個就不介紹了都在用.
          SVN: SubVersion已逐漸超越CVS,更適應于JavaEE的項目。Apache用了它很久后,Sourceforge剛剛推出SVN的支持。
          測試用例:主要JUnit單元測試,編寫TestCase,Spring也對Junit做了很好的支持
          ?
          后記:
          ?????? 以Spring為主的應用開發(fā)可選用的組件中間件真是眼花繚亂,所以針對不同的項目需求可以利用不同的開源產品解決,比如用Spring+Hibernate/ iBATIS或Spring+WebWork+Hibernate/ iBATIS或Spring+Struts+Hibernate/ iBATIS,合理的框架設計和代碼復用設計對項目開發(fā)效率和程序性能有很大的提高,也有利于后期的維護.
          posted @ 2006-09-11 13:41 xzc 閱讀(439) | 評論 (0)編輯 收藏
          本文將闡述如何用POI來讀取/寫入完整的Excel文件。
            
            約定:POI項目2.0版現(xiàn)在已經接近正式發(fā)行階段,開發(fā)進度迅速,不斷有新的功能集成到原有的系統(tǒng),同時也有對原有系統(tǒng)的修改。
            
            為了保證本文的及時性,本文將按照最近的1.9開發(fā)版說明。雖然編譯最近的發(fā)行版源代碼也能正常運行,但現(xiàn)在的代碼和2.0的發(fā)行版會有一些出入。
            
            一、Excel基礎
            
            Microsoft Excel 97文件格式也被稱為BIFF8,最近版本的Excel只對該格式作了少量的改動。增加對新格式的支持除了增加項目的復雜性之外,唯一的效果也許只是不得不使每個用戶升級代碼,沒有什么實際的好處。
            
            因此,在下文說明中,凡是提到Excel 97格式的地方其實都是指Excel從97到XP的格式。
            
            二、HSSF概況
            
            POI項目實現(xiàn)的Excel 97文件格式稱為HSSF??也許你已經猜到,HSSF是Horrible SpreadSheet Format的縮寫,也即“討厭的電子表格格式”(微軟使某些原本簡單的事情過分復雜,同時又過分簡單地處理了某些原本需要靈活性的事情,讓人不勝佩服!)
            
            也許HSSF的名字有點滑稽,就本質而言它是一個非常嚴肅、正規(guī)的API。通過HSSF,你可以用純Java代碼來讀取、寫入、修改Excel文件。
            
            前面一篇文章提到了POIFS,那么HSSF和POIFS又有什么關系呢?就象其他POI的API一樣,HSSF建立在POIFS的基礎上,因此在HSSF內的有些代碼和前文的某些代碼很相似。不過,當我們編寫基于HSSF API的代碼時,一般不需要了解POIFS API的細節(jié)。
            
            HSSF為讀取操作提供了兩類API:usermodel和eventusermodel,即“用戶模型”和“事件-用戶模型”。前者很好理解,后者比較抽象,但操作效率要高得多。usermodel主要有org.apache.poi.hssf.usermodel和org.apache.poi.hssf.eventusermodel包實現(xiàn)(在HSSF的早期版本中,org.apache.poi.hssf.eventusermodel屬于eventmodel包)。
            
            usermodel包把Excel文件映射成我們熟悉的結構,諸如Workbook、Sheet、Row、Cell等,它把整個結構以一組對象的形式保存在內存之中。eventusermodel要求用戶熟悉文件格式的底層結構,它的操作風格類似于XML的SAX API和AWT的事件模型(這就是eventusermodel名稱的起源),要掌握竅門才能用好。
            
            另外,eventusermodel的API只提供讀取文件的功能,也就是說不能用這個API來修改文件。
            
            三、通過usermodel讀取文件
            
            用HSSF的usermodel讀取文件很簡單。首先創(chuàng)建一個InputStream,然后創(chuàng)建一個HSSFWorkbook:
            
            InputStream myxls = new FileInputStream("workbook.xls"));
            HSSFWorkbook wb   = new HSSFWorkbook(myxls);
            
            有了HSSFWorkbook實例,接下來就可以提取工作表、工作表的行和列,例如:
            
            HSSFSheet sheet = wb.getSheetAt(0);    // 第一個工作表
            HSSFRow row   = sheet.getRow(2);    // 第三行
            HSSFCell cell  = row.getCell((short)3); // 第四個單元格
            
            上面這段代碼提取出第一個工作表第三行第四單元格。利用單元格對象可以獲得它的值,提取單元格的值時請注意它的類型:
            
            if (cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
            ("單元格是字符串,值是: " + cell.getStringCellValue());
            } else if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC) {
            ("單元格是數(shù)字,值是: " + cell.getCellValue());
            } else () {
            ("單元格的值不是字符串或數(shù)值。");
            }
            
            如果搞錯了數(shù)據(jù)類型,程序將遇到異常。特別地,用HSSF處理日期數(shù)據(jù)要小心。Excel內部以數(shù)值的形式保存日期數(shù)據(jù),區(qū)別日期數(shù)據(jù)的唯一辦法是通過單元格的格式(如果你曾經在Excel中設置過日期格式,應該明白這是什么意思)。
            
            因此,對于包含日期數(shù)據(jù)的單元格,cell.getCellType()將返回HSSFCell.CELL_TYPE_NUMERIC,不過利用工具函數(shù)HSSFDateUtil.isCellDateFormatted(cell)可以判斷出單元格的值是否為日期。isCellDateFormatted函數(shù)通過比較單元格的日期和Excel的內置日期格式得出結論??可以想象,按照這種判斷方法,很多時候isCellDateFormatted函數(shù)會返回否定的結論,存在一定的誤判可能。
            
            本文附錄包含了一個在Servlet環(huán)境中利用HSSF創(chuàng)建和返回Excel工作簿的實例。
            
            四、通過usermodel寫入文件
            
            寫入XLS文件比讀取XLS文件還要簡單。創(chuàng)建一個HSSFWorkbook實例,然后在適當?shù)臅r候創(chuàng)建一個把文件寫入磁盤的OutputStream,但延遲到處理結束時創(chuàng)建OutputStream也可以:
            
            HSSFWorkbook wb = new HSSFWorkbook();
            FileOutputStream fileOut
            = new FileOutputStream("workbook.xls");
            wb.write(fileOut);
            fileOut.close();
            
            創(chuàng)建工作表及其內容必須從相應的父對象出發(fā),例如:
            
            HSSFSheet sheet = wb.createSheet();
            HSSFRow row   = sheet.createRow((short)0);
            HSSFCell cell  = row.createCell((short)0);
            cell.setCellValue(1);
            row.createCell((short)1).setCellValue(1.2);
            row.createCell((short)2).setCellValue("一個字符串");
            row.createCell((short)3).setCellValue(true);
            
            如果要設置單元格的樣式,首先要創(chuàng)建一個樣式對象,然后把它指定給一個單元格??或者把它指定給多個具有相同樣式的單元格,例如,如果Excel表格中有一個摘要行,摘要行的數(shù)據(jù)必須是粗體、斜體,你可以創(chuàng)建一個summaryRowStyle樣式對象,然后把這個樣式指定給所有摘要行上的單元格。
            
            注意,CellFormat和CellStyle對象是工作簿對象的成員,單元格對象只是引用它們。
            ...
            HSSFCellStyle style = workbook.createCellStyle();
            style.setDataFormat
            (HSSFDataFormat.getBuiltinFormat("($#,##0_);[Red]($#,##0)"));
            style.setFillBackgroundColor(HSSFColor.AQUA.index);
            style.setFillPattern(HSSFCellStyle.BIG_SPOTS);
            ...
            someCell.setCellStyle(style);
            someOtherCell.setCellStyle(style);
            
            版本較新的HSSF允許使用數(shù)量有限的Excel公式。這一功能目前還是“Beta級質量”,正式使用之前務必仔細測試。指定公式的方式類如:someCell.setCellFormula(SUM(A1:A2:);。
            
            當前,公式中已經可以調用所有內建的函數(shù)或操作符,但邏輯操作符和函數(shù)(例如IF函數(shù))除外,這部分功能目前還在開發(fā)之中。

          五、通過eventusermodel讀取文件
            
            通過eventusermodel讀取文件要比使用usermodel復雜得多,但效率也要高不少,因為它要求應用程序一邊讀取數(shù)據(jù),一邊處理數(shù)據(jù)。
            
            eventusermodel實際上模擬了DOM環(huán)境下SAX處理XML文檔的辦法,應用程序首先要注冊期望處理的數(shù)據(jù),eventusermodel將在遇到匹配的數(shù)據(jù)結構時回調應用程序注冊的方法。使用eventusermodel最大的困難在于你必須熟悉Excel工作簿的內部結構。
            
            在HSSF中,低層次的二進制結構稱為記錄(Record)。記錄有不同的類型,每一種類型由org.apache.poi.hssf.record包中的一個Java類描述。例如,BOFRecord記錄表示W(wǎng)orkbook或Sheet區(qū)域的開始,RowRecord表示有一個行存在并保存其樣式信息。
            
            所有具有CellValueRecordInterface接口的記錄表示Excel的單元格,包括NumericRecord、LabelSSTRecord和FormulaRecord(還有其他一些,其中部分已被棄置不用,部分用于優(yōu)化處理,但一般而言,HSSF可以轉換它們)。
            
            下面是一個注冊事件處理句柄的例子:
            
            private EventRecordFactory factory = new EventRecordFactory();
            factory.registerListener(new ERFListener() {
            public boolean processRecord(Record rec) {
            (got BOF Record);
            return true;
            }
            }, new short[] {BOFRecord.sid});
            factory.processRecords(someInputStream);
            
            六、HSSF電子表格結構
            
            如前所述,HSSF建立在POIFS的基礎上。具體地說,Excel 97+文件是OLE 2復合文檔( OLE 2 Compound Document),底層的OLE 2復合文檔保存了一個總是命名為Workbook(Excel 95除外,HSSF不支持Excel 95)的流。
            
            然而,宏和圖片并不保存在Workbook流,它們有自己獨立的流,有時甚至會放到OLE 2 CDF文件之內的另一個目錄。理想情況下,宏也應該被保留,不過目前POI項目中還沒有合適的API來處理宏。
            
            每一個流之內是一組記錄,一個記錄其實就是一個字節(jié)數(shù)組,可分為記錄頭、記錄體兩部分。記錄頭指明了記錄的類型(也即ID)以及后繼數(shù)據(jù)的長度,記錄體被分割成多個字段(Field),字段包含數(shù)值數(shù)據(jù)(包括對其他記錄的引用)、字符數(shù)據(jù)或標記。
            
            Excel工作簿的頂級結構:
            
            Bla.xls {
            OLE2CDF headers
            "Workbook" stream {
            Workbook {
            Static String Table Record..
            Sheet names… and pointers
            }
            Sheet {
            ROW
            ROW
            …
            NUMBER RECORD (cell)
            LABELSST Record (cell)
            …
            }
            Sheet
            }
            }
            … images, macros, etc.
            Document Summary
            Summary
            
            七、通過HPSF讀取文檔屬性
            
            在Microsoft Word、Excel、PowerPoint等軟件中,用戶可以通過“文件”→“屬性”菜單給文檔添加附加信息,包括文檔的標題、主題、摘要、類別、關鍵詞等,同時應用軟件本身還會加入最后訪問的用戶、最后訪問和修改/打印的日期時間等信息。
            
            文檔的屬性和正文是分開保存的。如前所述,OLE 2 CDF文件內部就象是一個容器,里面包含許多類似目錄和文件的結構,而POIFS就是用來訪問其中的文件的工具。這些文件也稱為流,文檔的屬性就保存在POIFS文件系統(tǒng)中專用的流里面。
            
            以一個Word文檔為例:雖然在資源管理器中你只看到一個叫做MyFile.doc的文檔,其實在這個文檔的內部,又包含了一個WordDocument、一個SummaryInformation和一個DocumentSummaryInformation文檔;通常還會有其他的文檔,這里暫且不管。
            
            你能夠猜出這些文檔(流)分別包含什么內容嗎?不錯,WordDocument包含了你在Word里面編輯的文本,文檔的屬性保存在SummaryInformation和DocumentSummaryInformation流里面。也許將所有屬性保存在單個文檔里面看起來太簡單了,所以Microsoft決心要使用兩個流,為了使事情更復雜一點,這兩個流的名字前面還加上了八進制的\005字符??這是一個不可打印的字符,因此前面就把它省略了。
            
            Microsoft定義的標準屬性有一個好處,它們并不在乎主文檔到底是什么類型??不管是Word文檔、Excel工作簿還是PowerPoint幻燈。只要你知道如何讀取Excel文檔的屬性,就知道了如何讀取其他文檔的屬性。
            
            讀取文檔屬性其實并不復雜,因為Java程序可以利用POI項目的HPSF包。HPSF是 Horrible Property Set Format的縮寫,譯成中文就是“討厭的屬性集格式”。HPSF包是POI項目實現(xiàn)的讀取屬性工具,目前還不支持屬性寫入。
            
            對于讀取Microsoft定義的標準屬性,通過HPSF提供的API可以很方便地辦到;但如果要讀取任意屬性集就要用到更一般化的API,可以想象它要比讀取標準屬性的API復雜不少。本文只介紹讀取標準屬性的簡單API,因為對大多數(shù)應用程序來說這已經完全足夠了。
            
            下面就是一個讀取OLE 2 CDF文檔的標題(title)屬性的Java程序:
            
            import java.io.*;
            import org.apache.poi.hpsf.*;
            import org.apache.poi.poifs.eventfilesystem.*;
            
            /**
            * 讀取OLE 2文檔標題的示例程序,
            * 在命令行參數(shù)中指定文檔的文件名字。
            */
            
            public class ReadTitle
            {
            public static void main(String[] args) throws IOException
            {
            final String filename = args[0];
            POIFSReader r     = new POIFSReader();
            r.registerListener(new MyPOIFSReaderListener(),
            "\005SummaryInformation");
            r.read(new FileInputStream(filename));
            }
            
            static class MyPOIFSReaderListener
            implements POIFSReaderListener
            {
            public void processPOIFSReaderEvent(POIFSReaderEvent event)
            {
            SummaryInformation si = null;
            try
            {
            si = (SummaryInformation)
            PropertySetFactory.create(event.getStream());
            }
            catch (Exception ex)
            {
            throw new RuntimeException
            ("屬性集流\"" + event.getPath() +
            event.getName() + "\": " + ex);
            }
            
            final String title = si.getTitle();
            
            if (title != null)
            System.out.println("標題: \"" + title + "\"");
            else
            System.out.println("該文檔沒有標題.");
            }
            }
            }

           main()方法利用POIFS的事件系統(tǒng)從命令行指定的OLE 2文檔讀取名為\005SummaryInformation的流,當POIFSReader 遇到這個流時,它把控制傳遞給MyPOIFSReaderListener的processPOIFSReaderEvent()方法。
            
            processPOIFSReaderEvent()到底有什么用呢?它通過參數(shù)獲得一個輸入流,該輸入流包含了文檔標題等屬性。為了訪問文檔的屬性,我們從輸入流創(chuàng)建一個PropertySet實例,如下所示:
            
            si = (SummaryInformation) PropertySetFactory.create(event.getStream());
            
            這個語句其實包含三個步驟的操作:
            
            ◆ event.getStream()從POIFSReader傳入的POIFSReaderEvent獲得輸入流。
            
            ◆ 以剛才獲得的輸入流為參數(shù),調用PropertySetFactory的靜態(tài)方法create()。正如其名字所暗示的,PropertySetFactory是一個工廠類,它有一臺“機器”能夠把一個輸入流轉換成一個PropertySet實例,這臺機器就是create()方法。
            
            ◆ 把create()方法返回的PropertySet定型(cast)成為SummaryInformation。PropertySet提供了按照一般辦法讀取屬性集的各種機制,SummaryInformation是PropertySet的子類,即SummaryInformation類在PropertySet類的基礎上增加了操作Microsoft標準屬性的便捷方法。
            
            在這個處理過程中,可能引起錯誤的因素很多,因此我們把這部分內容放入了一個try塊,不過這個示例程序只按照最簡單的方式處理了異常,在實際應用中,最好能夠對可能出現(xiàn)的不同異常類型分別處理。
            
            除了一般的I/O異常之外,還有可能遇到HPSF特有的異常,例如,如果輸入流不包含屬性集或屬性集非法,就會拋出NoPropertySetStreamException異常。
            
            有一種錯誤不太常見,但也不是絕無可能\005SummaryInformation包含一個合法的屬性集,但不是摘要信息屬性集。如果出現(xiàn)這種情況,則定型成SummaryInformation操作會失敗,引發(fā)ClassCastException異常。
            
            獲得SummaryInformation實例之后,剩下的事情就很簡單了,只要調用getTitle()方法,然后輸出結果。
            
            除了getTitle()之外,SummaryInformation還包含其他一些便捷方法,例如getApplicationName()、getAuthor()、getCharCount()、和getCreateDateTime()等。HPSF的JavaDoc文檔詳細說明了所有這些方法。
            
            八、文檔摘要信息
            
            遺憾的是,并非所有的屬性都保存在摘要信息屬性集之中。許多(但不是全部)OLE 2文件還有另一個屬性集,稱為“文檔摘要信息”,對應的流是\005DocumentSummaryInformation。這個屬性集保存的屬性包括文檔的類別、PowerPoint幻燈的多媒體剪輯數(shù)量,等等。
            
            要訪問文檔摘要信息屬性集,程序的處理過程也和上例相似,只是注冊的目標應該改成\005DocumentSummaryInformation有時,你可能想要同時注冊到摘要信息和文檔摘要信息這兩個流。其余的處理方式和前面的例子差不多,你應該把包含文檔摘要信息的流傳遞給PropertySetFactory.create(),但這次工廠方法將返回一個DocumentSummaryInformation對象(而不是前面例子中的SummaryInformation對象)。
            
            如果同時注冊到了兩個流,注意檢查返回值的具體類型,或者使用Java的instanceof操作符,或者使用專用的isSummaryInformation()和isDocumentSummaryInformation()方法。記住,create()方法返回的總是一個PropertySet對象,因此你總是可以對create()返回對象調用isSummaryInformation()和isDocumentSummaryInformation()方法,PropertySet類之所以要提供這兩個方法,是因為屬性集可能是自定義的。
            
            如果你想要處理自定義的屬性集,或者要從標準的屬性集讀取用戶定義的屬性,必須使用一個更一般化的API,前面已經提到,這個API要復雜得多,本文不再討論,請參見HPSF的HOW-TO文檔和POI的文檔。
            
            結束語:本文探討了HSSF的應用以及如何輸出到Excel文件,另外還涉及了HPSF以及如何讀取屬性集文檔摘要信息。POI是一個功能非常強大的項目,許多主題本文尚未涉及,例如如何用HSSF Serializer將XML文檔轉換成Excel格式等,這一切仍有待您去研究了。
            
            九、附錄
            
            實例:利用Servlet創(chuàng)建和返回一個工作簿。
            
            package org.apache.poi.hssf.usermodel.examples;
            
            import java.io.*;
            import java.net.*;
            import javax.servlet.*;
            import javax.servlet.http.*;
            import org.apache.poi.hssf.usermodel.*;
            
            public class HSSFCreate extends HttpServlet {
            public void init(ServletConfig config)
            throws ServletException {
            super.init(config);
            }
            
            public void destroy() {
            }
            
            /** 處理HTTP GET 和POST請求
            * @param request:請求
            * @param response:應答
            */
            protected void processRequest(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
            
            response.setContentType("application/vnd.ms-excel");
            HSSFWorkbook wb = new HSSFWorkbook();
            HSSFSheet sheet = wb.createSheet("new sheet");
            
            // 創(chuàng)建一個新的行,添加幾個單元格。
            // 行號從0開始計算
            HSSFRow row   = sheet.createRow((short)0);
            // 創(chuàng)建一個單元格,設置單元格的值
            HSSFCell cell  = row.createCell((short)0);
            cell.setCellValue(1);
            
            row.createCell((short)1).setCellValue(1.2);
            row.createCell((short)2).setCellValue("一個字符串值");
            row.createCell((short)3).setCellValue(true);
            // 寫入輸出結果
            OutputStream out = response.getOutputStream();
            wb.write(out);
            out.close();
            }
            
            /** 處理HTTP GET請求
            * @param request:請求
            * @param response:應答
            */
            protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
            processRequest(request, response);
            }
            
            /** 處理HTTP POST請求
            * @param request:請求
            * @param response:應答
            */
            protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
            processRequest(request, response);
            }
            
            /** 返回關于Servlet的簡單說明
            */
            public String getServletInfo() {
            return "示例:在Servlet中用HSSF創(chuàng)建Excel工作簿";
            }
            }
          posted @ 2006-09-09 11:45 xzc 閱讀(416) | 評論 (0)編輯 收藏
           只要有表格,就會有Microsoft Excel,用Microsoft Excel處理數(shù)據(jù)已成為不少人的習慣。Jakarta POI API就為Java程序員提供了一條存取Microsoft文檔格式的神奇之路,其中最成熟的就是能存取Microsoft Excel文檔的HSSF API。

            本篇文章就舉例示范如何利用Java 創(chuàng)建和讀取Excel文檔,并設置單元格的字體和格式。

            為了保證示例程序的運行,必須安裝Java 2 sdk1.4.0 和Jakarta POI,Jakarta POI的Web站點是: http://jakarta.apache.org/poi/

            創(chuàng)建Excel 文檔

            示例1將演示如何利用Jakarta POI API 創(chuàng)建Excel 文檔。

            示例1程序如下:

          import org.apache.poi.hssf.usermodel.HSSFWorkbook;
          import org.apache.poi.hssf.usermodel.HSSFSheet;
          import org.apache.poi.hssf.usermodel.HSSFRow;
          import org.apache.poi.hssf.usermodel.HSSFCell;
          import java.io.FileOutputStream;
          public class CreateXL {

           /** Excel 文件要存放的位置,假定在D盤JTest目錄下*/

           public static String outputFile="D:/JTest/ gongye.xls";

           public static void main(String argv[]){

           try{

            // 創(chuàng)建新的Excel 工作簿

            HSSFWorkbook workbook = new HSSFWorkbook();

            // 在Excel工作簿中建一工作表,其名為缺省值
                // 如要新建一名為"效益指標"的工作表,其語句為:
                // HSSFSheet sheet = workbook.createSheet("效益指標");

            HSSFSheet sheet = workbook.createSheet();

            // 在索引0的位置創(chuàng)建行(最頂端的行)

            HSSFRow row = sheet.createRow((short)0);

            //在索引0的位置創(chuàng)建單元格(左上端)
            HSSFCell cell = row.createCell((short) 0);
            // 定義單元格為字符串類型
            cell.setCellType(HSSFCell.CELL_TYPE_STRING);
            // 在單元格中輸入一些內容
            cell.setCellValue("增加值");
            // 新建一輸出文件流
            FileOutputStream fOut = new FileOutputStream(outputFile);
            // 把相應的Excel 工作簿存盤
            workbook.write(fOut);
            fOut.flush();
            // 操作結束,關閉文件
            fOut.close();
            System.out.println("文件生成...");

           }catch(Exception e) {
            System.out.println("已運行 xlCreate() : " + e );
           }
          }
          }
            讀取Excel文檔中的數(shù)據(jù)

            示例2將演示如何讀取Excel文檔中的數(shù)據(jù)。假定在D盤JTest目錄下有一個文件名為gongye.xls的Excel文件。

            示例2程序如下:

          import org.apache.poi.hssf.usermodel.HSSFWorkbook;
          import org.apache.poi.hssf.usermodel.HSSFSheet;
          import org.apache.poi.hssf.usermodel.HSSFRow;
          import org.apache.poi.hssf.usermodel.HSSFCell;
          import java.io.FileInputStream;
          public class ReadXL {
           /** Excel文件的存放位置。注意是正斜線*/
           public static String fileToBeRead="D:/JTest/ gongye.xls";
           public static void main(String argv[]){
           try{
            // 創(chuàng)建對Excel工作簿文件的引用
            HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(fileToBeRead));
            // 創(chuàng)建對工作表的引用。
            // 本例是按名引用(讓我們假定那張表有著缺省名"Sheet1")
            HSSFSheet sheet = workbook.getSheet("Sheet1");
            // 也可用getSheetAt(int index)按索引引用,
            // 在Excel文檔中,第一張工作表的缺省索引是0,
            // 其語句為:HSSFSheet sheet = workbook.getSheetAt(0);
            // 讀取左上端單元
            HSSFRow row = sheet.getRow(0);
            HSSFCell cell = row.getCell((short)0);
            // 輸出單元內容,cell.getStringCellValue()就是取所在單元的值
            System.out.println("左上端單元是: " + cell.getStringCellValue());
           }catch(Exception e) {
            System.out.println("已運行xlRead() : " + e );
           }
          }
          }
            設置單元格格式

            在這里,我們將只介紹一些和格式設置有關的語句,我們假定workbook就是對一個工作簿的引用。在Java中,第一步要做的就是創(chuàng)建和設置字體和單元格的格式,然后再應用這些格式:

            1、創(chuàng)建字體,設置其為紅色、粗體:

          HSSFFont font = workbook.createFont();
          font.setColor(HSSFFont.COLOR_RED);
          font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
            2、創(chuàng)建格式

          HSSFCellStyle cellStyle= workbook.createCellStyle();
          cellStyle.setFont(font);
            3、應用格式

          HSSFCell cell = row.createCell((short) 0);
          cell.setCellStyle(cellStyle);
          cell.setCellType(HSSFCell.CELL_TYPE_STRING);
          cell.setCellValue("標題 "); 
          posted @ 2006-09-09 11:44 xzc 閱讀(258) | 評論 (0)編輯 收藏
          一、PL/SQL出現(xiàn)的目的

            結構化查詢語言(Structured Query Language,簡稱SQL)是用來訪問關系型數(shù)據(jù)庫一種通用語言,它屬于第四代語言(4GL),其執(zhí)行特點是非過程化,即不用指明執(zhí)行的具體方法和途徑,而是簡單的調用相應語句來直接取得結果即可。顯然,這種不關注任何實現(xiàn)細節(jié)的語言對于開發(fā)者來說有著極大的便利。 然而,對于有些復雜的業(yè)務流程又要求相應的程序來描述,那么4GL就有些無能為力了。PL/SQL的出現(xiàn)正是為了解決這一問題,PL/SQL是一種過程化語言,屬于第三代語言,它與C,C++,Java等語言一樣關注于處理細節(jié),因此可以用來實現(xiàn)比較復雜的業(yè)務邏輯。

            本教程分兩部分,第一部分主要對PL/SQL的編程基礎進行討論,第二部分結合一個案例來講解PL/SQL編程。希望讀者閱讀本文后能夠對PL/SQL編程有一個總體上的認識,為今后深入PL/SQL編程打下一個基礎。

            二、PL/SQL編程基礎

            掌握一門編程語言首要是要了解其基本的語法結構,即程序結構、數(shù)據(jù)類型、控制結構以及相應的內嵌函數(shù)(或編程接口)。

            1、PL/SQL程序結構

            PL/SQL程序都是以塊(block)為基本單位。如下所示為一段完整的PL/SQL塊:

          /*聲明部分,以declare開頭*/
          declare
          v_id integer;
          v_name varchar(20);
          cursor c_emp is select * from employee where emp_id=3;
          /*執(zhí)行部分,以begin開頭*/
          begin
           open c_emp;             --打開游標
           loop
            fetch c_emp into v_id,v_name;  --從游標取數(shù)據(jù)
            exit when c_emp%notfound ;
           end loop ;
          close c_emp;           --關閉游標
          dbms_output.PUT_LINE(v_name);
          /*異常處理部分,以exception開始*/
          exception
           when no_data_found then
            dbms_output.PUT_LINE('沒有數(shù)據(jù)');
          end ;

            從上面的PL/SQL程序段看出,整個PL/SQL塊分三部分:聲明部分(用declare開頭)、執(zhí)行部分(以begin開頭)和異常處理部分(以exception開頭)。其中執(zhí)行部分是必須的,其他兩個部分可選。無論PL/SQL程序段的代碼量有多大,其基本結構就是由這三部分組成。

            2、變量聲明與賦值

            PL/SQL主要用于數(shù)據(jù)庫編程,所以其所有數(shù)據(jù)類型跟oracle數(shù)據(jù)庫里的字段類型是一一對應的,大體分為數(shù)字型、布爾型、字符型和日期型。為方便理解后面的例程,這里簡單介紹兩種常用數(shù)據(jù)類型:number、varchar2。

             number

            用來存儲整數(shù)和浮點數(shù)。范圍為1E-130 ~10E125,其使用語法為:

          number[(precision, scale)]

            其中(precision, scale)是可選的,precision表示所有數(shù)字的個數(shù),scale表示小數(shù)點右邊數(shù)字的個數(shù)。

             varchar2

            用來存儲變長的字符串,其使用語法為:

          varchar2[(size)]

            其中size為可選,表示該字符串所能存儲的最大長度。

            在PL/SQL中聲明變量與其他語言不太一樣,它采用從右往左的方式聲明,比如聲明一個number類型的變量v_id,那其形式應為:

          v_id number;

            如果給上面的v_id變量賦值,不能用”=”應該用”:=”,即形式為:

          v_id :=5;
          3、控制結構

            PL/SQL程序段中有三種程序結構:條件結構、循環(huán)結構和順序結構。

             條件結構

            與其它語言完全類似,語法結構如下:

          if condition then
          statement1
          else
          statement2
          end if ;

             循環(huán)結構

            這一結構與其他語言不太一樣,在PL/SQL程序中有三種循環(huán)結構:

          a. loop … end loop;
          b. while condition loop … end loop;
          c. for variable in low_bound . . upper_bound loop … end loop;

            其中的“…”代表循環(huán)體。

             順序結構

            實際就是goto的運用,不過從程序控制的角度來看,盡量少用goto可以使得程序結構更加的清晰。

            4、SQL基本命令

            PL/SQL使用的數(shù)據(jù)庫操作語言還是基于SQL的,所以熟悉SQL是進行PL/SQL編程的基礎。表1-1為SQL語言的分類。

            表1-1 SQL語言分類

          類別SQL語句
          數(shù)據(jù)定義語言(DDL)Create ,Drop,Grant,Revoke, …
          數(shù)據(jù)操縱語言(DML)Update,Insert,Delete, …
          數(shù)據(jù)控制語言(DCL)Commit,Rollback,Savapoint, …
          其他Alter System,Connect,Allocate, …

            可以參閱其他關于SQL語言的資料來了解具體的語法結構,這里就不多贅述了。
          三、過程與函數(shù)

            PL/SQL中的過程和函數(shù)與其他語言的過程和函數(shù)的概念一樣,都是為了執(zhí)行一定的任務而組合在一起的語句。過程無返回值,函數(shù)有返回值。其語法結構為:
            過程:Create or replace procedure procname(參數(shù)列表) as PL/SQL語句塊

            函數(shù):Create or replace function funcname(參數(shù)列表) return 返回值 as PL/SQL語句塊

            這里為了更為方面的說明過程的運用,下面給出一個示例:

            問題:假設有一張表t1,有f1和f2兩個字段,f1為number類型,f2為varchar2類型,然后往t1里寫兩條記錄,內容自定。

          Create or replace procedure test_procedure as
          V_f11 number :=1; /*聲明變量并賦初值*/
          V_f12 number :=2;
          V_f21 varchar2(20) :=’first’;
          V_f22 varchar2(20) :=’second’;
          Begin
          Insert into t1 values (V_f11, V_f21);
          Insert into t1 values (V_f12, V_f22);
          End test_procedure; /*test_procedure可以省略*/

            至此,test_procedure存儲過程已經完成,然后經過編譯后就可以在其他PL/SQL塊或者過程中調用了。由于函數(shù)與過程具有很大的相似性,所以這里就不再重復了。

            四、游標

            這里特別提出游標的概念,是因為它在PL/SQL的編程中非常的重要。其定義為:用游標來指代一個DML SQL操作返回的結果集。即當一個對數(shù)據(jù)庫的查詢操作返回一組結果集時,用游標來標注這組結果集,以后通過對游標的操作來獲取結果集中的數(shù)據(jù)信息。定義游標的語法結構如下:

          cursor cursor_name is SQL語句;

            在本文第一段代碼中有一句話如下:

          cursor c_emp is select * from employee where emp_id=3;

            其含義嵌ㄒ逡桓鲇偽阠_emp,其代表著employee表中所有emp_id字段為3的結果集。當需要操作該結果集時,必須完成三步:打開游標、使用fetch語句將游標里的數(shù)據(jù)取出、關閉游標。請參照本文第一段代碼的注釋理解游標操作的三步驟。

            五、其他概念

            PL/SQL中包的概念很重要,主要是對一組功能相近的過程和函數(shù)進行封裝,類似于面向對象中的名字空間的概念。

            觸發(fā)器是一種特殊的存儲過程,其調用者比較特殊,是當發(fā)生特定的事件才被調用,主要用于多表之間的消息通知。

            六、調試環(huán)境

            PL/SQL的調試環(huán)境目前比較多,除了Oracle自帶有調試環(huán)境Sql*plus以外,本人推薦TOAD這個工具,該工具用戶界面友好,可以提高程序的編制效率。

            本文主要講解PL/SQL的基礎部分,熟悉這部分內容后可以進行存儲過程的編寫和應用,對于提高數(shù)據(jù)庫服務器端的執(zhí)行效率很有幫助。


          原載:http://www.yesky.com/SoftChannel/72342371928965120/20040913/1853193.shtml?
          posted @ 2006-09-05 21:14 xzc 閱讀(414) | 評論 (1)編輯 收藏

          摘要:

          JavaMail API是讀取、撰寫、發(fā)送電子信息的可選包。我們可用它來建立如Eudora、Foxmail、MS Outlook Express一般的郵件用戶代理程序(Mail User Agent,簡稱MUA)。讓我們看看JavaMail API是如何提供信息訪問功能的吧!JavaMail API被設計用于以不依賴協(xié)議的方式去發(fā)送和接收電子信息,文中著重:如何以不依賴于協(xié)議的方式發(fā)送接收電子信息,這也是本文所要描述的.

          作者:cleverpig(作者的Blog:http://blog.matrix.org.cn/page/cleverpig)
          原文:http://www.matrix.org.cn/resource/article/44/44101_JavaMail.html
          關鍵字:java,mail,pop,smtp

          一、JavaMail API簡介
          JavaMail API是讀取、撰寫、發(fā)送電子信息的可選包。我們可用它來建立如Eudora、Foxmail、MS Outlook Express一般的郵件用戶代理程序(Mail User Agent,簡稱MUA)。而不是像sendmail或者其它的郵件傳輸代理(Mail Transfer Agent,簡稱MTA)程序那樣可以傳送、遞送、轉發(fā)郵件。從另外一個角度來看,我們這些電子郵件用戶日常用MUA程序來讀寫郵件,而MUA依賴著MTA處理郵件的遞送。
          在清楚了到MUA與MTA之間的關系后,讓我們看看JavaMail API是如何提供信息訪問功能的吧!JavaMail API被設計用于以不依賴協(xié)議的方式去發(fā)送和接收電子信息,這個API被分為兩大部分:

          基本功能:如何以不依賴于協(xié)議的方式發(fā)送接收電子信息,這也是本文所要描述的,不過在下文中,大家將看到這只是一廂情愿而已。
          第二個部分則是依賴特定協(xié)議的,比如SMTP、POP、IMAP、NNTP協(xié)議。在這部分的JavaMail API是為了和服務器通訊,并不在本文的內容中。

          二、相關協(xié)議一覽
          在我們步入JavaMail API之前,先看一下API所涉及的協(xié)議。以下便是大家日常所知、所樂于使用的4大信息傳輸協(xié)議:
          SMTP
          POP
          IMAP
          MIME
          當然,上面的4個協(xié)議,并不是全部,還有NNTP和其它一些協(xié)議可用于傳輸信息,但是由于不常用到,所以本文便不提及了。理解這4個基本的協(xié)議有助于我們更好的使用JavaMail API。然而JavaMail API是被設計為與協(xié)議無關的,目前我們并不能克服這些協(xié)議的束縛。確切的說,如果我們使用的功能并不被我們選擇的協(xié)議支持,那么JavaMail API并不可能如魔術師一樣神奇的賦予我們這種能力。

          1.SMTP
          簡單郵件傳輸協(xié)議定義了遞送郵件的機制。在下文中,我們將使用基于Java-Mail的程序與公司或者ISP的SMTP服務器進行通訊。這個SMTP服務器將郵件轉發(fā)到接收者的SMTP服務器,直至最后被接收者通過POP或者IMAP協(xié)議獲取。這并不需要SMTP服務器使用支持授權的郵件轉發(fā),但是卻的確要注意SMTP服務器的正確設置(SMTP服務器的設置與JavaMail API無關)。

          2.POP
          POP是一種郵局協(xié)議,目前為第3個版本,即眾所周知的POP3。POP定義了一種用戶如何獲得郵件的機制。它規(guī)定了每個用戶使用一個單獨的郵箱。大多數(shù)人在使用POP時所熟悉的功能并非都被支持,例如查看郵箱中的新郵件數(shù)量。而這個功能是微軟的Outlook內建的,那么就說明微軟Outlook之類的郵件客戶端軟件是通過查詢最近收到的郵件來計算新郵件的數(shù)量來實現(xiàn)前面所說的功能。因此在我們使用JavaMail API時需要注意,當需要獲得如前面所講的新郵件數(shù)量之類的信息時,我們不得不自己進行計算。

          3.IMAP
          IMAP使用在接收信息的高級協(xié)議,目前版本為第4版,所以也被稱為IMAP4。需要注意的是在使用IMAP時,郵件服務器必須支持該協(xié)議。從這個方面講,我們并不能完全使用IMAP來替代POP,不能期待IMAP在任何地方都被支持。假如郵件服務器支持IMAP,那么我們的郵件程序將能夠具有以下被IMAP所支持的特性:每個用戶在服務器上可具有多個目錄,這些目錄能在多個用戶之間共享。
          其與POP相比高級之處顯而易見,但是在嘗試采取IMAP時,我們認識到它并不是十分完美的:由于IMAP需要從其它服務器上接收新信息,將這些信息遞送給用戶,維護每個用戶的多個目錄,這都為郵件服務器帶來了高負載。并且IMAP與POP的一個不同之處是POP用戶在接收郵件時將從郵件服務器上下載郵件,而IMAP允許用戶直接訪問郵件目錄,所以在郵件服務器進行備份作業(yè)時,由于每個長期使用此郵件系統(tǒng)的用戶所用的郵件目錄會占有很大的空間,這將直接導致郵件服務器上磁盤空間暴漲。

          4.MIME
          MIME并不是用于傳送郵件的協(xié)議,它作為多用途郵件的擴展定義了郵件內容的格式:信息格式、附件格式等等。一些RFC標準都涉及了MIME:RFC 822, RFC 2045, RFC 2046, RFC 2047,有興趣的Matrixer可以閱讀一下。而作為JavaMail API的開發(fā)者,我們并不需關心這些格式定義,但是這些格式被用在了程序中。

          5.NNTP和其它的第三方協(xié)議
          正因為JavaMail API在設計時考慮到與第三方協(xié)議實現(xiàn)提供商之間的分離,故我們可以很容易的添加一些第三方協(xié)議。SUN維護著一個第三方協(xié)議實現(xiàn)提供商的列表:http://java.sun.com/products/javamail/Third_Party.html,通過此列表我們可以找到所需要的而又不被SUN提供支持的第三方協(xié)議:比如NNTP這個新聞組協(xié)議和S/MIME這個安全的MIME協(xié)議。

          三、安裝
          1.安裝JavaMail
          為了使用JavaMail API,需要從http://java.sun.com/products/javamail/downloads/index.html下載文件名格式為javamail-[version].zip的文件(這個文件中包括了JavaMail實現(xiàn)),并將其中的mail.jar文件添加到CLASSPATH中。這個實現(xiàn)提供了對SMTP、IMAP4、POP3的支持。
          注意:在安裝JavaMail實現(xiàn)之后,我們將在demo目錄中發(fā)現(xiàn)許多有趣的簡單實例程序。
          在安裝了JavaMail之后,我們還需要安裝JavaBeans Activation Framework,因為這個框架是JavaMail API所需要的。如果我們使用J2EE的話,那么我們并無需單獨下載JavaMail,因為它存在于J2EE.jar中,只需將J2EE.jar加入到CLASSPATH即可。

          2.安裝JavaBeans Activation Framework
          http://java.sun.com/products/javabeans/glasgow/jaf.html下載JavaBeans Activation Framework,并將其添加到CLASSPATH中。此框架增加了對任何數(shù)據(jù)塊的分類、以及對它們的處理的特性。這些特性是JavaMail API需要的。雖然聽起來這些特性非常模糊,但是它對于我們的JavaMail API來說只是提供了基本的MIME類型支持。
          到此為止,我們應當把mail.jar和activation.jar都添加到了CLASSPATH中。
          當然如果從方便的角度講,直接把這兩個Jar文件復制到JRE目錄的lib/ext目錄中也可以。

          四、初次認識JavaMail API
          1.了解我們的JavaMail環(huán)境
          A.縱覽JavaMail核心類結構
          打開JavaMail.jar文件,我們將發(fā)現(xiàn)在javax.mail的包下面存在著一些核心類:Session、Message、Address、Authenticator、Transport、Store、Folder。而且在javax.mail.internet包中還有一些常用的子類。
          B.Session
          Session類定義了基本的郵件會話。就像Http會話那樣,我們進行收發(fā)郵件的工作都是基于這個會話的。Session對象利用了java.util.Properties對象獲得了郵件服務器、用戶名、密碼信息和整個應用程序都要使用到的共享信息。
          Session類的構造方法是私有的,所以我們可以使用Session類提供的getDefaultInstance()這個靜態(tài)工廠方法獲得一個默認的Session對象:
          						
          Properties props = new Properties();
          // fill props with any information
          Session session = Session.getDefaultInstance(props, null);

          或者使用getInstance()這個靜態(tài)工廠方法獲得自定義的Session:

          Properties props = new Properties();
          // fill props with any information
          Session session = Session.getInstance(props, null);

          從上面的兩個例子中不難發(fā)現(xiàn),getDefaultInstance()和getInstance()方法的第二個參數(shù)都是null,這是因為在上面的例子中并沒有使用到郵件授權,下文中將對授權進行詳細介紹。
          從很多的實例看,在對mail server進行訪問的過程中使用共享的Session是足夠的,即使是工作在多個用戶郵箱的模式下也不例外。

          C.Message
          當我們建立了Session對象后,便可以被發(fā)送的構造信息體了。在這里SUN提供了Message類型來幫助開發(fā)者完成這項工作。由于Message是一個抽象類,大多數(shù)情況下,我們使用javax.mail.internet.MimeMessage這個子類,該類是使用MIME類型、MIME信息頭的郵箱信息。信息頭只能使用US-ASCII字符,而非ASCII字符將通過編碼轉換為ASCII的方式使用。
          為了建立一個MimeMessage對象,我們必須將Session對象作為MimeMessage構造方法的參數(shù)傳入:

          MimeMessage message = new MimeMessage(session);

          注意:對于MimeMessage類來講存在著多種構造方法,比如使用輸入流作為參數(shù)的構造方法。

          在建立了MimeMessage對象后,我們需要設置它的各個part,對于MimeMessage類來說,這些part就是MimePart接口。最基本的設置信息內容的方法就是通過表示信息內容和米么類型的參數(shù)調用setContent()方法:

          message.setContent("Hello", "text/plain");

          然而,如果我們所使用的MimeMessage中信息內容是文本的話,我們便可以直接使用setText()方法來方便的設置文本內容。

          message.setText("Hello");

          前面所講的兩種方法,對于文本信息,后者更為合適。而對于其它的一些信息類型,比如HTML信息,則要使用前者。
          別忘記了,使用setSubject()方法對郵件設置郵件主題:

          message.setSubject("First");


          D.Address
          到這里,我們已經建立了Session和Message,下面將介紹如何使用郵件地址類:Address。像Message一樣,Address類也是一個抽象類,所以我們將使用javax.mail.internet.InternetAddress這個子類。
          通過傳入代表郵件地址的字符串,我們可以建立一個郵件地址類:

          Address address = new InternetAddress("president@whitehouse.gov");

          如果要在郵件地址后面增加名字的話,可以通過傳遞兩個參數(shù):代表郵件地址和名字的字符串來建立一個具有郵件地址和名字的郵件地址類:

          Address address = new InternetAddress("president@whitehouse.gov", "George Bush");

          本文在這里所講的郵件地址類是為了設置郵件信息的發(fā)信人和收信人而準備的,在建立了郵件地址類后,我們通過message的setFrom()和setReplyTo()兩種方法設置郵件的發(fā)信人:

          message.setFrom(address);
          message.setReplyTo(address);

          若在郵件中存在多個發(fā)信人地址,我們可用addForm()方法增加發(fā)信人:

          Address address[] = ...;
          message.addFrom(address);

          為了設置收信人,我們使用addRecipient()方法增加收信人,此方法需要使用Message.RecipientType的常量來區(qū)分收信人的類型:

          message.addRecipient(type, address)

          下面是Message.RecipientType的三個常量:
          Message.RecipientType.TO
          Message.RecipientType.CC
          Message.RecipientType.BCC
          因此,如果我們要發(fā)送郵件給總統(tǒng),并發(fā)用一個副本給第一夫人的話,下面的方法將被用到:

          Address toAddress = new InternetAddress("vice.president@whitehouse.gov");
          Address ccAddress = new InternetAddress("first.lady@whitehouse.gov");
          message.addRecipient(Message.RecipientType.TO, toAddress);
          message.addRecipient(Message.RecipientType.CC, ccAddress);

          JavaMail API并沒有提供檢查郵件地址有效性的機制。當然我們可以自己完成這個功能:驗證郵件地址的字符是否按照RFC822規(guī)定的格式書寫或者通過DNS服務器上的MX記錄驗證等。

          E.Authenticator
          像java.net類那樣,JavaMail API通過使用授權者類(Authenticator)以用戶名、密碼的方式訪問那些受到保護的資源,在這里“資源”就是指郵件服務器。在javax.mail包中可以找到這個JavaMail的授權者類(Authenticator)。
          在使用Authenticator這個抽象類時,我們必須采用繼承該抽象類的方式,并且該繼承類必須具有返回PasswordAuthentication對象(用于存儲認證時要用到的用戶名、密碼)getPasswordAuthentication()方法。并且要在Session中進行注冊,使Session能夠了解在認證時該使用哪個類。
          下面代碼片斷中的MyAuthenticator就是一個Authenticator的子類。

          Properties props = new Properties();
          // fill props with any information
          Authenticator auth = new MyAuthenticator();
          Session session = Session.getDefaultInstance(props, auth);


          F.Transport
          在發(fā)送信息時,Transport類將被用到。這個類實現(xiàn)了發(fā)送信息的協(xié)議(通稱為SMTP),此類是一個抽象類,我們可以使用這個類的靜態(tài)方法send()來發(fā)送消息:

          Transport.send(message);

          當然,方法是多樣的。我們也可由Session獲得相應協(xié)議對應的Transport實例。并通過傳遞用戶名、密碼、郵件服務器主機名等參數(shù)建立與郵件服務器的連接,并使用sendMessage()方法將信息發(fā)送,最后關閉連接:

          message.saveChanges(); // implicit with send()
          Transport transport = session.getTransport("smtp");
          transport.connect(host, username, password);
          transport.sendMessage(message, message.getAllRecipients());
          transport.close();

          評論:上面的方法是一個很好的方法,尤其是在我們在同一個郵件服務器上發(fā)送多個郵件時。因為這時我們將在連接郵件服務器后連續(xù)發(fā)送郵件,然后再關閉掉連接。send()這個基本的方法是在每次調用時進行與郵件服務器的連接的,對于在同一個郵件服務器上發(fā)送多個郵件來講可謂低效的方式。
          注意:如果需要在發(fā)送郵件過程中監(jiān)控mail命令的話,可以在發(fā)送前設置debug標志:

          session.setDebug(true)。


          G.Store和Folder
          接收郵件和發(fā)送郵件很類似都要用到Session。但是在獲得Session后,我們需要從Session中獲取特定類型的Store,然后連接到Store,這里的Store代表了存儲郵件的郵件服務器。在連接Store的過程中,極有可能需要用到用戶名、密碼或者Authenticator。

          // Store store = session.getStore("imap");
          Store store = session.getStore("pop3");
          store.connect(host, username, password);

          在連接到Store后,一個Folder對象即目錄對象將通過Store的getFolder()方法被返回,我們可從這個Folder中讀取郵件信息:

          Folder folder = store.getFolder("INBOX");
          folder.open(Folder.READ_ONLY);
          Message message[] = folder.getMessages();

          上面的例子首先從Store中獲得INBOX這個Folder(對于POP3協(xié)議只有一個名為INBOX的Folder有效),然后以只讀(Folder.READ_ONLY)的方式打開Folder,最后調用Folder的getMessages()方法得到目錄中所有Message的數(shù)組。

          注意:對于POP3協(xié)議只有一個名為INBOX的Folder有效,而對于IMAP協(xié)議,我們可以訪問多個Folder(想想前面講的IMAP協(xié)議)。而且SUN在設計Folder的getMessages()方法時采取了很智能的方式:首先接收新郵件列表,然后再需要的時候(比如讀取郵件內容)才從郵件服務器讀取郵件內容。
          在讀取郵件時,我們可以用Message類的getContent()方法接收郵件或是writeTo()方法將郵件保存,getContent()方法只接收郵件內容(不包含郵件頭),而writeTo()方法將包括郵件頭。

          System.out.println(((MimeMessage)message).getContent());

          在讀取郵件內容后,別忘記了關閉Folder和Store。

          folder.close(aBoolean);
          store.close();

          傳遞給Folder.close()方法的boolean 類型參數(shù)表示是否在刪除操作郵件后更新Folder。

          H.繼續(xù)向前進!
          在講解了以上的七個Java Mail核心類定義和理解了簡單的代碼片斷后,下文將詳細講解怎樣使用這些類實現(xiàn)JavaMail API所要完成的高級功能。

          五、使用JavaMail API
          在明確了JavaMail API的核心部分如何工作后,本人將帶領大家學習一些使用Java Mail API任務案例。
          1.發(fā)送郵件
          在獲得了Session后,建立并填入郵件信息,然后發(fā)送它到郵件服務器。這便是使用Java Mail API發(fā)送郵件的過程,在發(fā)送郵件之前,我們需要設置SMTP服務器:通過設置Properties的mail.smtp.host屬性。

          String host = ...;
          String from = ...;
          String to = ...;

          // Get system properties
          Properties props = System.getProperties();

          // Setup mail server
          props.put("mail.smtp.host", host);

          // Get session
          Session session = Session.getDefaultInstance(props, null);

          // Define message
          MimeMessage message = new MimeMessage(session);
          message.setFrom(new InternetAddress(from));
          message.addRecipient(Message.RecipientType.TO,
            new InternetAddress(to));
          message.setSubject("Hello JavaMail");
          message.setText("Welcome to JavaMail");
          // Send message
          Transport.send(message);

          由于建立郵件信息和發(fā)送郵件的過程中可能會拋出異常,所以我們需要將上面的代碼放入到try-catch結構塊中。

          2.接收郵件
          為了在讀取郵件,我們獲得了session,并且連接到了郵箱的相應store,打開相應的Folder,然后得到我們想要的郵件,當然別忘記了在結束時關閉連接。

          String host = ...;
          String username = ...;
          String password = ...;

          // Create empty properties
          Properties props = new Properties();

          // Get session
          Session session = Session.getDefaultInstance(props, null);

          // Get the store
          Store store = session.getStore("pop3");
          store.connect(host, username, password);

          // Get folder
          Folder folder = store.getFolder("INBOX");
          folder.open(Folder.READ_ONLY);

          // Get directory
          Message message[] = folder.getMessages();

          for (int i=0, n=message.length; i<n; i++) {
             System.out.println(i + ": " + message[i].getFrom()[0]
               + "\t" + message[i].getSubject());
          }

          // Close connection
          folder.close(false);
          store.close();

          上面的代碼所作的是從郵箱中讀取每個郵件,并且顯示郵件的發(fā)信人地址和主題。從技術角度講,這里存在著一個異常的可能:當發(fā)信人地址為空時,getFrom()[0]將拋出異常。

          下面的代碼片斷有效的說明了如何讀取郵件內容,在顯示每個郵件發(fā)信人和主題后,將出現(xiàn)用戶提示從而得到用戶是否讀取該郵件的確認,如果輸入YES的話,我們可用Message.writeTo(java.io.OutputStream os)方法將郵件內容輸出到控制臺上,關于Message.writeTo()的具體用法請看JavaMail API。

          BufferedReader reader = new BufferedReader (
            new InputStreamReader(System.in));

          // Get directory
          Message message[] = folder.getMessages();
          for (int i=0, n=message.length; i<n; i++) {
            System.out.println(i + ": " + message[i].getFrom()[0]
              + "\t" + message[i].getSubject());

            System.out.println("Do you want to read message? " +
              "[YES to read/QUIT to end]");
            String line = reader.readLine();
            if ("YES".equals(line)) {
              message[i].writeTo(System.out);
            } else if ("QUIT".equals(line)) {
              break;
            }
          }


          3.刪除郵件和標志
          設置與message相關的Flags是刪除郵件的常用方法。這些Flags表示了一些系統(tǒng)定義和用戶定義的不同狀態(tài)。在Flags類的內部類Flag中預定義了一些標志:
          Flags.Flag.ANSWERED
          Flags.Flag.DELETED
          Flags.Flag.DRAFT
          Flags.Flag.FLAGGED
          Flags.Flag.RECENT
          Flags.Flag.SEEN
          Flags.Flag.USER
          但需要在使用時注意的:標志存在并非意味著這個標志被所有的郵件服務器所支持。例如,對于刪除郵件的操作,POP協(xié)議不支持上面的任何一個。所以要確定哪些標志是被支持的——通過訪問一個已經打開的Folder對象的getPermanetFlags()方法,它將返回當前被支持的Flags類對象。
          刪除郵件時,我們可以設置郵件的DELETED標志:

          message.setFlag(Flags.Flag.DELETED, true);

          但是首先要采用READ_WRITE的方式打開Folder:

          folder.open(Folder.READ_WRITE);

          在對郵件進行刪除操作后關閉Folder時,需要傳遞一個true作為對刪除郵件的擦除確認。

          folder.close(true);

          Folder類中另一種用于刪除郵件的方法expunge()也同樣可刪除郵件,但是它并不為sun提供的POP3實現(xiàn)支持,而其它第三方提供的POP3實現(xiàn)支持或者并不支持這種方法。
          另外,介紹一種檢查某個標志是否被設置的方法:Message.isSet(Flags.Flag flag)方法,其中參數(shù)為被檢查的標志。

          4.郵件認證
          我們在前面已經學會了如何使用Authenticator類來代替直接使用用戶名和密碼這兩字符串作為Session.getDefaultInstance()或者Session.getInstance()方法的參數(shù)。在前面的小試牛刀后,現(xiàn)在我們將了解到全面認識一下郵件認證。
          我們在此取代了直接使用郵件服務器主機名、用戶名、密碼這三個字符串作為連接到POP3 Store的方式,使用存儲了郵件服務器主機名信息的屬性文件,并在獲得Session時傳入自定義的Authenticator實例:

          // Setup properties
          Properties props = System.getProperties();
          props.put("mail.pop3.host", host);

          // Setup authentication, get session
          Authenticator auth = new PopupAuthenticator();
          Session session = Session.getDefaultInstance(props, auth);

          // Get the store
          Store store = session.getStore("pop3");
          store.connect();


          PopupAuthenticator類繼承了抽象類Authenticator,并且通過重載Authenticator類的getPasswordAuthentication()方法返回PasswordAuthentication類對象。而getPasswordAuthentication()方法的參數(shù)param是以逗號分割的用戶名、密碼組成的字符串。

          import javax.mail.*;
          import java.util.*;

          public class PopupAuthenticator extends Authenticator {

            public PasswordAuthentication getPasswordAuthentication(String param) {
              String username, password;

              StringTokenizer st = new StringTokenizer(param, ",");
              username = st.nextToken();
              password = st.nextToken();

              return new PasswordAuthentication(username, password);
            }

          }


          5.回復郵件
          回復郵件的方法很簡單:使用Message類的reply()方法,通過配置回復郵件的收件人地址和主題(如果沒有提供主題的話,系統(tǒng)將默認將“Re:”作為郵件的主體),這里不需要設置任何的郵件內容,只要復制發(fā)信人或者reply-to到新的收件人。而reply()方法中的boolean參數(shù)表示是否將郵件回復給發(fā)送者(參數(shù)值為false),或是恢復給所有人(參數(shù)值為true)。
          補充一下,reply-to地址需要在發(fā)信時使用setReplyTo()方法設置。

          MimeMessage reply = (MimeMessage)message.reply(false);
          reply.setFrom(new InternetAddress("president@whitehouse.gov"));
          reply.setText("Thanks");
          Transport.send(reply);


          6.轉發(fā)郵件
          轉發(fā)郵件的過程不如前面的回復郵件那樣簡單,它將建立一個轉發(fā)郵件,這并非一個方法就能做到。
          每個郵件是由多個部分組成,每個部分稱為一個郵件體部分,是一個BodyPart類對象,對于MIME類型郵件來講就是MimeBodyPart類對象。這些郵件體包含在成為Multipart的容器中對于MIME類型郵件來講就是MimeMultiPart類對象。在轉發(fā)郵件時,我們建立一個文字郵件體部分和一個被轉發(fā)的文字郵件體部分,然后將這兩個郵件體放到一個Multipart中。說明一下,復制一個郵件內容到另一個郵件的方法是僅復制它的DataHandler(數(shù)據(jù)處理者)即可。這是由JavaBeans Activation Framework定義的一個類,它提供了對郵件內容的操作命令的訪問、管理了郵件內容操作,是不同的數(shù)據(jù)源和數(shù)據(jù)格式之間的一致性接口。

          // Create the message to forward
          Message forward = new MimeMessage(session);

          // Fill in header
          forward.setSubject("Fwd: " + message.getSubject());
          forward.setFrom(new InternetAddress(from));
          forward.addRecipient(Message.RecipientType.TO,
            new InternetAddress(to));

          // Create your new message part
          BodyPart messageBodyPart = new MimeBodyPart();
          messageBodyPart.setText(
            "Here you go with the original message:\n\n");

          // Create a multi-part to combine the parts
          Multipart multipart = new MimeMultipart();
          multipart.addBodyPart(messageBodyPart);

          // Create and fill part for the forwarded content
          messageBodyPart = new MimeBodyPart();
          messageBodyPart.setDataHandler(message.getDataHandler());

          // Add part to multi part
          multipart.addBodyPart(messageBodyPart);

          // Associate multi-part with message
          forward.setContent(multipart);

          // Send message
          Transport.send(forward);


          7.使用附件
          附件作為與郵件相關的資源經常以文本、表格、圖片等格式出現(xiàn),如流行的郵件客戶端一樣,我們可以用JavaMail API從郵件中獲取附件或是發(fā)送帶有附件的郵件。

          A.發(fā)送帶有附件的郵件
          發(fā)送帶有附件的郵件的過程有些類似轉發(fā)郵件,我們需要建立一個完整郵件的各個郵件體部分,在第一個部分(即我們的郵件內容文字)后,增加一個具有DataHandler的附件而不是在轉發(fā)郵件時那樣復制第一個部分的DataHandler。

          如果我們將文件作為附件發(fā)送,那么要建立FileDataSource類型的對象作為附件數(shù)據(jù)源;如果從URL讀取數(shù)據(jù)作為附件發(fā)送,那么將要建立URLDataSource類型的對象作為附件數(shù)據(jù)源。

          然后將這個數(shù)據(jù)源(FileDataSource或是URLDataSource)對象作為DataHandler類構造方法的參數(shù)傳入,從而建立一個DataHandler對象作為數(shù)據(jù)源的DataHandler。

          接著將這個DataHandler設置為郵件體部分的DataHandler。這樣就完成了郵件體與附件之間的關聯(lián)工作,下面的工作就是BodyPart的setFileName()方法設置附件名為原文件名。

          最后將兩個郵件體放入到Multipart中,設置郵件內容為這個容器Multipart,發(fā)送郵件。

          // Define message
          Message message = new MimeMessage(session);
          message.setFrom(new InternetAddress(from));
          message.addRecipient(Message.RecipientType.TO,
            new InternetAddress(to));
          message.setSubject("Hello JavaMail Attachment");

          // Create the message part
          BodyPart messageBodyPart = new MimeBodyPart();

          // Fill the message
          messageBodyPart.setText("Pardon Ideas");

          Multipart multipart = new MimeMultipart();
          multipart.addBodyPart(messageBodyPart);

          // Part two is attachment
          messageBodyPart = new MimeBodyPart();
          DataSource source = new FileDataSource(filename);
          messageBodyPart.setDataHandler(new DataHandler(source));
          messageBodyPart.setFileName(filename);
          multipart.addBodyPart(messageBodyPart);

          // Put parts in message
          message.setContent(multipart);

          // Send the message
          Transport.send(message);

          如果我們使用servlet實現(xiàn)發(fā)送帶有附件的郵件,則必須上傳附件給servlet,這時需要注意提交頁面form中對編碼類型的設置應為multipart/form-data。

          <FORM ENCTYPE="multipart/form-data"
              method=post action="/myservlet">
            <INPUT TYPE="file" NAME="thefile">
            <INPUT TYPE="submit" VALUE="Upload">
          </FORM>


          B.讀取郵件中的附件
          讀取郵件中的附件的過程要比發(fā)送它的過程復雜一點。因為帶有附件的郵件是多部分組成的,我們必須處理每一個部分獲得郵件的內容和附件。
          但是如何辨別郵件信息內容和附件呢?Sun在Part類(BodyPart類實現(xiàn)的接口類)中提供了getDisposition()方法讓開發(fā)者獲得郵件體部分的部署類型,當該部分是附件時,其返回之將是Part.ATTACHMENT。但附件也可以沒有部署類型的方式存在或者部署類型為Part.INLINE,無論部署類型為Part.ATTACHMENT還是Part.INLINE,我們都能把該郵件體部分導出保存。

          Multipart mp = (Multipart)message.getContent();

          for (int i=0, n=multipart.getCount(); i<n; i++) {
            Part part = multipart.getBodyPart(i));

            String disposition = part.getDisposition();

            if ((disposition != null) &&
                ((disposition.equals(Part.ATTACHMENT) ||
                 (disposition.equals(Part.INLINE))) {
              saveFile(part.getFileName(), part.getInputStream());
            }
          }

          下列代碼中使用了saveFile方法是自定義的方法,它根據(jù)附件的文件名建立一個文件,如果本地磁盤上存在名為附件的文件,那么將在文件名后增加數(shù)字表示區(qū)別。然后從郵件體中讀取數(shù)據(jù)寫入到本地文件中(代碼省略)。

          // from saveFile()
          File file = new File(filename);
          for (int i=0; file.exists(); i++) {
            file = new File(filename+i);
          }

          以上是郵件體部分被正確設置的簡單例子,如果郵件體部分的部署類型為null,那么我們通過獲得郵件體部分的MIME類型來判斷其類型作相應的處理,代碼結構框架如下:

          if (disposition == null) {
            // Check if plain
            MimeBodyPart mbp = (MimeBodyPart)part;
            if (mbp.isMimeType("text/plain")) {
              // Handle plain
            } else {
              // Special non-attachment cases here of
              // image/gif, text/html, ...
            }
          ...
          }


          8.處理HTML郵件
          前面的例子中發(fā)送的郵件都是以文本為內容的(除了附件),下面將介紹如何接收和發(fā)送基于HTML的郵件。
          A.發(fā)送HTML郵件
          假如我們需要發(fā)送一個HTML文件作為郵件內容,并使郵件客戶端在讀取郵件時獲取相關的圖片或者文字的話,只要設置郵件內容為html代碼,并設置內容類型為text/html即可:

          String htmlText = "<H1>Hello</H1>" +
            "<img src=\"http://www.jguru.com/images/logo.gif\">";
          message.setContent(htmlText, "text/html"));

          請注意:這里的圖片并不是在郵件中內嵌的,而是在URL中定義的。郵件接收者只有在線時才能看到。
          在接收郵件時,如果我們使用JavaMail API接收郵件的話是無法實現(xiàn)以HTML方式顯示郵件內容的。因為JavaMail API郵件內容視為二進制流。所以要顯示HTML內容的郵件,我們必須使用JEditorPane或者第三方HTML展現(xiàn)組件。

          以下代碼顯示了如何使用JEditorPane顯示郵件內容:

          if (message.getContentType().equals("text/html")) {
            String content = (String)message.getContent();
            JFrame frame = new JFrame();
            JEditorPane text = new JEditorPane("text/html", content);
            text.setEditable(false);
            JScrollPane pane = new JScrollPane(text);
            frame.getContentPane().add(pane);
            frame.setSize(300, 300);
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.show();
          }


          B.在郵件中包含圖片
          如果我們在郵件中使用HTML作為內容,那么最好將HTML中使用的圖片作為郵件的一部分,這樣無論是否在線都會正確的顯示HTML中的圖片。處理方法就是將HTML中用到的圖片作為郵件附件并使用特殊的cid URL作為圖片的引用,這個cid就是對圖片附件的Content-ID頭的引用。
          處理內嵌圖片就像向郵件中添加附件一樣,不同之處在于我們必須通過設置圖片附件所在的郵件體部分的header中Content-ID為一個隨機字符串,并在HTML中img的src標記中設置為該字符串。這樣就完成了圖片附件與HTML的關聯(lián)。

          String file = ...;

          // Create the message
          Message message = new MimeMessage(session);

          // Fill its headers
          message.setSubject("Embedded Image");
          message.setFrom(new InternetAddress(from));
          message.addRecipient(Message.RecipientType.TO,
            new InternetAddress(to));

          // Create your new message part
          BodyPart messageBodyPart = new MimeBodyPart();
          String htmlText = "<H1>Hello</H1>" +
            "<img src=\"cid:memememe\">";
          messageBodyPart.setContent(htmlText, "text/html");

          // Create a related multi-part to combine the parts
          MimeMultipart multipart = new MimeMultipart("related");
          multipart.addBodyPart(messageBodyPart);

          // Create part for the image
          messageBodyPart = new MimeBodyPart();

          // Fetch the image and associate to part
          DataSource fds = new FileDataSource(file);
          messageBodyPart.setDataHandler(new DataHandler(fds));
          messageBodyPart.setHeader("Content-ID","<memememe>");

          // Add part to multi-part
          multipart.addBodyPart(messageBodyPart);

          // Associate multi-part with message
          message.setContent(multipart);


          9.在郵件中搜索短語
          JavaMail API提供了過濾器機制,它被用來建立搜索短語。這個短語由javax.mail.search包中的SearchTerm抽象類來定義,在定義后我們便可以使用Folder的Search()方法在Folder中查找郵件:

          SearchTerm st = ...;
          Message[] msgs = folder.search(st);

          下面有22個不同的類(繼承了SearchTerm類)供我們使用:
          AND terms (class AndTerm)
          OR terms (class OrTerm)
          NOT terms (class NotTerm)
          SENT DATE terms (class SentDateTerm)
          CONTENT terms (class BodyTerm)
          HEADER terms (FromTerm / FromStringTerm, RecipientTerm / RecipientStringTerm, SubjectTerm, etc.)
          使用這些類定義的斷語集合,我們可以構造一個邏輯表達式,并在Folder中進行搜索。下面是一個實例:在Folder中搜索郵件主題含有“ADV”字符串或者發(fā)信人地址為friend@public.com的郵件。

          SearchTerm st =
            new OrTerm(
              new SubjectTerm("ADV:"),
              new FromStringTerm("friend@public.com"));
          Message[] msgs = folder.search(st);


          六、參考資源
          JavaMail API Home
          Sun’s JavaMail API基礎
          JavaBeans Activation Framework Home
          javamail-interest mailing list
          Sun's JavaMail FAQ
          jGuru's JavaMail FAQ
          Third Party Products List

          七、代碼下載
          http://java.sun.com/developer/onlineTraining/JavaMail/exercises.html
          posted @ 2006-09-04 17:12 xzc 閱讀(212) | 評論 (0)編輯 收藏

          摘要:

          本文描述了apache commons中的commons loggings部分,Commons Logging和Log4J用來提供日志支持。
          介紹
          命令行參數(shù)解析、應用程序配置和日志記錄,作為一個應用程序的骨架,隨處可見。因此,Apache軟件組織開發(fā)出了一套通用的類庫,用來幫助軟件開發(fā)人員完成這些“骨架”的建立。其中:
          &#8226;Commons CLI用于命令行解析
          &#8226;Commons Configuration用于讀取properties格式或者XML格式的配置信息
          &#8226;Commons Logging和Log4J用來提供日志支持。
          這些通用的類庫都在http://jakarta.apache.org/commons/index.html網(wǎng)址上提供下載

          Apache組織開發(fā)了一套用于支持Logging的Log4J,Java 1.4版本也引入了一套內置的Logging框架,如果開發(fā)者想在這兩套Logging系統(tǒng)之間自由的切換,該怎么辦呢?答案就是,使用Commons Logging。Commons Logging定義了一套抽象的Logging接口,用戶可以通過配置,使這些接口指向任何一個已存在的Logging系統(tǒng)。

          &#8226;使用抽象Logging接口
          問題:
          你在編寫一個可以重復使用的庫,需要寫入Log信息,但你不想使你的Logging功能綁定在Apache Log4J或者JDK 1.4 Logging框架上。
          解決方案:

          public static void main(String[] args) {//自己替換[]

          ??System.setProperty("org.apache.commons.logging.Log",
          ??????"org.apache.commons.logging.impl.Jdk14Logger");
          ??Log log = LogFactory.getLog("com.discursive.jccook.SomeApp");

          ??if (log.isTraceEnabled()) {
          ????log.trace("This is a trace message");
          ??}

          ??if (log.isDebugEnabled()) {
          ????log.debug("This is a debug message");
          ??}

          ??log.info("This is an informational message");
          ??log.warn("This is a warning");
          ??log.error("This is an error");
          ??log.fatal("This is fatal");

          }

          LogFactory.getLog方法會根據(jù)底層環(huán)境返回一個適當?shù)腖og實現(xiàn)。如果用戶想指定一個具體的Logging系統(tǒng)實現(xiàn),可以設置org.apache.commons.logging.Log系統(tǒng)屬性。例如:
          System.setProperty("org.apache.commons.logging.Log",
          "org.apache.commons.logging.impl.Log4JLogger");
          這樣就會使用Log4J作為Logging系統(tǒng)。
          org.apache.commons.logging.Log可以設定為:
          &#8226;org.apache.commons.logging.impl.Log4JLogger??使用Log4J
          &#8226;org.apache.commons.logging.impl.Jdk14Logger??使用JDK 1.4 Logging框架
          &#8226;org.apache.commons.logging.impl.SimpleLog??使用Commons Logging內置的簡單Log實現(xiàn)
          其他:
          總結一下,Commons Logging會按照下列順序來指定具體的Log實現(xiàn)。
          &#8226;如果定義了org.apache.commons.logging.Log系統(tǒng)參數(shù),實用指定的Logging實現(xiàn)。
          &#8226;如果在CLASSPATH里發(fā)現(xiàn)了Log4J,使用Log4J。
          &#8226;如果使用的是JDK1.4,使用JDK1.4內置的Logging框架。
          &#8226;如果都沒有找到,則使用Commons Logging內置的簡單Log實現(xiàn)。


          Jakarta Commons Logging學習筆記 轉載

          1、Commons-Loggin簡介

            Jakarta Commons Logging (JCL)提供的是一個日志(Log)接口(interface),同時兼顧輕量級和不依賴于具體的日志實現(xiàn)工具。 它提供給中間件/日志工具開發(fā)者一個簡單的日志操作抽象,允許程序開發(fā)人員使用不同的具體日志實現(xiàn)工具。用戶被假定已熟悉某種日志實現(xiàn)工具的更高級別的細節(jié)。JCL提供的接口,對其它一些日志工具,包括Log4J, Avalon LogKit, and JDK 1.4等,進行了簡單的包裝,此接口更接近于Log4J和LogKit的實現(xiàn).

          2、快速入門

            JCL有兩個基本的抽象類:Log(基本記錄器)和LogFactory(負責創(chuàng)建Log實例)。當commons-logging.jar被加入到CLASSPATH之后,它會心可能合理地猜測你喜歡的日志工具,然后進行自我設置,用戶根本不需要做任何設置。默認的LogFactory是按照下列的步驟去發(fā)現(xiàn)并決定那個日志工具將被使用的(按照順序,尋找過程會在找到第一個工具時中止):

          尋找當前factory中名叫org.apache.commons.logging.Log配置屬性的值

          尋找系統(tǒng)中屬性中名叫org.apache.commons.logging.Log的值

          如果應用程序的classpath中有l(wèi)og4j,則使用相關的包裝(wrapper)類(Log4JLogger)

          如果應用程序運行在jdk1.4的系統(tǒng)中,使用相關的包裝類(Jdk14Logger)

          使用簡易日志包裝類(SimpleLog)


          3、開發(fā)使用logging


          //在程序文件頭部import相關的類
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
          ......
          //在類中獲取一個實例
          public class MYCLASS
          {
          private static Log log = LogFactory.getLog(MyCLASS.class);
          ...
           }

          日志信息被送往記錄器,如上例中的log。這個發(fā)送過程,是通過調用Log接口中定義的方法完成的,不同方法跟不同的級別聯(lián)系在一起,日志信息通過哪個級別的方法發(fā)送,就標明了日志信息的級別。org.apache.commons.logging.Log接口中定義的方法,按嚴重性由高到低的順序有:


          log.fatal(Object message);

          log.fatal(Object message, Throwable t);

          log.error(Object message);

          log.error(Object message, Throwable t);

          log.warn(Object message);

          log.warn(Object message, Throwable t);

          log.info(Object message);

          log.info(Object message, Throwable t);

          log.debug(Object message);

          log.debug(Object message, Throwable t);

          log.trace(Object message);

          log.trace(Object message, Throwable t);
          除此以外,還提供下列方法以便代碼保護.

          log.isFatalEnabled();

          log.isErrorEnabled();

          log.isWarnEnabled();

          log.isInfoEnabled();

          log.isDebugEnabled();

          log.isTraceEnabled();

            信息級別
            確保日志信息在內容上和反應問題的嚴重程度上的恰當,是非常重要的。

          fatal非常嚴重的錯誤,導致系統(tǒng)中止。期望這類信息能立即顯示在狀態(tài)控制臺上。

          error其它運行期錯誤或不是預期的條件。期望這類信息能立即顯示在狀態(tài)控制臺上。

          warn使用了不贊成使用的API、非常拙劣使用API, '幾乎就是'錯誤, 其它運行時不合需要和不合預期的狀態(tài)但還沒必要稱為 "錯誤"。期望這類信息能立即顯示在狀態(tài)控制臺上。

          info運行時產生的有意義的事件。期望這類信息能立即顯示在狀態(tài)控制臺上。

          debug系統(tǒng)流程中的細節(jié)信息。期望這類信息僅被寫入log文件中。

          trace更加細節(jié)的信息。期望這類信息僅被寫入log文件中。

          通常情況下,記錄器的級別不應低于info.也就是說,通常情況下debug的信息不應被寫入log文件中。
            工作機理

          生命周期
          JCL LogFactory必須實現(xiàn)建立/斷開到日志工具的連接,實例化/初始化/解構一個日志工具.

          異常處理
          JCL Log 接口沒有指定任何異常處理,對接口的實現(xiàn)必須捕獲并處理異常。


          多線程
          JCL Log 和 LogFactory 的實現(xiàn),必須確保任何日志工具對并行的要求.


            記錄器的設置
            JCL采用的記錄器的不同其設置內容也不同。Log4J是默認首選記錄器,對其設置可通過系統(tǒng)屬性(system properties)或一個屬性文件進行設置,下面是其設置參數(shù)。
          <table border="1"><tr><td>參數(shù)</td><td>值域</td><td>默認值</td><td>說明</td></tr><tr><td>log4j.configuration</td><td></td><td>log4j.properties</td><td>指定配置文件的名字</td></tr><tr><td>log4j.rootCategory</td><td>priority [, appender]*</td><td></td><td>設定根記錄器的級別</td></tr><tr><td>log4j.logger&lt;.logger.name&gt;</td><td>DEBUG, INFO, WARN, ERROR, or FATAL</td><td>設定logger.name這個記錄器的級別</td></tr><tr><td>log4j.appender&lt;.appender&gt;.Threshold</td><td>priority</td><td>指定記錄設備appender(console, files, sockets, and others)的最低級別。</td></tr></table><br />
          posted @ 2006-09-04 00:48 xzc 閱讀(923) | 評論 (0)編輯 收藏
          Log4j由三個重要的組件構成:日志信息的優(yōu)先級,日志信息的輸出目的地,日志信息的輸出格式。日志信息的優(yōu)先級從高到低有ERROR、WARN、INFO、DEBUG,分別用來指定這條日志信息的重要程度;日志信息的輸出目的地指定了日志將打印到控制臺還是文件中;而輸出格式則控制了日志信息的顯示內容。

            一、定義配置文件

            其實您也可以完全不使用配置文件,而是在代碼中配置Log4j環(huán)境。但是,使用配置文件將使您的應用程序更加靈活。Log4j支持兩種配置文件格式,一種是XML格式的文件,一種是Java特性文件(鍵=值)。下面我們介紹使用Java特性文件做為配置文件的方法:

            1.配置根Logger,其語法為:

            log4j.rootLogger = [ level ] , appenderName, appenderName, …

            其中,level 是日志記錄的優(yōu)先級,分為OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定義的級別。Log4j建議只使用四個級別,優(yōu)先級從高到低分別是ERROR、WARN、INFO、DEBUG。通過在這里定義的級別,您可以控制到應用程序中相應級別的日志信息的開關。比如在這里定義了INFO級別,則應用程序中所有DEBUG級別的日志信息將不被打印出來。 appenderName就是指定日志信息輸出到哪個地方。您可以同時指定多個輸出目的地。

            2.配置日志信息輸出目的地Appender,其語法為:

            log4j.appender.appenderName = fully.qualified.name.of.appender.class
            log4j.appender.appenderName.option1 = value1
            …
            log4j.appender.appenderName.option = valueN

            其中,Log4j提供的appender有以下幾種:
            org.apache.log4j.ConsoleAppender(控制臺),
            org.apache.log4j.FileAppender(文件),
            org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件),
            org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件),
            org.apache.log4j.WriterAppender(將日志信息以流格式發(fā)送到任意指定的地方)

            3.配置日志信息的格式(布局),其語法為:

            log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
            log4j.appender.appenderName.layout.option1 = value1
            …
            log4j.appender.appenderName.layout.option = valueN

            其中,Log4j提供的layout有以下幾種:
            org.apache.log4j.HTMLLayout(以HTML表格形式布局),
            org.apache.log4j.PatternLayout(可以靈活地指定布局模式),
            org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串),
            org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等等信息)

          Log4J采用類似C語言中的printf函數(shù)的打印格式格式化日志信息,打印參數(shù)如下:
          %m 輸出代碼中指定的消息
          %n? 輸出一個回車換行符,Windows平臺為“\r\n”,Unix平臺為“\n”?
          %p? 輸出優(yōu)先級,即DEBUG,INFO,WARN,ERROR,F(xiàn)ATAL
          %r?? 輸出自應用啟動到輸出該log信息耗費的毫秒數(shù)
          %c? 輸出所屬的類目,通常就是所在類的全名
          %t?? 輸出產生該日志事件的線程名
          %d? 輸出日志時間點的日期或時間,默認格式為ISO8601,也可以在其后指定格式,
          ??????? 比如:%d{yyy MMM dd HH:mm:ss,SSS},輸出類似:2002年10月18日 22:10:28,921
          %l?? 輸出日志事件的發(fā)生位置,包括類目名、發(fā)生的線程,以及在代碼中的行數(shù)。
          ??????? 舉例:Testlog4.main(TestLog4.java:10)
          %F? 類目名
          %L? 代碼中的行數(shù)

            二、在代碼中使用Log4j

            1.得到記錄器

            使用Log4j,第一步就是獲取日志記錄器,這個記錄器將負責控制日志信息。其語法為:

            public static Logger getLogger( String name)

            通過指定的名字獲得記錄器,如果必要的話,則為這個名字創(chuàng)建一個新的記錄器。Name一般取本類的名字,比如:

            static Logger logger = Logger.getLogger ( ServerWithLog4j.class.getName () )

            2.讀取配置文件

            當獲得了日志記錄器之后,第二步將配置Log4j環(huán)境,其語法為:

            BasicConfigurator.configure (): 自動快速地使用缺省Log4j環(huán)境。
            PropertyConfigurator.configure ( String configFilename) :讀取使用Java的特性文件編寫的配置文件。
            DOMConfigurator.configure ( String filename ) :讀取XML形式的配置文件。

            3.插入記錄信息(格式化日志信息)

            當上兩個必要步驟執(zhí)行完畢,您就可以輕松地使用不同優(yōu)先級別的日志記錄語句插入到您想記錄日志的任何地方,其語法如下:

            Logger.debug ( Object message ) ;
            Logger.info ( Object message ) ;
            Logger.warn ( Object message ) ;
            Logger.error ( Object message ) ;


          介紹
          命令行參數(shù)解析、應用程序配置和日志記錄,作為一個應用程序的骨架,隨處可見。因此,Apache軟件組織開發(fā)出了一套通用的類庫,用來幫助軟件開發(fā)人員完成這些“骨架”的建立。其中:
          &#8226;Commons CLI用于命令行解析
          &#8226;Commons Configuration用于讀取properties格式或者XML格式的配置信息
          &#8226;Commons Logging和Log4J用來提供日志支持。
          這些通用的類庫都在http://jakarta.apache.org/commons/index.html網(wǎng)址上提供下載

          Log4J是一個高度可配置的Logging框架,提供了結構化,多種目標和格式支持。

          &#8226;配置Log4J
          問題:
          Log4J支持Properties和XML兩種格式的配置文件。
          解決方案:
          定義log4j.properties配置文件

          # 所有Log信息輸出到標準輸出(System.out)和在下面指定的一個文件
          # WARN是默認的logging級別
          log4j.rootCategory = WARN, STDOUT, FILE

          # 應用程序的logging級別是DEBUG
          log4j.logger.com.discursive = DEBUG

          # 配置標準輸出Appender
          log4j.appender.STDOUT = org.apache.log4j.ConsoleAppender
          log4j.appender.STDOUT.layout = org.apache.log4j.PatternLayout
          log4j.appender.STDOUT.layout.ConversionPattern = %5p (%F:%L) %m%n

          # 配置輸出文件Appender
          log4j.appender.FILE = org.apache.log4j.RollingFileAppender
          log4j.appender.FILE.File = output.log
          log4j.appender.FILE.MaxFileSize = 2000KB
          log4j.appender.FILE.MaxBackupIndex = 5
          log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
          log4j.appender.FILE.layout.ConversionPattern = %d %-5p %c - %m%n



          PropertyConfigurator.configure(getClass()
          ??.getResource("/resources/log4j.properties"));

          Logger logger = Logger.getLogger("com.discursive.SomeApp");
          logger.info("This is a info message");
          logger.error("This is a error message");


          使用BasicConfigurator類來加載log4j.properties配置。使用Logger.getLogger獲得一個logger實例。
          配置文件中的rootCategory指定將log輸出到控制臺和output.log文件。文件Appender使用了RollingFileAppender,當文件大小達到最大文件大小(MaxFileSize)2000KB時,RollingFileAppender會備份原log文件,并再創(chuàng)建一個新的log文件。
          配置文件指定默認的logging級別是DEBUG(log4j.logger.com.discursive = DEBUG)。所以,所有級別低于DEBUG的log信息都不會被輸出。Log4J按重要度定義了五個log級別,分別是:DEBUG, INFO, WARN, ERROR, 和FATAL。
          其他:
          Log4J還可以使用XML格式的配置文件,使用DOMConfigurator讀取。
          Log4J使用Appender和Layout來定制log輸出。Appender指定輸出到何處,Layout指定如何輸出(輸出的格式)。
          Log4J內置的Appender有:
          &#8226;SMTPAppender
          &#8226;RollingFileAppender
          &#8226;SocketAppender
          &#8226;SyslogAppender
          &#8226;NTEventLogAppender
          Log4J支持的Layout有
          &#8226;XMLLayout
          &#8226;PatternLayout
          &#8226;HTMLLayout
          &#8226;DateLayout.


          附1
          # 所有Log信息輸出到標準輸出(System.out)和在下面指定的一個文件
          # WARN是默認的logging級別
          log4j.rootCategory = INFO, STDOUT
          #log4j.rootCategory = INFO, STDOUT, FILE

          # 配置標準輸出Appender
          log4j.appender.STDOUT = org.apache.log4j.ConsoleAppender
          log4j.appender.STDOUT.layout = org.apache.log4j.PatternLayout
          log4j.appender.STDOUT.layout.ConversionPattern = %d{ABSOLUTE} %-5p [%c:%L] %m%n

          # 配置輸出文件Appender
          log4j.appender.FILE = org.apache.log4j.RollingFileAppender
          log4j.appender.FILE.File = output.log
          log4j.appender.FILE.MaxFileSize = 2000KB
          log4j.appender.FILE.MaxBackupIndex = 5
          log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
          log4j.appender.FILE.layout.ConversionPattern = %d %-5p [%c:%L] %m%n

          # 應用程序的logging級別是DEBUG
          log4j.logger.com.xzc = DEBUG


          附2
          ### direct log messages to stdout ###
          log4j.appender.stdout=org.apache.log4j.ConsoleAppender
          log4j.appender.stdout.Target=System.out
          log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
          log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - <%m>%n

          ### direct messages to file hibernate.log ###
          #log4j.appender.file=org.apache.log4j.FileAppender
          #log4j.appender.file.File=hibernate.log
          #log4j.appender.file.layout=org.apache.log4j.PatternLayout
          #log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

          ### set log levels - for more verbose logging change 'info' to 'debug' ###

          log4j.rootLogger=warn, stdout

          log4j.logger.net.sf.hibernate=warn

          ### log just the SQL
          #log4j.logger.net.sf.hibernate.SQL=debug

          ### log JDBC bind parameters ###
          log4j.logger.net.sf.hibernate.type=info

          ### log schema export/update ###
          log4j.logger.net.sf.hibernate.tool.hbm2ddl=debug

          ### log cache activity ###
          #log4j.logger.net.sf.hibernate.cache=debug

          ### enable the following line if you want to track down connection ###
          ### leakages when using DriverManagerConnectionProvider ###
          #log4j.logger.net.sf.hibernate.connection.DriverManagerConnectionProvider=trace

          附3

          log4j.rootLogger=debug, stdout, R
          
          log4j.appender.stdout=org.apache.log4j.ConsoleAppender
          log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
          
          # Pattern to output the caller's file name and line number.
          log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
          
          log4j.appender.R=org.apache.log4j.RollingFileAppender
          log4j.appender.R.File=example.log
          
          log4j.appender.R.MaxFileSize=100KB
          # Keep one backup file
          log4j.appender.R.MaxBackupIndex=1
          
          log4j.appender.R.layout=org.apache.log4j.PatternLayout
          log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n





          附4
          # Log4j由三個重要的組件構成:日志信息的優(yōu)先級,日志信息的輸出目的地,日志信息的輸出格式
          # 1.配置根Logger,其語法為:
          #  log4j.rootLogger = [ level ] , appenderName, appenderName, …
          # 2.配置日志信息輸出目的地Appender,其語法為:
          #  log4j.appender.appenderName = fully.qualified.name.of.appender.class
          #  log4j.appender.appenderName.option1 = value1
          #  …
          #  log4j.appender.appenderName.option = valueN
          # Log4j提供的appender有以下幾種:
          # org.apache.log4j.ConsoleAppender(控制臺),
          # org.apache.log4j.FileAppender(文件),
          # org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件),
          # org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件),
          # org.apache.log4j.WriterAppender(將日志信息以流格式發(fā)送到任意指定的地方)
          # 3.配置日志信息的格式(布局),其語法為:
          #  log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
          #  log4j.appender.appenderName.layout.option1 = value1
          #  …
          #  log4j.appender.appenderName.layout.option = valueN
          # Log4j提供的layout有以下幾種:
          # org.apache.log4j.HTMLLayout(以HTML表格形式布局),
          # org.apache.log4j.PatternLayout(可以靈活地指定布局模式),
          # org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串),
          # org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等等信息)
          # Log4J采用類似C語言中的printf函數(shù)的打印格式格式化日志信息,打印參數(shù)如下:
          # %m? 輸出代碼中指定的消息
          # %n? 輸出一個回車換行符,Windows平臺為“\r\n”,Unix平臺為“\n”
          # %p? 輸出優(yōu)先級,即DEBUG,INFO,WARN,ERROR,F(xiàn)ATAL
          # %r? 輸出自應用啟動到輸出該log信息耗費的毫秒數(shù)
          # %c? 輸出所屬的類目,通常就是所在類的全名
          # %t? 輸出產生該日志事件的線程名
          # %d? 輸出日志時間點的日期或時間,默認格式為ISO8601,也可以在其后指定格式,
          #???? 比如:%d{yyy MMM dd HH:mm:ss,SSS},輸出類似:2002年10月18日 22:10:28,921
          # %l? 輸出日志事件的發(fā)生位置,包括類目名、發(fā)生的線程,以及在代碼中的行數(shù)。
          #???? 舉例:Testlog4.main(TestLog4.java:10)
          # %F? 類目名
          # %L? 代碼中的行數(shù)
          # 所有Log信息輸出到標準輸出(System.out)和在下面指定的一個文件 
          # 日志信息的優(yōu)先級從從高到低有ERROR、WARN、INFO、DEBUG
          # WARN是默認的logging級別
          log4j.rootCategory = INFO, STDOUT
          #log4j.rootCategory = INFO, STDOUT, FILE
          #log4j.rootCategory = INFO, STD
          ?
          # 配置標準輸出Appender
          log4j.appender.STDOUT = org.apache.log4j.ConsoleAppender
          log4j.appender.STDOUT.layout = org.apache.log4j.PatternLayout
          #log4j.appender.STDOUT.layout.ConversionPattern = %d{ABSOLUTE} %-5p [%c:%t:%L] %m%n
          log4j.appender.STDOUT.layout.ConversionPattern = %d{ABSOLUTE} %-5p [ %l ] %m%n
          # 配置輸出文件Appender
          log4j.appender.FILE = org.apache.log4j.RollingFileAppender
          log4j.appender.FILE.File = output.log
          log4j.appender.FILE.MaxFileSize = 2000KB
          log4j.appender.FILE.MaxBackupIndex = 5
          log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
          log4j.appender.FILE.layout.ConversionPattern = %d %-5p [%c:%L] %m%n
          # 配置默認輸出layout
          log4j.appender.STD = org.apache.log4j.ConsoleAppender
          log4j.appender.STD.layout = org.apache.log4j.TTCCLayout
          # 應用程序的logging級別是DEBUG
          log4j.logger.com.xzc = DEBUG




          posted @ 2006-09-04 00:42 xzc 閱讀(8321) | 評論 (4)編輯 收藏

          Java提供了兩類主要的異常:runtime exception和checked exception。所有的checked exception是從java.lang.Exception類衍生出來的,而runtime exception則是從java.lang.RuntimeException或java.lang.Error類衍生出來的。

          它們的不同之處表現(xiàn)在兩方面:機制上和邏輯上。

            一、機制上

            它們在機制上的不同表現(xiàn)在兩點:1.如何定義方法;2. 如何處理拋出的異常。請看下面CheckedException的定義:

          public class CheckedException extends Exception
          {
           public CheckedException() {}
           public CheckedException( String message )
           {
            super( message );
           }
          }

            以及一個使用exception的例子:

          public class ExceptionalClass
          {
           public void method1()
            throws CheckedException
            {
             // ... throw new CheckedException( "...出錯了" );
            }
           public void method2( String arg )
            {
             if( arg == null )
             {
              throw new NullPointerException( "method2的參數(shù)arg是null!" );
             }
            }
           public void method3() throws CheckedException
            {
             method1();
            }
          }

            你可能已經注意到了,兩個方法method1()和method2()都會拋出exception,可是只有method1()做了聲明。另外,method3()本身并不會拋出exception,可是它卻聲明會拋出CheckedException。在向你解釋之前,讓我們先來看看這個類的main()方法:

          public static void main( String[] args )
          {
           ExceptionalClass example = new ExceptionalClass();
           try
           {
            example.method1();
            example.method3();
           }
           catch( CheckedException ex ) { } example.method2( null );
          }

            在main()方法中,如果要調用method1(),你必須把這個調用放在try/catch程序塊當中,因為它會拋出Checked exception。

            相比之下,當你調用method2()時,則不需要把它放在try/catch程序塊當中,因為它會拋出的exception不是checked exception,而是runtime exception。會拋出runtime exception的方法在定義時不必聲明它會拋出exception。

            現(xiàn)在,讓我們再來看看method3()。它調用了method1()卻沒有把這個調用放在try/catch程序塊當中。它是通過聲明它會拋出method1()會拋出的exception來避免這樣做的。它沒有捕獲這個exception,而是把它傳遞下去。實際上main()方法也可以這樣做,通過聲明它會拋出Checked exception來避免使用try/catch程序塊(當然我們反對這種做法)。

            小結一下:

            * Runtime exceptions:

             在定義方法時不需要聲明會拋出runtime exception;

             在調用這個方法時不需要捕獲這個runtime exception;

             runtime exception是從java.lang.RuntimeException或java.lang.Error類衍生出來的。

            * Checked exceptions:

             定義方法時必須聲明所有可能會拋出的checked exception;

             在調用這個方法時,必須捕獲它的checked exception,不然就得把它的exception傳遞下去;

             checked exception是從java.lang.Exception類衍生出來的。
          二、邏輯上

            從邏輯的角度來說,checked exceptions和runtime exception是有不同的使用目的的。checked exception用來指示一種調用方能夠直接處理的異常情況。而runtime exception則用來指示一種調用方本身無法處理或恢復的程序錯誤。

            checked exception迫使你捕獲它并處理這種異常情況。以java.net.URL類的構建器(constructor)為例,它的每一個構建器都會拋出MalformedURLException。MalformedURLException就是一種checked exception。設想一下,你有一個簡單的程序,用來提示用戶輸入一個URL,然后通過這個URL去下載一個網(wǎng)頁。如果用戶輸入的URL有錯誤,構建器就會拋出一個exception。既然這個exception是checked exception,你的程序就可以捕獲它并正確處理:比如說提示用戶重新輸入。

            再看下面這個例子:

          public void method()
          {
           int [] numbers = { 1, 2, 3 };
           int sum = numbers[0] + numbers[3];
          }

            在運行方法method()時會遇到ArrayIndexOutOfBoundsException(因為數(shù)組numbers的成員是從0到2)。對于這個異常,調用方無法處理/糾正。這個方法method()和上面的method2()一樣,都是runtime exception的情形。上面我已經提到,runtime exception用來指示一種調用方本身無法處理/恢復的程序錯誤。而程序錯誤通常是無法在運行過程中處理的,必須改正程序代碼。

            總而言之,在程序的運行過程中一個checked exception被拋出的時候,只有能夠適當處理這個異常的調用方才應該用try/catch來捕獲它。而對于runtime exception,則不應當在程序中捕獲它。如果你要捕獲它的話,你就會冒這樣一個風險:程序代碼的錯誤(bug)被掩蓋在運行當中無法被察覺。因為在程序測試過程中,系統(tǒng)打印出來的調用堆棧路徑(StackTrace)往往使你更快找到并修改代碼中的錯誤。有些程序員建議捕獲runtime exception并紀錄在log中,我反對這樣做。這樣做的壞處是你必須通過瀏覽log來找出問題,而用來測試程序的測試系統(tǒng)(比如Unit Test)卻無法直接捕獲問題并報告出來。

            在程序中捕獲runtime exception還會帶來更多的問題:要捕獲哪些runtime exception?什么時候捕獲?runtime exception是不需要聲明的,你怎樣知道有沒有runtime exception要捕獲?你想看到在程序中每一次調用方法時,都使用try/catch程序塊嗎?

          --------------附加程序-------------------------

          public class Junk {
          //     public static void main(String args[]) {
          //         try {
          //             a();
          //         } catch(HighLevelException e) {
          //             e.printStackTrace();
          //         }
          //     }
              
               public static void main(String args[]) {
                   try {
                       a();
                   } catch(Exception e) {
                       e.printStackTrace();
                       System.out.println("-------------------------");
                       System.out.println(e.getMessage());
                   }
               }
              
              
               static void a() throws HighLevelException {
                   try {
                       b();
                   } catch(MidLevelException e) {
                       throw new HighLevelException(e);
                   }
               }
               static void b() throws MidLevelException {
                   c();
               }  
               static void c() throws MidLevelException {
                   try {
                       d();
                   } catch(LowLevelException e) {
                       throw new MidLevelException(e);
                   }
               }
               static void d() throws LowLevelException {
                  e();
               }
               static void e() throws LowLevelException {
                   throw new LowLevelException("e throw exception!");
               }
           }

           class HighLevelException extends Exception {
               HighLevelException(Throwable cause) { super(cause); }
               HighLevelException(String message,Throwable cause) {
                super(message,cause);
               }
           }

           class MidLevelException extends Exception {
               MidLevelException(Throwable cause)  { super(cause); }
               MidLevelException(String message,Throwable cause)  {
                super(message,cause);
               }
           }
           
           class LowLevelException extends Exception {
            public LowLevelException(){
            }
            public LowLevelException(String message){
             super(message);
            }
           }
           

          posted @ 2006-08-24 21:08 xzc 閱讀(229) | 評論 (0)編輯 收藏
          僅列出標題
          共32頁: First 上一頁 24 25 26 27 28 29 30 31 32 下一頁 
          主站蜘蛛池模板: 溧水县| 灌南县| 淅川县| 常德市| 水城县| 靖江市| 浑源县| 巧家县| 松阳县| 茂名市| 木里| 福清市| 无棣县| 宜章县| 兰考县| 石家庄市| 稻城县| 始兴县| 昌邑市| 买车| 绍兴市| 武城县| 宿迁市| 新宾| 临湘市| 乌拉特后旗| 图片| 屏南县| 霍城县| 黄冈市| 宣武区| 辽源市| 普宁市| 萝北县| 修武县| 新兴县| 雅安市| 龙胜| 涞源县| 满洲里市| 罗田县|