隨筆-22  評論-6  文章-17  trackbacks-0
           

          我們要開發一個java類:其內容只有一句,輸出"hello ant"字符串。并使用ant完成編譯和運行工作,這個例子只是為了跑通ant,不附加多余的東西。

          下圖為文件組織,請建立相應的目錄,并編寫HelloAnt.java

          按照人家老外的文件組織規則咱也照搬。

           hello.ant.HelloAnt.java

          在項目根目錄(hello-ant\)寫1個文件:ant執行配置文件build.xml

           build.xml

          ok,一切大功告成,哦,不,還沒有運行它。

          dos下進入hello-ant的目錄,即build.xml所在的目錄,我們要用ant工具執行它 , 

          執行: %ant_home%/bin/ant -file build.xml     用ant工具執行當前目錄下的配置文件build.xml 

          或  :ant -file build.xml                    你如果設置%ant_home%/bin到path中

          這次ok了,這是答案:

          命令提示符窗口
          D:\temp\hello-ant>ant -file build.xml
          Buildfile: build.xml

          main:
          [javac] Compiling 1 source file to D:\temp\hello-ant\build\classes
          [java] hello ant,ant 的第一次接觸,好棒!

          BUILD SUCCESSFUL
          Total time: 2 seconds
          D:\temp\hello-ant>

          檢查一下build/classes目錄,哦,看到編譯過的文件就在這里:
          build/classes/hello/ant/HelloAnt.class.

          hello ant 進級

          (此段比較廢話,可以略過)
          你也許會說:這末簡單的工作寫個批處理不就得了,又xml又ant的,把我的時間都浪費完了,我用jbuild或
          webShpere不就得了,怎末說你才明白呢?反正網上開源項目大多數都用ant,你總不能給人家個*.jpx吧,
          而且這樣的工具太貴,受不了(當然用D的兄弟不怕^_^ ),而且ant可以讓你明確的管理和自動化所有的東西:
          編譯-實施-測試...,哎,稍微麻煩一點點,但節約你以前花在零碎的copy,paste上的時間.而且我發現管理
          代碼的質量有所提高.

          我們要改進build.xml,讓它做更多的事情:

          • 定義全局變量
          • 初始化,主要是建立目錄
          • 編譯  (已有)
          • 打包為jar
          • 建立API documentation
          • 生成distribution產品

          凡事都講究平衡,你要ant給你做更多事,當然要累一點點,不過只用累一次,以后的代碼修改后的構建都是"一鍵式"完成,我們制作一個hello的簡單例子,你可以自己做j2ee的練習。

          我們要擴充目錄結構,使它更像回事:

          ant處理編譯之前的目錄:

          ant處理之后的目錄:

          圖中:\src,\docs,\lib是自己組織的文件結構,\build,\dist是ant動態生成的成品。

          \src                     源文件:java源,script源,jsp源,xml配置.....
          \src\main         java源
          \src\script     window,unix,liunx的執行script,我們的簡單只有一個:
                            run.bat:  java hello.ant.HelloAnt

          \docs                手寫說明文檔
          \lib                   程序所需類庫的jar,比如j2ee.jar,mail,jar...

          \build               用ant動態生成的構建目錄
          \build\classes  編譯的類文件
          \build\docs         copy "\docs"的手寫說明文檔,和ant生成的api文檔
          \build\lib           放置我們自己的HelloAnt.class打包成品hello-ant.jar

          \dist\bin        copy "\src\script" 得執行文件
          \dist\docs      copy "\build\docs" 的文檔
          \dist\lib         除了copy "\build\lib"下的hello-ant.jar外,
                                        還應copy "\lib"的程序所需jar,這里我們沒有。

          以上是我學老外的文件組織,大家可以按照自己的愛好組織

          我們編寫必要的文件:

          hello.ant. HelloAnt.java
          已有
          \src\script.bat
          \docs\index.html 隨便寫一個手寫的文檔

          hello ant 軟件項目手冊docs


          訪問api文檔

           
          \build.xml 配置文件 


          build.xml多了些,但其實很簡單:(注釋比較詳細可以參照,這里再簡單說一下)

          一個build.xml包含一個工程的自動化處理的完整xml說明,并且基本由3種東東組成:

          <project >

              1.全局變量的定義
              <property/>

              2.任務組
              <target>
                  3.許多單項任務... 像copy,delete,javac,jar...
                  <task1/>
                  <task2/>
                  <task3/>
              </target>

          </project>

          參考及下載:

          本文程序:第1個hello-ant

          本文程序:第2個進階的hello-ant

          ant最新下載:
          http://jakarta.apache.org/ant/index.html
           

          ant具體的編寫方法參考ant手冊以下2部分就形,
          http://jakarta.apache.org/ant/manual/using 使用說明
          http://jakarta.apache.org/ant/manual/coretasklist.html 核心tasks
          其他一大堆東西你要看也行。不過我覺得比較浪費時間。
          http://jakarta.apache.org/ant/manual/index.html
            手冊index

          huihoo.com翻譯改編的ant/manual/using
          http://www.huihoo.com/java/ant.html

          用ANT構造Application作者:余斌斌    
          http://developer.ccidnet.com/pub/disp/Article?columnID=295&articleID=27619&pageNO=1

          ibm 利用 Ant 和 JUnit 進行增量開發——使用單元測試來逐步改進代碼

          http://www-900.ibm.com/developerWorks/cn/java/j-ant/index.shtml

          posted @ 2005-12-07 14:33 surffish 閱讀(574) | 評論 (1)編輯 收藏

          本文以最新發布的Ant 1.5.1為例,介紹這款優秀的Build工具的安裝配置、基本應用和一些高級話題。最新的Ant下載地址是 http://jakarta.apache.org/ant/

          Ant是一種基于Java的Build工具。理論上來說,它有些類似于C中的make,但比make優越。現在存在的大多數Build工具,如make、gnumake、nmake、jam等都存在這樣或那樣的不足,比如依賴于特定的平臺、配置文件過于復雜或者對格式無法檢查而容易出錯等。與這些工具相比較,Ant的兩個特性決定了它是一款優秀的Build工具:

          1. 基于Java的實現。具有良好的跨平臺性,同時可以通過增加新的Java類來擴展Ant的功能,而無需去了解不同平臺上不同的腳本語言。

          2.基于XML的配置文件。Ant以XML樹來描述Target/Task的關系,文件結構清晰、易讀易寫,并且利用XML對格式的控制來避免由于配置文件的錯誤造成的Build操作失敗。

          安裝與配置

          Ant的安裝非常簡單,把從網上下載的jakarta-ant-1.5.1-bin.zip解開到一個目錄下即可(以下假定安裝在目錄D:\jakarta-ant-1.5.1)。接下來需要進行環境變量配置:

          SET ANT_HOME=D:\jakarta-ant-1.5.1 //注意是Ant的安裝目錄,不是bin子目錄
          SET PATH=%PATH%;%ANT_HOME%\bin;


          在配置環境變量之前,請確認已經正確設置了JAVA_HOME系統變量。輸入ant命令,看到如下輸出說明已成功安裝了Ant工具:

          Buildfile: build.xml does not exist!
          Build failed


          提示信息表明在當前目錄不存在build.xml配置文件,但這本身已經說明Ant成功運行了。

          快速入門

          下面用一個最簡單也是最經典的例子-HelloWorld來感受一下Ant吧。

          //HelloWorld.java
          package com.sharetop.antdemo;
          public class HelloWorld {
          public static void main( String args[] ) {
          System.out.println("Hello world. ");
          }
          }


          要讓Ant編譯這個文件,首先需要編寫一個Build配置文件。在一般情況下,這個文件被命名為build.xml。

          <?xml version="1.0" encoding="UTF-8" ?>
          <project name="HelloWorld" default="run" basedir="." >
          <property name="src" value="src"/>
          <property name="dest" value="classes"/>
          <property name="hello_jar" value="hello.jar" />
          <target name="init">
          <mkdir dir="${dest}"/>
          </target>
          <target name="compile" depends="init">
          <javac srcdir="${src}" destdir="${dest}"/>
          </target>
          <target name="build" depends="compile">
          <jar jarfile="${hello_jar}" basedir="${dest}"/>
          </target>
          <target name="run" depends="build">
          <java classname="com.sharetop.antdemo.HelloWorld" classpath="${hello_jar}"/>
          </target>
          </project>


          來看一下這個文件的內容,它描述了以下信息:工程的名字為HelloWorld,工程有四個target,分別是init、compil、build和run,缺省是run。compile只有一個任務javac,源文件位于src目錄下,輸出的類文件要放在classes目錄下。build的任務是jar,生成的jar文件為hello.jar,它打包時以classes為根目錄。而run則是執行這個HelloWorld類,用hello.jar作為classpath。這四個target之間有一個依賴關系,這種關系用depends來指定。即如果Target A依賴于Target B,那么在執行Target A之前會首先執行Target B。所以從下面運行缺省Target(run)的輸出看,這四個Target的執行順序是:init→compile→build→run。文件目錄結構如圖1所示。HelloWorld.java文件在src\com\sharetop\antdemo子目錄下。


          圖1 ant_demo應用的目錄結構


          在命令行輸入命令:ant,然后運行,可以看到如下輸出:

          如果配置文件名不是build.xml,比如是build_front.xml,那么,可以使用-buildfile命令參數指定:

          G:\myDoc\ant_demo>ant -buildfile build_front.xml


          也可以單獨執行指定的某個target,比如,只編譯不打包執行,可以使用下面輸入命令即可:

          G:\myDoc\ant_demo>ant compile

          在相應的目錄下會找到編譯出的HelloWorld.class文件。

          再看看上面的build.xml配置文件,文件開頭定義了3個屬性,分別指定了源文件輸出路徑、類文件輸出路徑和生成的Jar文件名,后面對這些路徑的引用都通過一個${property name}來引用。所以,要注意這樣一個原則“目錄的定義與目錄的引用應該分開”。
          基本應用

          建立工程的目錄

          一般要根據工程的實際情況來建立工程的目錄結構。但是,有一些比較通用的組織形式可供參考,比如所有的jakarta項目都使用類似的目錄結構。下面讓我們來看一下這種目錄結構的特點。

          表1

          目錄 文件
          bin 公共的二進制文件,以及運行腳本
          build 臨時創建的文件,如類文件等
          dist 目標輸出文件,如生成Jar文件等。
          doc/javadocs 文檔。
          lib 需要導出的Java包
          src 源文件

          對于一個簡單的工程,一般包括表1的幾個目錄。其中bin、lib、doc和src目錄需要在CVS的控制之下。當然在這樣的目錄結構上,也可以做一些調整,例如,可以建立一個extra目錄來放置需要發布的Jar文件、Inf文件及圖像文件等。同樣,如果開發Web應用可以建立一個Web目錄放置JSP、HTML等文件。

          如果我們開發的是一個比較復雜的項目,包括多個子項目,并且各個子項目是由不同的開發人員來完成的,那么要如何來設計它的目錄結構?首先有一點是需要確定的,不同的子項目應該擁有不同的Build文件,并且整個項目也應該有一個總的Build文件。可以通過Ant任務或是AntCall任務調用子項目的Build文件,如下例:

          <target name="core" depends="init">
          <ant dir="components" target="core"/>
          <ant dir="waf/src" target="core"/>
          <ant dir="apps" target="core"/>
          </target>


          在各個子項目的耦合不是非常緊密的情況下,各個子項目應該有各自獨立的目錄結構,也就是說它們可以有自己的src、doc、build、dist等目錄及自己的build.xml文件,但是可以共享lib和bin目錄。而對于那些耦合緊密的子項目,則推薦使用同一個src目錄,但是不同的子項目有不同的子目錄,各個子項目的build.xml文件可以放在根目錄下,也可以移到各個子項目的目錄下。

          編寫Build文件

          要用好Ant工具,關鍵是要編寫一個build.xml文件。要編寫出一個結構良好、靈活可擴展的Build文件,有兩個問題要考慮,一是了解Build文件的基本結構,二是了解Ant定義的大量任務。

          Ant的Build文件是一個標準的XML文件,它包含一個根節點Project,每個Project定義了至少一個或多個Target,每個Target又是一系列Task的集合。它們之間的關系如圖2所示。


          圖2 build.xml文件的結構


          每個Task是一段可被執行的代碼,比如,前例中的javac、jar就是兩個最常用的Task。Ant定義了大量的核心Task,我們要考慮的第二個問題正是如何去掌握這大量的Task。其實唯一的方法就是邊學習邊實踐,這方面最好的參考就是官方的Ant使用手冊。

          外部文件的使用

          使用外部的Property文件可以保存一些預設置的公共屬性變量。這些屬性可以在多個不同的Build文件中使用。

          可以將一個外部的XML文件導入Build文件中,這樣多個項目的開發者可以通過引用來共享一些代碼,同樣,這也有助于Build文件的重用,示例代碼如下所示:

          <!DOCTYPE project [
          <!ENTITY share-variable SYSTEM "file:../share-variable.xml">
          <!ENTITY build-share SYSTEM "file:../build-share.xml">
          ]>
          <project name="main" default="complie" basedir=".">
          &share-variable;
          &build-share;
          ... ...


          在J2EE項目中的應用

          只要掌握了Ant的使用方法,在J2EE項目中的應用與在其它項目中的應用并沒有太大的不同,但是仍有幾點是需要注意的。

          一是要清楚War和Jar文件的目錄結構,主要是War的配置文件web.xml文件的位置和EJB的配置文件(ejb-jar.xml和weblogic-ejb-jar.xml等)的位置,在調用Jar任務打包文件時一定要記得把它們也包含進來。一般在編譯之前就要注意把這些需打包的文件拷入相應目錄下。二是在J2EE項目中可能會涉及到一些特殊的任務,比如在Weblogic中會調用ejbc預編譯EJB的代碼存根,或者需要在Ant中同時發布Jar到相應的服務器中等。可以用兩種途徑實現這些任務,一是擴展Ant任務實現這些任務,二是直接用Java任務來執行這些命令。下面是打包、發布一個EJB的build.xml配置文件片斷,代碼如下:

          <target name="deploy_HelloEJB" depends="compile">
          <delete dir="${temp}/ejb_make"/> <!-- 首先刪除臨時目錄 -->
          <delete file="${temp}/helloEJB.jar"/>
          <!-- 刪除WebLogic域中老版本的EJB -->
          <delete file="${weblogic.deploy.dest}/helloEJB.jar"/>
          <!-- 創建META-INF目錄,放置ejb-jar.xml和weblogic-ejb-jar.xml -->
          <mkdir dir="${temp}/ejb_make/META-INF"/>
          <!-- 拷貝ejb-jar.xml和weblogic-ejb-jar.xml 到臨時目錄-->
          <copy todir="${temp}/ejb_make/META-INF">
          <fileset dir="etc/baseinfo">
          <include name="*.xml"/>
          </fileset>
          </copy>
          <!-- 拷貝所有的helloEJB類到臨時目錄 -->
          <copy todir="${temp}/ejb_make/">
          <fileset dir="${dest.classes}/"> <!-- dest.classes是輸出的類文件目錄 -->
          <include name="${dest.classes}/helloEJB/**"/>
          </fileset>
          </copy>
          <!-- 將所有這些文件打包成helloEJB.jar -->
          <jar jarfile="${temp}/helloEJB.jar" basedir="${temp}/ejb_make"/>
          <!-- 進行weblogic.ejbc編譯 -->
          <java classpath="${wl_cp}" classname="weblogic.ejbc" fork="yes" >
          <classpath>
          <fileset dir="lib">
          <include name="*.jar" />
          </fileset>
          </classpath>
          <arg value="${temp}/helloEJB.jar" />
          <arg value="${temp}/helloEJB_deploy.jar" />
          </java>
          <!-- 拷貝/發布到WebLogic的{DOMAIN}\applications目錄 -->
          <copy file="${temp}/helloEJB_deploy.jar" todir="${weblogic.deploy.dest}"/>
          </target>


          用Ant配合JUnit實現單元測試

          Ant 提供了JUnit任務,可以執行單元測試代碼。如何使用JUnit,以及如何編寫測試用例(TestCase),感興趣的讀者可以參閱JUnit的相關文檔。在Ant中使用JUnit的方法非常簡單,首先需要把junit.jar拷入ANT_HOME\lib下,確認在這個目錄下有optional.jar,因為JUnit是Ant的擴展任務,需要引用這個擴展包。然后就是在Build文件中加入JUnit的任務,代碼如下:

          <target name="run" depends="client">
          <junit printsummary="yes" fork="yes" haltonfailure="yes">
          <classpath>
          <pathelement location="client.jar" />
          </classpath>
          <formatter type="plain" />
          <test name="com.sharetop.antdemo.HelloWorldTest" />
          </junit>
          </target>


          高級話題

          為Ant開發擴展任務

          為Ant實現擴展任務其實是非常容易的,只需按照以下幾個步驟即可:

          1. 創建一個Java類繼承org.apache.tools.ant.Task類;

          2. 對每個屬性實現set方法。Ant會根據需要自動完成類型轉換;

          3. 如果擴展的任務需要嵌套其它的Task,那么這個Java類必需實現接口org.apache.tools.ant.TaskContainer;

          4. 如果擴展的任務要支持Text,需要增加一個方法void addText(String);

          5. 對每個嵌套的元素,實現create、add 或 addConfigured 方法;

          6. 實現public void execute方法;

          7. 在build.xml文件中使用 <taskdef> 來引用自定義的Task。

          下面以一個簡單的例子來說明如何為Ant增加一個hello任務,它可以連續打印多條信息,打印的次數由屬性count指定,而打印的內容則由它內嵌的一個helloinfo任務的message屬性指定,看上去這非常類似JSP中自定義標簽的一些概念,實現代碼如下:

          //HelloInfoTask.java
          package com.sharetop.antdemo;
          import org.apache.tools.ant.*;
          public class HelloInfoTask {
          private String msg;
          public void execute() throws BuildException {
          System.out.println(msg);
          }
          public void setMessage(String msg) {
          this.msg = msg;
          }
          }


          下面是外部Task類的代碼:

          //HelloTask.java
          package com.sharetop.antdemo;
          import org.apache.tools.ant.*;
          public class HelloTask extends Task implements org.apache.tools.ant.TaskContainer
          {
          private Task info;
          private int count;
          public void execute() throws BuildException {
          for(int i=0;i<count;i++)
          info.execute();
          }
          public void setCount(int c){
          this.count=c;
          }
          public void addTask(Task t){
          this.info=t;
          }
          }


          實現了這兩個Task,在build.xml文件中定義它的task name,就可以在Target中執行它了。如果你不想使用 <taskdef> 標簽來定義Task,也可以通過修改default.properties文件來實現引入新Task,這個文件位于org.apache.tools.ant.taskdefs 包里。下例是一個使用 標簽來引入新Task的Build文件部分:

          <target name="hello" depends="client">
          <taskdef name="hello"
          classname="com.sharetop.antdemo.HelloTask" classpath="client.jar"/>
          <taskdef name="helloinfo"
          classname="com.sharetop.antdemo.HelloInfoTask" classpath="client.jar"/>
          <hello count="3" >
          <helloinfo message="hello world" />
          </hello>
          </target>
          在自己的程序中調用Ant

          Ant的任務其實就是一段功能代碼。Ant內置的大量任務對于我們開發Java應用具有非常大的意義,為什么我們不能直接使用呢?

          因為盡管在程序中調用Ant的任務并不復雜,而且我們知道Ant的任務其實都是一些Java類,調用的方法無非就是執行這些類而已,不過在執行之前需要對它做一些初始化的工作,所以我們需要引用一個Task類的子類來實現這個功能,比如如下代碼:

          package com.sharetop.antdemo;
          import org.apache.tools.ant.*;
          import org.apache.tools.ant.taskdefs.*;
          import java.io.File;
          public class RunAntTask {
          public RunAntTask() {
          }
          public static void main(String args[]){
          AntJAR j = new AntJAR();
          j.setBasedir(new File("./classes"));
          j.setJarfile(new File("aaa.jar"));
          j.execute();
          }
          }
          final class AntJAR extends Jar {
          public AntJAR() {
          project = new Project();
          project.init();
          taskType = "jar";
          taskName = "jar";
          }
          }


          注意AntJAR類的構造方法,先創建了Project并初始化它,這是直接調用Task的必需條件。

          如果要在自己的程序中執行Ant,需要了解的是Ant定義的幾個BuildEvent,它包括:

          ◆ Build started

          ◆ Build finished

          ◆ Target started

          ◆ Target finished

          ◆ Task started

          ◆ Task finished

          ◆ Message logged

          我們需要做的是實現BuildListener接口來處理各種事件,而執行Ant的方法與上面給的例子非常類似,以實際開發的AntBuilder軟件的部分代碼為例:

          public void buildTarget(String targetName,String buildFileName) {
          try {
          Project p = new Project();
          p.init();
          File f = new File(buildFileName);
          p.setUserProperty("ant.file",f.getAbsolutePath());
          ProjectHelper.configureProject(p,f);
          p.addBuildListener(this);
          if( targetName==null )
          p.executeTarget(p.getDefaultTarget());
          else
          p.executeTarget(targetName);
          }
          catch (Exception ex) {
          jTextArea1.append(ex.getMessage());
          }
          }


          創建Project并初始化,設置它的配置文件(build.xml),執行它缺省的或指定的Target,然后在實現了BuildListenser接口的監聽器類中對你感興趣的事件作處理,代碼如下:

          public void buildStarted(BuildEvent event){ /* nothing*/ }
          public void buildFinished(BuildEvent event) { /* nothing*/ }
          public void targetStarted(BuildEvent event) {
          this.jTextArea1.append(event.getTarget().getName()+": \n\r");
          }
          public void targetFinished(BuildEvent event) {/* nothing*/ }
          public void taskStarted(BuildEvent event) {/* nothing*/ }
          public void taskFinished(BuildEvent event) { /* nothing*/ }
          public void messageLogged(BuildEvent event) {
          int prior = event.getPriority();
          switch(prior){
          case Project.MSG_ERR :
          this.jTextArea1.append("["+event.getTask().getTaskName()+"]Err:"
          +event.getMessage());
          break;
          case Project.MSG_INFO:
          this.jTextArea1.append("["+event.getTask().getTaskName()+"]"+event.getMessage
          ());
          break;
          case Project.MSG_WARN:
          this.jTextArea1.append("["+event.getTask().getTaskName()+"]"
          +event.getMessage());
          break;
          case Project.MSG_VERBOSE:
          this.jTextArea1.append(event.getMessage());
          break;
          }
          }


          Build.xml文件的寫法每個公司都有不同,這里沒有太大的參考價值,所以略去。(全文完)
          posted @ 2005-12-07 14:29 surffish 閱讀(1649) | 評論 (0)編輯 收藏
           

          //服務器代碼
          /********************************Main******************************/

          import java.io.*;
          import java.net.*;
          import java.util.*;
          public class ServerMain{
           public static Vector socketVector=new Vector();
           public static void main(String[] args) throws IOException{
                    System.out.println("服務器啟動........");
            ServerSocket s = new ServerSocket(5000);
            while(true){
             Socket soc = s.accept();
             SocketThread st=new SocketThread(soc);
             socketVector.addElement(st);
             st.start();
            }
           }
           public static void sendEveryone(String msg){
                          Object object=null;
            int len=socketVector.size();
            for(int i=0;i<len;i++){
                                  try {
                                    object=socketVector.elementAt(i);
                                    SocketThread st = (SocketThread)object;
                                    st.sender.send(msg);
                                  }
                                  catch (Exception ex) {
                                    socketVector.removeElement(object);
                                  }
            }
           }
                  public static void removeObject(Object object){
                    socketVector.removeElement(object);
                  }
                  public static void removeObject(Sender sender) throws Exception{
                    int len=socketVector.size();
                          for(int i=0;i<len;i++){
                            Object object=socketVector.elementAt(i);
                            SocketThread st = (SocketThread)object;
                            if(st.sender==sender)
                              socketVector.removeElement(object);
                          }

                  }

          }

          /*********************************SocketThread **********************/
          import java.io.*;
          import java.net.*;
          import java.util.*;
          public class SocketThread implements Runnable{
           public Socket socke;
           public DataInputStream dis;
           public DataOutputStream dos;
           public Sender sender;
                  private boolean stop;
                  Calendar date;// = Calendar.getInstance(TimeZone.getTimeZone("Asia/ShangHai"));
           public SocketThread(Socket sok){
            socke=sok;
           }
           public void start(){
            Thread t=new Thread(this);
            t.start();
           }
                public void run() {
                  try {
                    socke.setKeepAlive(true);
                    dis = new DataInputStream(socke.getInputStream());
                    dos = new DataOutputStream(socke.getOutputStream());
                    sender = new Sender(dos);
                    while (true) {
                      StringBuffer sb = new StringBuffer();
                     char c;
                     while (((c = dis.readChar()) != '\n') && (c != -1)) {
                       sb.append(c);
                     }
                      if (c == -1) {
                        break;
                      }
                      date = Calendar.getInstance(TimeZone.getTimeZone("Asia/ShangHai"));
                      String ljnTime="("+date.get(date.YEAR)+"/"+(date.get(date.MONTH)+1)+"/"+date.get(date.DATE)+" "+date.get(date.HOUR_OF_DAY)+":"+date.get(date.MINUTE)+":"+date.get(date.SECOND)+")";
                      String acceptMsg=sb.toString();
                      System.out.println(ljnTime+acceptMsg);
                      ServerMain.sendEveryone(acceptMsg);
                    }
                    stop();
                   ServerMain.removeObject(this);
                  } catch (IOException ioe) {
                  } catch (Exception e) {
                    e.printStackTrace();
                  }
                }
            public void stop() {
             try {
              stop = true;
              if(sender!=null){
                sender.stop1();
              }
              if (dis != null) {
               dis.close();
              }
              if (dos != null) {
               dos.close();
              }
              if (socke != null) {
               socke.close();
              }
             } catch (IOException ioe) {
             }
            }
          }
          /*********************************************Sender**************************/
          import java.io.*;
          public class Sender extends Thread {
           private DataOutputStream dos;
           private String message;
           public Sender(DataOutputStream os) {
            this.dos = os;
            start();
           }
           public synchronized void send(String msg) {
            message = msg;
            notify();
           }

           public synchronized void run() {

            while (true) {
             if (message == null) {
              try {
               wait();
              } catch (InterruptedException e) {
              }
             }

             if (message == null) {
              break;
             }

             try {
             dos.writeChars(message);
             dos.writeChars("\r\n");
             } catch (IOException ioe) {
              try {
                ServerMain.removeObject(this);
              }
              catch (Exception ex) {
              }
             }
             message = null;
            }
           }

           public synchronized void stop1() {
            message = null;
            notify();
           }
          }
          //下面為手機客戶端代碼

          import javax.microedition.midlet.*;
          import javax.microedition.lcdui.*;

          public class ChatClientMIDlet extends MIDlet {
            private static ChatClientMIDlet instance;
            public static ChatForm displayable = new ChatForm();
            /** Constructor */
            public ChatClientMIDlet() {
              instance = this;
            }

            /** Main method */
            public void startApp() {
              Display.getDisplay(this).setCurrent(displayable);
            }

            /** Handle pausing the MIDlet */
            public void pauseApp() {
            }

            /** Handle destroying the MIDlet */
            public void destroyApp(boolean unconditional) {
            }
            public static void setCurrent(Displayable s){
               Display.getDisplay(instance).setCurrent(s);
            }
            /** Quit the MIDlet */
            public static void quitApp() {
              instance.destroyApp(true);
              instance.notifyDestroyed();
              instance = null;
            }

          }
          /********************************************************ChatForm ************************/
          import com.nec.io.*;
          import javax.microedition.lcdui.*;
          import javax.microedition.io.*;
          import java.io.*;
          public class ChatForm extends Form implements CommandListener,Runnable {
            public Form mainForm=new Form("聊天室");
            public Command enter=new Command("進入",Command.OK,0);
            public Command send=new Command("發送",Command.OK,1);
            public Command exit=new Command("退出",Command.EXIT,0);
            public DataInputStream dis;
            public DataOutputStream dos;
            public SocketConnection sc;
            public Sender sender;
            public boolean stop;
            public TextField textField=new TextField("請輸入昵稱:", null, 10, TextField.ANY);
            public TextField info=new TextField("請輸入消息:", null,255, TextField.ANY);
            public ChoiceGroup choiceGroup=new ChoiceGroup(null,ChoiceGroup.EXCLUSIVE);
            public String MyName="游客";
            public boolean isCurrent=false;;
            public ChatForm() {
              super("我的聊天室");
              append(textField);
              addCommand(enter);
              mainForm.append(info);
              mainForm.append(choiceGroup);
              choiceGroup.append(");
              setCommandListener(this);
              mainForm.addCommand(send);
              mainForm.addCommand(exit);
              mainForm.setCommandListener(this);
            }
            public void commandAction(Command c, Displayable dis) {
              if(c==enter){
               if(textField.getString().length()==0){
                Alert alert=new  Alert("警告","昵稱不能為空!", null, AlertType.WARNING) ;
                alert.setTimeout(3000);
                ChatClientMIDlet.setCurrent(alert);
                return;
               }else
                 {
                   MyName = textField.getString();
                  append("正在進入......");
                 }
                Thread t=new Thread(this);
                t.start();
              }else if(c==send){
                if(info.getString().length()==0){
                  Alert alert=new  Alert("警告","消息內容不能為空!", null, AlertType.WARNING) ;
                  alert.setTimeout(3000);
                  ChatClientMIDlet.setCurrent(alert);
                  return;
                }
                sender.send(MyName+"說:"+info.getString());
                info.setString("");
              }
              else{
                stop();
                ChatClientMIDlet.quitApp();
              }
            }
            public void run() {
             try {
              sc = (SocketConnection) Connector.open("socket://127.0.0.1:5000");
              sc.setSocketOption(SocketConnection.LINGER, 5);
              dis = new DataInputStream(sc.openInputStream());
              dos = new DataOutputStream(sc.openOutputStream());
              sender = new Sender(dos);
              sender.send("歡迎"+MyName+"進入房間");
              while (true) {
                if(stop) break;
                StringBuffer sb = new StringBuffer();
               char c;
               while (((c = dis.readChar()) != '\n') && (c != -1)) {
                  sb.append(c);
                }
               if (c == -1)   break;
               if(!isCurrent){
                 ChatClientMIDlet.setCurrent(mainForm);
                 isCurrent=true;
               }
               String msg=sb.toString();
               msg=msg.substring(0,msg.length()-2);
               choiceGroup.insert(0,msg,null);
               choiceGroup.setSelectedIndex(0,true);
              }
              stop();
             } catch (ConnectionNotFoundException cnfe) {
             } catch (IOException ioe) {
              if (!stop) {
               ioe.printStackTrace();
              }
             } catch (Exception e) {
              e.printStackTrace();
             }
            }
            public void stop() {
              try {
               stop = true;
               if (sender != null) {
                sender.stop1();
               }

               if (dis != null) {
                dis.close();
               }

               if (dos != null) {
                dos.close();
               }

               if (sc != null) {
                sc.close();
               }
              } catch (IOException ioe) {
              }
             }

          }
          /**************************Sender*********************************/

          同上

          //怎樣獲取一個文件夾下中的所有文件
          File dir = new File("fileDirectory");
          String[] filel= dir.list();

          //oracle啟動方法
          --啟動數據庫
          su - oracle
          sqlplus /nolog
          connect /as sysdba
          startup
          exit
          --監聽
          lsnrctl
          start
          posted @ 2005-11-21 09:57 surffish 閱讀(227) | 評論 (0)編輯 收藏
          telnet 10.194.132.1
          ftp 192.168.10.1
          ls
          vi
          i
          esc
          :wq
          ctrl + c
          cp
          posted @ 2005-11-10 15:23 surffish 閱讀(286) | 評論 (0)編輯 收藏
           

          Weblogic 8.1 sp1 上調試《精通EJB 2》源代碼

           

           ----連接池及數據源ejbPool的配置.

           

          作者: naxin9999@163.com

          --------------------------------------------------------------------------------

           

          . 首先在mysql數據庫中新建一個me2庫,再建一個sa用戶(密碼:222222).授予權限.再提交源代碼中的EJB.sql文件 ( EJB.sql文件頭加上use me2; )

          具體如下:

          d:\mysql –u root –p

          mysql> create database me2;

          mysql> GRANT ALL PRIVILEGES ON me2.* TO sa@'localhost' IDENTIFIED BY '222222' WITH GRANT OPTION;

          mysql> quit

          d:\ mysql –u sa –p < EJB.sql

          password: (輸入222222)

          完成.

          . 下面開始創建DS:

          a.    mysql的驅動程序mm.mysql-2.0.14-bin.jar復制到 D:\bea\weblogic81\server\lib 目錄下.

          b.    修改D:\bea\user_projects\domains\mydomain 目錄下的startweblogic.cmd文件中的 CLASSPATH設置,加上%WL_HOME%\server\lib\mm.mysql-2.0.14-bin.jar;

          c.     起動weblogic,登陸 http://localhost:7001/console

           

          --------------------------------------------------------------------------------

           

           

          1.先建個pool (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          2. (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          3. (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          4. (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          5. (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          6. (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          7. 再建JDBC DS (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          8. (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          9. (下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          10(下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          11. (下圖)

           

           

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

           

          12. 部署你的EJB,可以進行測試了(下圖)

           

           

           

           

           

           

          --------------------------------------------------------------------------------

           

          13. 運行cmp 2.0的例子. Client輸出:

           

           

           

          14. Console 上輸出

           


          1.     http://localhost:7001/console 找到 Configure a new JMS Topic..

           


          2.如下圖輸入:


          3Create成功后.


          4.部署你的消息驅動Bean


          5.運行客戶端,run_client.bat

           


          6.weblogic控制臺上的輸出.

           

           

           

          posted @ 2005-10-31 15:35 surffish 閱讀(270) | 評論 (0)編輯 收藏

          /*$T BYBARP~1.H GC 1.137 09/13/05 08:37:52 */
          #ifndef ByBarPrinterH
           #define ByBarPrinterH
           #include "vcl.h"
           #define BPLA_OK    1000 //一切正常
                    ///
           #define BPLA_COMERROR  1001 //通訊錯或者未聯接打印機
           #define BPLA_PARAERROR  1002 //參數錯誤
           #define BPLA_FILEOPENERROR 1003 //文件打開錯誤
           #define BPLA_FILEREADERROR 1004 //文件讀錯誤
           #define BPLA_FILEWRITEERROR 1005 //文件寫錯誤
           #define BPLA_FILEERROR  1006 //文件不合要求
           #define BPLA_NUMBEROVER  1007 //指定的接收信息數量過大
           #define BPLA_IMAGETYPEERROR 1008 //圖象文件格式不正確

           #define BPLA_PAPERSHORT  1009 //缺紙
           #define BPLA_RIBBIONSHORT 1010 //缺色帶
           #define BPLA_BUSY   1011 //解釋器忙
           #define BPLA_PAUSE   1012 //暫停
                    ///中
           #define BPLA_HEADHEAT  1013 //打印頭過熱
           #define BPLA_HEADOVER  1014 //打印頭抬起
           #define BPLA_CUT   1015 //切刀錯
           #define BPLA_READERROR  1016 //read error

          //打開端口
          typedef int (__stdcall *mBY_Open_Port) (int devtype, int porttype, char *opencode, int codelength, int baudrate);

          //打印
          typedef int (__stdcall *mBY_Print_Label)
           (
            int LabelType,
            const char *JiDaJu,
            const char *FenFaJu,
            const char *TiaoMaXinXi,
            const char *HaoMa,
            const char *JianShu,
            const char *ZhongLiang,
            const char *ZhuanKouJuDaiHao,
            const char *XingHao,
            const char *BeiZhu,
            const char *BiaoPaiBianHao,
            const char *ZhongLeiJianCheng,
            const char *BenZhuan,
            int Pieces
           );

          //查詢狀態
          typedef int (__stdcall *mBY_Query_Status) ();

          //關閉端口
          typedef int (__stdcall *mBY_Close_Port) (char *closecode, long codelength);
          class PACKAGE CByBarPrinter
          {
          public:
           long OpenPort(long nPcIndex, long nPortIndex, long baudrate);
           void ClosePort(void);
           long PrintLabel
             (
              long LabelType,
              String JiDaJu,
              String FenFaJu,
              String TiaoMaXinXi,
              String HaoMa,
              String JianShu,
              String ZhongLiang,
              String ZhuanKouJuDaiHao,
              String XingHao,
              String BeiZhu,
              String BiaoPaiBianHao,
              String ZhongLeiJianCheng,
              String BenZhuan,
              long Pieces
             );
           CByBarPrinter(void);
           long GetStatus(void);
          private:
           HINSTANCE m_hDll;
           bool  LoadLib(void);
          };
          #endif

          /*$T BYBARP~1.CPP GC 1.137 09/13/05 08:37:52 */
          #pragma hdrstop
          #include "ByBarPrinter.h"

          /* ---------------------------------------------------------------------------------------------------------------------
           ----------------------------------------------------------------------------------------------------------------------- */
          CByBarPrinter::CByBarPrinter(void)
          {
           m_hDll = NULL;
          }

          /* ---------------------------------------------------------------------------------------------------------------------
           ----------------------------------------------------------------------------------------------------------------------- */
          bool CByBarPrinter::LoadLib(void)
          {
           if(m_hDll == NULL) m_hDll = ::LoadLibrary("byprndll.dll");
           if(m_hDll == NULL)
            return false;
           else
            return true;
          }

          /* ---------------------------------------------------------------------------------------------------------------------
           ----------------------------------------------------------------------------------------------------------------------- */
          long CByBarPrinter::OpenPort(long nPcIndex, long nPortIndex, long baudrate)
          {
           long retCode;
           if(!LoadLib())
           {
            retCode = -2;
            return retCode;
           }

           mBY_Open_Port BY_Open_Port;
           BY_Open_Port = (mBY_Open_Port) GetProcAddress(m_hDll, "BY_Open_Port");
           if(BY_Open_Port == NULL)
           {
            retCode = -1;
           }
           else
           {
            String szCom;
            szCom = "COM";
            szCom += IntToStr(nPortIndex);

            int nLen = szCom.Length();

            retCode = BY_Open_Port(nPcIndex, nPortIndex, szCom.c_str(), nLen, 19200);
           }

           return retCode;
          }

          /* ---------------------------------------------------------------------------------------------------------------------
           ----------------------------------------------------------------------------------------------------------------------- */
          void CByBarPrinter::ClosePort(void)
          {
           if(!LoadLib()) return;

           mBY_Close_Port BY_Close_Port;
           BY_Close_Port = (mBY_Close_Port) GetProcAddress(m_hDll, "BY_Close_Port");

           char CloseCode[50];
           int  CodeLength = 0;
           memset(CloseCode, 0, 50);
           BY_Close_Port(CloseCode, CodeLength);

           //FreeLibrary(m_hDll);
           delete[] CloseCode;
           return;
          }

          /* ---------------------------------------------------------------------------------------------------------------------
           ----------------------------------------------------------------------------------------------------------------------- */
          long CByBarPrinter::PrintLabel
          (
           long LabelType,
           String JiDaJu,
           String FenFaJu,
           String TiaoMaXinXi,
           String HaoMa,
           String JianShu,
           String ZhongLiang,
           String ZhuanKouJuDaiHao,
           String XingHao,
           String BeiZhu,
           String BiaoPaiBianHao,
           String ZhongLeiJianCheng,
           String BenZhuan,
           long Pieces
          )
          {
           long retCode;
           if(!LoadLib())
           {
            retCode = -2;
            return retCode;
           }

           //USES_CONVERSION;
           mBY_Print_Label BY_Print_Label;
           BY_Print_Label = (mBY_Print_Label) GetProcAddress(m_hDll, "BY_Print_Label");
           if(BY_Print_Label == NULL)
           {
            retCode = -1;
            return S_OK;
           }

           retCode = BY_Print_Label
            (
             LabelType,
             JiDaJu.c_str(),
             FenFaJu.c_str(),
             TiaoMaXinXi.c_str(),
             HaoMa.c_str(),
             JianShu.c_str(),
             ZhongLiang.c_str(),
             ZhuanKouJuDaiHao.c_str(),
             XingHao.c_str(),
             BeiZhu.c_str(),
             BiaoPaiBianHao.c_str(),
             ZhongLeiJianCheng.c_str(),
             BenZhuan.c_str(),
             Pieces
            );

           return retCode;
          }

          /* ---------------------------------------------------------------------------------------------------------------------
           ----------------------------------------------------------------------------------------------------------------------- */
          long CByBarPrinter::GetStatus(void)
          {
           long state;
           if(!LoadLib())
           {
            state = -1;
            return state;
           }

           mBY_Query_Status BY_Query_Status;
           BY_Query_Status = (mBY_Query_Status) GetProcAddress(m_hDll, "BY_Query_Status");
           if(BY_Query_Status == NULL)
            state = -1;
           else
            state = BY_Query_Status();

           return state;
          }

          #pragma package(smart_init)

          posted @ 2005-10-31 13:54 surffish 閱讀(245) | 評論 (0)編輯 收藏
          substr(string,1,8)

          1.
          //將一個表中符合條件的記錄批量更新到另外一張表。(假設表名為:toffice,tofficetemp)
          ??????update toffice a
          ??????set a.office_code = (select b.office_code from tofficetemp b where a.jgdm = b.jgdm)
          ??????where a.jgdm in (select jgdm from tofficetemp)
          ??????update tofficenexas set isuse = '0' where rowid in?
          ??????(select rowid from tofficenexas a where?
          ??????rowid !=(select max(rowid) from tofficenexas b where a.office_id = b.office_id and
          ??????a.father_office_id = b.father_office_id and a.big_kind_id =b.big_kind_id))

          2

          //查詢某個時間段的值

          select * from tdespatch t
          where t.out_time between to_date('2005-10-18 15:47:42','yyyy-mm-dd hh24:mi:ss')
          ?and to_date('2005-10-20 10:47:42','yyyy-mm-dd hh24:mi:ss')


          3。
          //復制表(只復制結構,源表名:a?新表名:b)
          SQL:?select?*?into?b?from?a?where?1<>1

          4。
          //拷貝表(拷貝數據,源表名:a?目標表名:b)
          SQL:?insert?into?b(a,?b,?c)?select?d,e,f?from?b;

          5。
          說明:顯示文章、提交人和最后回復時間
          SQL:?select?a.title,a.username,b.adddate?from?table?a,(select?max(adddate)?adddate?from?table?where?table.title=a.title)?b

          說明:外連接查詢(表名1:a?表名2:b)
          SQL:?select?a.a,?a.b,?a.c,?b.c,?b.d,?b.f?from?a?LEFT?OUT?JOIN?b?ON?a.a?=?b.c

          說明:日程安排提前五分鐘提醒
          SQL:??select?*?from?日程安排?where?datediff('minute',f開始時間,getdate())>5


          說明:兩張關聯表,刪除主表中已經在副表中沒有的信息
          SQL:??
          delete?from?info?where?not?exists?(?select?*?from?infobz?where?info.infid=infobz.infid?)?

          說明:--
          SQL:??
          SELECT?A.NUM,?A.NAME,?B.UPD_DATE,?B.PREV_UPD_DATE
          ??FROM?TABLE1,?
          ????(SELECT?X.NUM,?X.UPD_DATE,?Y.UPD_DATE?PREV_UPD_DATE
          ????????FROM?(SELECT?NUM,?UPD_DATE,?INBOUND_QTY,?STOCK_ONHAND
          ????????????????FROM?TABLE2
          ??????????????WHERE?TO_CHAR(UPD_DATE,'YYYY/MM')?=?TO_CHAR(SYSDATE,?'YYYY/MM'))?X,?
          ????????????(SELECT?NUM,?UPD_DATE,?STOCK_ONHAND
          ????????????????FROM?TABLE2
          ??????????????WHERE?TO_CHAR(UPD_DATE,'YYYY/MM')?=?
          ????????????????????TO_CHAR(TO_DATE(TO_CHAR(SYSDATE,?'YYYY/MM')?&brvbar;&brvbar;?'/01','YYYY/MM/DD')?-?1,?'YYYY/MM')?)?Y,?
          ????????WHERE?X.NUM?=?Y.NUM?(+)
          ??????????AND?X.INBOUND_QTY?+?NVL(Y.STOCK_ONHAND,0)?<>?X.STOCK_ONHAND?)?B
          WHERE?A.NUM?=?B.NUM

          說明:--
          SQL:??
          select?*?from?studentinfo?where?not?exists(select?*?from?student?where?studentinfo.id=student.id)?and?系名稱='"&strdepartmentname&"'?and?專業名稱='"&strprofessionname&"'?order?by?性別,生源地,高考總成績

          說明:
          從數據庫中去一年的各單位電話費統計(電話費定額賀電化肥清單兩個表來源)
          SQL:?
          SELECT?a.userper,?a.tel,?a.standfee,?TO_CHAR(a.telfeedate,?'yyyy')?AS?telyear,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'01',?a.factration))?AS?JAN,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'02',?a.factration))?AS?FRI,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'03',?a.factration))?AS?MAR,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'04',?a.factration))?AS?APR,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'05',?a.factration))?AS?MAY,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'06',?a.factration))?AS?JUE,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'07',?a.factration))?AS?JUL,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'08',?a.factration))?AS?AGU,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'09',?a.factration))?AS?SEP,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'10',?a.factration))?AS?OCT,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'11',?a.factration))?AS?NOV,
          ??????SUM(decode(TO_CHAR(a.telfeedate,?'mm'),?'12',?a.factration))?AS?DEC
          FROM?(SELECT?a.userper,?a.tel,?a.standfee,?b.telfeedate,?b.factration
          ????????FROM?TELFEESTAND?a,?TELFEE?b
          ????????WHERE?a.tel?=?b.telfax)?a
          GROUP?BY?a.userper,?a.tel,?a.standfee,?TO_CHAR(a.telfeedate,?'yyyy')

          說明:四表聯查問題:
          SQL:?select?*?from?a?left?inner?join?b?on?a.a=b.b?right?inner?join?c?on?a.a=c.c??inner?join?d?on?a.a=d.d?where?.....

          說明:得到表中最小的未使用的ID號
          SQL:
          SELECT?(CASE?WHEN?EXISTS(SELECT?*?FROM?Handle?b?WHERE?b.HandleID?=?1)?THEN?MIN(HandleID)?+?1?ELSE?1?END)?as?HandleID
          ?FROM??Handle
          ?WHERE?NOT?HandleID?IN?(SELECT?a.HandleID?-?1?FROM?Handle?a)
          6?? 根據父表 只取子表最新的一條記錄{

          SQL> select * from testa ;

          ?

          ??????? F1

          ----------

          ???????? 1

          ???????? 2

          ???????? 3

          SQL> select * from testb ;

          ??????? F1 F2

          ---------- -------------------

          ???????? 1 2006-04-10 14:56:41

          ???????? 1 2006-04-10 14:56:53

          ???????? 1 2006-04-10 14:57:00

          ???????? 2 2006-04-10 14:57:08

          ???????? 3 2006-04-10 14:57:19

          ???????? 3 2006-04-10 14:57:25?

          6 rows selected.

          SQL> select testa.f1,v1.f1,v1.f2 from testa ,(select f1,f2,row_number() over (partition by f1 order

          by f2 desc) rn from testb) v1 where testa.f1 = v1.f1 and v1.rn = 1 ;

          ??????? F1???????? F1 F2

          ---------- ---------- -------------------

          ???????? 1??????? ??1 2006-04-10 14:57:00

          ???????? 2????????? 2 2006-04-10 14:57:08

          ???????? 3????????? 3 2006-04-10 14:57:25
          ------------------------------------------

          7 查詢語句的優化
          select t.* from acc$ t
          where
          not exists (在此不使用not in 是為了性能上的考慮
          (select 'a' from crm$ a
          where
          a.客戶名=t.用戶名稱
          --a.客戶號=t.用戶編號

          )

          posted @ 2005-10-30 09:42 surffish 閱讀(282) | 評論 (0)編輯 收藏
          用連接池提高Servlet訪問數據庫的效率

          作者:[本站編輯] 來源:[CSDN] 瀏覽:[ ]


          Java Servlet作為首選的服務器端數據處理技術,正在迅速取代CGI腳本。Servlet超越CGI的優勢之一在于,不僅多個請求可以共享公用資源,而且還可以在不同用戶請求之間保留持續數據。本文介紹一種充分發揮該特色的實用技術,即數據庫連接池。

          一、實現連接池的意義

          動態Web站點往往用數據庫存儲的信息生成Web頁面,每一個頁面請求導致一次數據庫訪問。連接數據庫不僅要開銷一定的通訊和內存資源,還必須完成用戶驗證、安全上下文配置這類任務,因而往往成為最為耗時的操作。當然,實際的連接時間開銷千變萬化,但1到2秒延遲并非不常見。如果某個基于數據庫的Web應用只需建立一次初始連
          接,不同頁面請求能夠共享同一連接,就能獲得顯著的性能改善。
          Servlet是一個Java類。Servlet引擎(它可能是Web服務軟件的一部分,也可能是一個獨立的附加模塊)在系統啟動或Servlet第一次被請求時將該類裝入Java虛擬機并創建它的一個實例。不同用戶請求由同一Servlet實例的多個獨立線程處理。那些要
          求在不同請求之間持續有效的數據既可以用Servlet的實例變量來保存,也可以保存在獨立的輔助對象中。
          用JDBC訪問數據庫首先要創建與數據庫之間的連接,獲得一個連接對象(Connection),由連接對象提供執行SQL語句的方法。
          本文介紹的數據庫連接池包括一個管理類DBConnectionManager,負責提供與多個連接池對象(DBConnectionPool類)之間的接口。每一個連接池對象管理一組JDBC連接對象,每一個連接對象可以被任意數量的Servlet共享。
          類DBConnectionPool提供以下功能:

          1) 從連接池獲取(或創建)可用連接。
          2) 把連接返回給連接池。
          3) 在系統關閉時釋放所有資源,關閉所有連接。

          此外, DBConnectionPool類還能夠處理無效連接(原來登記為可用的連接,由于某種原因不再可用,如超時,通訊問題)
          ,并能夠限制連接池中的連接總數不超過某個預定值。
          管理類DBConnectionManager用于管理多個連接池對象,它提供以下功能:

          1) 裝載和注冊JDBC驅動程序。
          2) 根據在屬性文件中定義的屬性創建連接池對象。
          3) 實現連接池名字與其實例之間的映射。
          4) 跟蹤客戶程序對連接池的引用,保證在最后一個客戶程序結束時安全地關閉所有連接池。

          本文余下部分將詳細說明這兩個類,最后給出一個示例演示Servlet使用連接池的一般過程。

          二、具體實現

          DBConnectionManager.java程序清單如下:

          001 import java.io.*;
          002 import java.sql.*;
          003 import java.util.*;
          004 import java.util.Date;
          005
          006 /**
          007 * 管理類DBConnectionManager支持對一個或多個由屬性文件定義的數據庫連接
          008 * 池的訪問.客戶程序可以調用getInstance()方法訪問本類的唯一實例.
          009 */
          010 public class DBConnectionManager {
          011 static private DBConnectionManager instance; // 唯一實例
          012 static private int clients;
          013
          014 private Vector drivers = new Vector();
          015 private PrintWriter log;
          016 private Hashtable pools = new Hashtable();
          017
          018 /**
          019 * 返回唯一實例.如果是第一次調用此方法,則創建實例
          020 *
          021 * @return DBConnectionManager 唯一實例
          022 */
          023 static synchronized public DBConnectionManager getInstance() {
          024 if (instance == null) {
          025 instance = new DBConnectionManager();
          026 }
          027 clients++;
          028 return instance;
          029 }
          030
          031 /**
          032 * 建構函數私有以防止其它對象創建本類實例
          033 */
          034 private DBConnectionManager() {
          035 init();
          036 }
          037
          038 /**
          039 * 將連接對象返回給由名字指定的連接池
          040 *
          041 * @param name 在屬性文件中定義的連接池名字
          042 * @param con 連接對象
          043 */
          044 public void freeConnection(String name, Connection con) {
          045 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
          046 if (pool != null) {
          047 pool.freeConnection(con);
          048 }
          049 }
          050
          051 /**
          052 * 獲得一個可用的(空閑的)連接.如果沒有可用連接,且已有連接數小于最大連接數
          053 * 限制,則創建并返回新連接
          054 *
          055 * @param name 在屬性文件中定義的連接池名字
          056 * @return Connection 可用連接或null
          057 */
          058 public Connection getConnection(String name) {
          059 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
          060 if (pool != null) {
          061 return pool.getConnection();
          062 }
          063 return null;
          064 }
          065
          066 /**
          067 * 獲得一個可用連接.若沒有可用連接,且已有連接數小于最大連接數限制,
          068 * 則創建并返回新連接.否則,在指定的時間內等待其它線程釋放連接.
          069 *
          070 * @param name 連接池名字
          071 * @param time 以毫秒計的等待時間
          072 * @return Connection 可用連接或null
          073 */
          074 public Connection getConnection(String name, long time) {
          075 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
          076 if (pool != null) {
          077 return pool.getConnection(time);
          078 }
          079 return null;
          080 }
          081
          082 /**
          083 * 關閉所有連接,撤銷驅動程序的注冊
          084 */
          085 public synchronized void release() {
          086 // 等待直到最后一個客戶程序調用
          087 if (--clients != 0) {
          088 return;
          089 }
          090
          091 Enumeration allPools = pools.elements();
          092 while (allPools.hasMoreElements()) {
          093 DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
          094 pool.release();
          095 }
          096 Enumeration allDrivers = drivers.elements();
          097 while (allDrivers.hasMoreElements()) {
          098 Driver driver = (Driver) allDrivers.nextElement();
          099 try {
          100 DriverManager.deregisterDriver(driver);
          101 log("撤銷JDBC驅動程序 " + driver.getClass().getName()+"的注冊");
          102 }
          103 catch (SQLException e) {
          104 log(e, "無法撤銷下列JDBC驅動程序的注冊: " + driver.getClass().getName());
          105 }
          106 }
          107 }
          108
          109 /**
          110 * 根據指定屬性創建連接池實例.
          111 *
          112 * @param props 連接池屬性
          113 */
          114 private void createPools(Properties props) {
          115 Enumeration propNames = props.propertyNames();
          116 while (propNames.hasMoreElements()) {
          117 String name = (String) propNames.nextElement();
          118 if (name.endsWith(".url")) {
          119 String poolName = name.substring(0, name.lastIndexOf("."));
          120 String url = props.getProperty(poolName + ".url");
          121 if (url == null) {
          122 log("沒有為連接池" + poolName + "指定URL");
          123 continue;
          124 }
          125 String user = props.getProperty(poolName + ".user");
          126 String password = props.getProperty(poolName + ".password");
          127 String maxconn = props.getProperty(poolName + ".maxconn", "0");
          128 int max;
          129 try {
          130 max = Integer.valueOf(maxconn).intValue();
          131 }
          132 catch (NumberFormatException e) {
          133 log("錯誤的最大連接數限制: " + maxconn + " .連接池: " + poolName);
          134 max = 0;
          135 }
          136 DBConnectionPool pool =
          137 new DBConnectionPool(poolName, url, user, password, max);
          138 pools.put(poolName, pool);
          139 log("成功創建連接池" + poolName);
          140 }
          141 }
          142 }
          143
          144 /**
          145 * 讀取屬性完成初始化
          146 */
          147 private void init() {
          148 InputStream is = getClass().getResourceAsStream("/db.properties");
          149 Properties dbProps = new Properties();
          150 try {
          151 dbProps.load(is);
          152 }
          153 catch (Exception e) {
          154 System.err.println("不能讀取屬性文件. " +
          155 "請確保db.properties在CLASSPATH指定的路徑中");
          156 return;
          157 }
          158 String logFile = dbProps.getProperty("logfile", "DBConnectionManager.log");
          159 try {
          160 log = new PrintWriter(new FileWriter(logFile, true), true);
          161 }
          162 catch (IOException e) {
          163 System.err.println("無法打開日志文件: " + logFile);
          164 log = new PrintWriter(System.err);
          165 }
          166 loadDrivers(dbProps);
          167 createPools(dbProps);
          168 }
          169
          170 /**
          171 * 裝載和注冊所有JDBC驅動程序
          172 *
          173 * @param props 屬性
          174 */
          175 private void loadDrivers(Properties props) {
          176 String driverClasses = props.getProperty("drivers");
          177 StringTokenizer st = new StringTokenizer(driverClasses);
          178 while (st.hasMoreElements()) {
          179 String driverClassName = st.nextToken().trim();
          180 try {
          181 Driver driver = (Driver)
          182 Class.forName(driverClassName).newInstance();
          183 DriverManager.registerDriver(driver);
          184 drivers.addElement(driver);
          185 log("成功注冊JDBC驅動程序" + driverClassName);
          186 }
          187 catch (Exception e) {
          188 log("無法注冊JDBC驅動程序: " +
          189 driverClassName + ", 錯誤: " + e);
          190 }
          191 }
          192 }
          193
          194 /**
          195 * 將文本信息寫入日志文件
          196 */
          197 private void log(String msg) {
          198 log.println(new Date() + ": " + msg);
          199 }
          200
          201 /**
          202 * 將文本信息與異常寫入日志文件
          203 */
          204 private void log(Throwable e, String msg) {
          205 log.println(new Date() + ": " + msg);
          206 e.printStackTrace(log);
          207 }
          208
          209 /**
          210 * 此內部類定義了一個連接池.它能夠根據要求創建新連接,直到預定的最
          211 * 大連接數為止.在返回連接給客戶程序之前,它能夠驗證連接的有效性.
          212 */
          213 class DBConnectionPool {
          214 private int checkedOut;
          215 private Vector freeConnections = new Vector();
          216 private int maxConn;
          217 private String name;
          218 private String password;
          219 private String URL;
          220 private String user;
          221
          222 /**
          223 * 創建新的連接池
          224 *
          225 * @param name 連接池名字
          226 * @param URL 數據庫的JDBC URL
          227 * @param user 數據庫帳號,或 null
          228 * @param password 密碼,或 null
          229 * @param maxConn 此連接池允許建立的最大連接數
          230 */
          231 public DBConnectionPool(String name, String URL, String user, String password,
          232 int maxConn) {
          233 this.name = name;
          234 this.URL = URL;
          235 this.user = user;
          236 this.password = password;
          237 this.maxConn = maxConn;
          238 }
          239
          240 /**
          241 * 將不再使用的連接返回給連接池
          242 *
          243 * @param con 客戶程序釋放的連接
          244 */
          245 public synchronized void freeConnection(Connection con) {
          246 // 將指定連接加入到向量末尾
          247 freeConnections.addElement(con);
          248 checkedOut--;
          249 notifyAll();
          250 }
          251
          252 /**
          253 * 從連接池獲得一個可用連接.如沒有空閑的連接且當前連接數小于最大連接
          254 * 數限制,則創建新連接.如原來登記為可用的連接不再有效,則從向量刪除之,
          255 * 然后遞歸調用自己以嘗試新的可用連接.
          256 */
          257 public synchronized Connection getConnection() {
          258 Connection con = null;
          259 if (freeConnections.size() > 0) {
          260 // 獲取向量中第一個可用連接
          261 con = (Connection) freeConnections.firstElement();
          262 freeConnections.removeElementAt(0);
          263 try {
          264 if (con.isClosed()) {
          265 log("從連接池" + name+"刪除一個無效連接");
          266 // 遞歸調用自己,嘗試再次獲取可用連接
          267 con = getConnection();
          268 }
          269 }
          270 catch (SQLException e) {
          271 log("從連接池" + name+"刪除一個無效連接");
          272 // 遞歸調用自己,嘗試再次獲取可用連接
          273 con = getConnection();
          274 }
          275 }
          276 else if (maxConn == 0 || checkedOut < maxConn) {
          277 con = newConnection();
          278 }
          279 if (con != null) {
          280 checkedOut++;
          281 }
          282 return con;
          283 }
          284
          285 /**
          286 * 從連接池獲取可用連接.可以指定客戶程序能夠等待的最長時間
          287 * 參見前一個getConnection()方法.
          288 *
          289 * @param timeout 以毫秒計的等待時間限制
          290 */
          291 public synchronized Connection getConnection(long timeout) {
          292 long startTime = new Date().getTime();
          293 Connection con;
          294 while ((con = getConnection()) == null) {
          295 try {
          296 wait(timeout);
          297 }
          298 catch (InterruptedException e) {}
          299 if ((new Date().getTime() - startTime) >= timeout) {
          300 // wait()返回的原因是超時
          301 return null;
          302 }
          303 }
          304 return con;
          305 }
          306
          307 /**
          308 * 關閉所有連接
          309 */
          310 public synchronized void release() {
          311 Enumeration allConnections = freeConnections.elements();
          312 while (allConnections.hasMoreElements()) {
          313 Connection con = (Connection) allConnections.nextElement();
          314 try {
          315 con.close();
          316 log("關閉連接池" + name+"中的一個連接");
          317 }
          318 catch (SQLException e) {
          319 log(e, "無法關閉連接池" + name+"中的連接");
          320 }
          321 }
          322 freeConnections.removeAllElements();
          323 }
          324
          325 /**
          326 * 創建新的連接
          327 */
          328 private Connection newConnection() {
          329 Connection con = null;
          330 try {
          331 if (user == null) {
          332 con = DriverManager.getConnection(URL);
          333 }
          334 else {
          335 con = DriverManager.getConnection(URL, user, password);
          336 }
          337 log("連接池" + name+"創建一個新的連接");
          338 }
          339 catch (SQLException e) {
          340 log(e, "無法創建下列URL的連接: " + URL);
          341 return null;
          342 }
          343 return con;
          344 }
          345 }
          346 }

          三、類DBConnectionPool說明

          該類在209至345行實現,它表示指向某個數據庫的連接池。數據庫由JDBC URL標識。一個JDBC URL由三部分組成:協議標識(總是jdbc),驅動程序標識(如 odbc、idb、oracle等),數據庫標識(其格式依賴于驅動程序)。例如,jdbc:odbc:de
          mo,即是一個指向demo數據庫的JDBC URL,而且訪問該數據庫要使用JDBC-ODBC驅動程序。每個連接池都有一個供客戶程序使用的名字以及可選的用戶帳號、密碼、最大連接數限制。如果Web應用程序所支持的某些數據庫操作可以被所有用戶執行,而其它一些操作應由特別許可的用戶執行,則可以為兩類操作分別定義連接池,兩個連接池使用相同的JDBC URL,但使用不同的帳號和密碼。
          類DBConnectionPool的建構函數需要上述所有數據作為其參數。如222至238行所示,這些數據被保存為它的實例變量:
          如252至283行、285至305行所示, 客戶程序可以使用DBConnectionPool類提供的兩個方法獲取可用連接。兩者的共同之處在于:如連接池中存在可用連接,則直接返回,否則創建新的連接并返回。如果沒有可用連接且已有連接總數等于最大限制
          數,第一個方法將直接返回null,而第二個方法將等待直到有可用連接為止。
          所有的可用連接對象均登記在名為freeConnections的向量(Vector)中。如果向量中有多于一個的連接,getConnection()總是選取第一個。同時,由于新的可用連接總是從尾部加入向量,從而使得數據庫連接由于長時間閑置而被關閉的風險減低到最小程度。
          第一個getConnection()在返回可用連接給客戶程序之前,調用了isClosed()方法驗證連接仍舊有效。如果該連接被關閉或觸發異常,getConnection()遞歸地調用自己以嘗試獲取另外的可用連接。如果在向量freeConnections中不存在任何可用連
          接,getConnection()方法檢查是否已經指定最大連接數限制。如已經指定,則檢查當前連接數是否已經到達極限。此處maxConn為0表示沒有限制。如果沒有指定最大連接數限制或當前連接數小于該值,該方法嘗試創建新的連接。如創建成功,則增加已使用連接的計數并返回,否則返回空值。
          如325至345行所示,創建新連接由newConnection()方法實現。創建過程與是否已經指定數據庫帳號、密碼有關。
          JDBC的DriverManager類提供多個getConnection()方法,這些方法要用到JDBC URL與其它一些參數,如用戶帳號和密碼等。
          DriverManager將使用指定的JDBC URL確定適合于目標數據庫的驅動程序及建立連接。
          在285至305行實現的第二個getConnection()方法需要一個以毫秒為單位的時間參數,該參數表示客戶程序能夠等待的最長時間。建立連接的具體操作仍舊由第一個getConnection()方法實現。
          該方法執行時先將startTime初始化為當前時間。在while循環中嘗試獲得一個連接。如果失敗,則以給定的時間值為參數調用wait()。wait()的返回可能是由于其它線程調用notify()或notifyAll(),也可能是由于預定時間已到。為找出wait()返回的真正原因,程序用當前時間減開始時間(startTime),如差值大于預定時間則返回空值,否則再次調用getConnection()。
          把空閑的連接登記到連接池由240至250行的freeConnection()方法實現,它的參數為返回給連接池的連接對象。該對象被加入到freeConnections向量的末尾,然后減少已使用連接計數。調用notifyAll()是為了通知其它正在等待可用連接的線程。
          許多Servlet引擎為實現安全關閉提供多種方法。數據庫連接池需要知道該事件以保證所有連接能夠正常關閉。DBConnectionManager類負協調整個關閉過程,但關閉連接池中所有連接的任務則由DBConnectionPool類負責。在307至323行實現的release()方法供DBConnectionManager調用。該方法遍歷freeConnections向量并關閉所有連接,然后從向量中刪除這些連接。

          四、類DBConnectionManager 說明

          該類只能創建一個實例,其它對象能夠調用其靜態方法(也稱為類方法)獲得該唯一實例的引用。如031至036行所示,DBConnectionManager類的建構函數是私有的,這是為了避免其它對象創建該類的實例。
          DBConnectionManager類的客戶程序可以調用getInstance()方法獲得對該類唯一實例的引用。如018至029行所示,類的唯一實例在getInstance()方法第一次被調用期間創建,此后其引用就一直保存在靜態變量instance中。每次調用getInstance()
          都增加一個DBConnectionManager的客戶程序計數。即,該計數代表引用DBConnectionManager唯一實例的客戶程序總數,它將被用于控制連接池的關閉操作。
          該類實例的初始化工作由146至168行之間的私有方法init()完成。其中 getResourceAsStream()方法用于定位并打開外部文件。外部文件的定位方法依賴于類裝載器的實現。標準的本地類裝載器查找操作總是開始于類文件所在路徑,也能夠搜索CLASSPATH中聲明的路徑。db.properties是一個屬性文件,它包含定義連接池的鍵-值對。可供定義的公用屬性如下:

          drivers 以空格分隔的JDBC驅動程序類列表
          logfile 日志文件的絕對路徑

          其它的屬性和特定連接池相關,其屬性名字前應加上連接池名字:

          <poolname>.url 數據庫的 JDBC URL
          <poolname>.maxconn 允許建立的最大連接數,0表示沒有限制
          <poolname>.user 用于該連接池的數據庫帳號
          <poolname>.password 相應的密碼

          其中url屬性是必需的,而其它屬性則是可選的。數據庫帳號和密碼必須合法。用于Windows平臺的db.properties文件示例
          如下:

          drivers=sun.jdbc.odbc.JdbcOdbcDriver jdbc.idbDriver
          logfile=D:\\user\\src\\java\\DBConnectionManager\\log.txt

          idb.url=jdbc:idb:c:\\local\\javawebserver1.1\\db\\db.prp
          idb.maxconn=2

          access.url=jdbc:odbc:demo
          access.user=demo
          access.password=demopw

          注意在Windows路徑中的反斜杠必須輸入2個,這是由于屬性文件中的反斜杠同時也是一個轉義字符。
          init()方法在創建屬性對象并讀取db.properties文件之后,就開始檢查logfile屬性。如果屬性文件中沒有指定日志文件,則默認為當前目錄下的DBConnectionManager.log文件。如日志文件無法使用,則向System.err輸出日志記錄。
          裝載和注冊所有在drivers屬性中指定的JDBC驅動程序由170至192行之間的loadDrivers()方法實現。該方法先用StringTokenizer將drivers屬性值分割為對應于驅動程序名稱的字符串,然后依次裝載這些類并創建其實例,最后在 DriverManager中注冊
          該實例并把它加入到一個私有的向量drivers。向量drivers將用于關閉服務時從DriverManager取消所有JDBC 驅動程序的注冊。
          init()方法的最后一個任務是調用私有方法createPools()創建連接池對象。如109至142行所示,createPools()方法先創建所有屬性名字的枚舉對象(即Enumeration對象,該對象可以想象為一個元素系列,逐次調用其nextElement()方法將順序返
          回各元素),然后在其中搜索名字以“.url”結尾的屬性。對于每一個符合條件的屬性,先提取其連接池名字部分,進而讀取所有屬于該連接池的屬性,最后創建連接池對象并把它保存在實例變量pools中。散列表(Hashtable類 )pools實現連接
          池名字到連接池對象之間的映射,此處以連接池名字為鍵,連接池對象為值。
          為便于客戶程序從指定連接池獲得可用連接或將連接返回給連接池,類DBConnectionManager提供了方法getConnection()和freeConnection()。所有這些方法都要求在參數中指定連接池名字,具體的連接獲取或返回操作則調用對應的連接池對象完成。它們的實現分別在051至064行、066至080行、038至049行。
          如082至107行所示,為實現連接池的安全關閉,DBConnectionManager提供了方法release()。在上面我們已經提到,所有DBConnectionManager的客戶程序都應該調用靜態方法getInstance()以獲得該管理器的引用,此調用將增加客戶程序計數。
          客戶程序在關閉時調用release()可以遞減該計數。當最后一個客戶程序調用release(),遞減后的引用計數為0,就可以調用各個連接池的release()方法關閉所有連接了。管理類release()方法最后的任務是撤銷所有JDBC驅動程序的注冊。

          五、Servlet使用連接池示例

          Servlet API所定義的Servlet生命周期類如:

          1) 創建并初始化Servlet(init()方法)。
          2) 響應客戶程序的服務請求(service()方法)。
          3) Servlet終止運行,釋放所有資源(destroy()方法)。

          本例演示連接池應用,上述關鍵步驟中的相關操作為:

          1) 在init(),用實例變量connMgr 保存調用DBConnectionManager.getInstance()所返回的引用。
          2) 在service(),調用getConnection(),執行數據庫操作,用freeConnection()將連接返回給連接池。
          3) 在destroy(),調用release()關閉所有連接,釋放所有資源。

          示例程序清單如下:

          import java.io.*;
          import java.sql.*;
          import javax.servlet.*;
          import javax.servlet.http.*;
          public class TestServlet extends HttpServlet {
          private DBConnectionManager connMgr;

          public void init(ServletConfig conf) throws ServletException {
          super.init(conf);
          connMgr = DBConnectionManager.getInstance();
          }

          public void service(HttpServletRequest req, HttpServletResponse res)
          throws IOException {

          res.setContentType("text/html");
          PrintWriter out = res.getWriter();
          Connection con = connMgr.getConnection("idb");
          if (con == null) {
          out.println("不能獲取數據庫連接.");
          return;
          }
          ResultSet rs = null;
          ResultSetMetaData md = null;
          Statement stmt = null;
          try {
          stmt = con.createStatement();
          rs = stmt.executeQuery("SELECT * FROM EMPLOYEE");
          md = rs.getMetaData();
          out.println("<H1>職工數據</H1>");
          while (rs.next()) {
          out.println("<BR>");
          for (int i = 1; i < md.getColumnCount(); i++) {
          out.print(rs.getString(i) + ", ");
          }
          }
          stmt.close();
          rs.close();
          }
          catch (SQLException e) {
          e.printStackTrace(out);
          }
          connMgr.freeConnection("idb", con);
          }

          public void destroy() {
          connMgr.release();
          super.destroy();
          }
          }

          posted @ 2005-10-28 09:50 surffish 閱讀(301) | 評論 (0)編輯 收藏

          exp airport/airport@ems file=airport2.dmp owner=airport log=exp2.log

          posted @ 2005-10-26 09:56 surffish 閱讀(1186) | 評論 (0)編輯 收藏
          僅列出標題
          共3頁: 上一頁 1 2 3 下一頁 
          主站蜘蛛池模板: 辛集市| 星座| 沅陵县| 芜湖市| 博湖县| 偃师市| 云龙县| 五家渠市| 桓仁| 伽师县| 仁化县| 蒙阴县| 蕲春县| 永修县| 闽侯县| 芷江| 大邑县| 天台县| 玉龙| 衡阳市| 旅游| 丰顺县| 杭锦后旗| 宣汉县| 鄂伦春自治旗| 通州区| 朝阳市| 渑池县| 庆城县| 涞源县| 连云港市| 思茅市| 遂宁市| 富蕴县| 桃园县| 咸阳市| 会东县| 宁阳县| 高平市| 海晏县| 泉州市|