<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Leo's Blog

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      13 隨筆 :: 3 文章 :: 18 評論 :: 0 Trackbacks

    2006年5月9日 #

    ??? 昨天讀了一篇關于JDBC4.0設計與性能提高的文章,由于小弟英語翻譯水準實在有限,就不在這里獻丑了,就把原文給大家轉載出來供大家閱讀:

    轉載自:http://www.javaworld.com/javaworld/jw-05-2006/jw- 0501-jdbc .html

    Design and performance improvements with JDBC 4.0

    Effectively utilize JDBC'S features to get desired results with less code

    Summary
    Java Database Connectivity (JDBC) 4.0 is ready for release by mid 2006 as a part of Java Standard Edition 6.0. How can you leverage the new specification to improve the design and performance of database access and interactions in Java applications? This article discusses the new features of JDBC 4.0, illustrates its solutions to some existing problems, and presents its improvements in design and performance through examples. (2,100 words; May 1, 2006)
    By Shashank Tiwari


    Java Database Connectivity (JDBC), which has existed from the first public version of the core Java language, has evolved significantly over the last 10 years. In its current version, 4.0, which will be packaged with Java Standard Edition 6.0 (Java SE is Sun's new name for J2SE), it shows significant improvements in design and provides a richer API, with focus on ease of development and improvement in productivity.

    This article discusses some of the important changes in the JDBC specification that either improve the design or facilitate better performance. The article does not enlist or survey every single change incorporated as a part of Java Specification Request 221, the JDBC 4.0 initiative.

    After reading this article, you should be ready to leverage the new features in your next set of applications.

    Annotations and the generic DataSet
    I assume you are already aware of annotations and generics, which were introduced in Java with J2SE 5.0. JDBC 4.0 introduces annotations and the generic DataSet. This change aims to simplify execution of SQL queries (in scenarios that return a single result set) and SQL DML (data manipulation language) statements (that return either a row count or nothing).

    The new API defines a set of Query and DataSet interfaces. The Query interface defines a set of methods decorated with the JDBC annotations. These decorated methods describe the SQL select and update statements, and specify how the result set should be bound to a DataSet. The DataSet interface is a parameterized type, as defined by generics. The DataSet interface provides a type-safe definition for the result set data.

    All Query interfaces inherit from the BaseQuery interface. A concrete implementation of the interface can be instantiated using either the Connection.createQueryObject() or DataSource.createQueryObject() methods and passing a Query interface type as its parameter.

    A DataSet interface inherits from java.util.List. A data class describing the columns of the result set data, returned by an annotated method of the Query interface, is its parameter type. A DataSet can be manipulated and operated upon both in a connected and disconnected mode. Thus, the DataSet is implemented either as a ResultSet or a CachedRowSet, depending on its operating mode: connected or disconnected. DataSet, being a sub-interface of the java.util.List, allows access of its data rows with the Iterator pattern, using the java.util.Iterator interface.

    The data class or the user-defined class, which is a parameter type of the DataSet interface, can be specified in two ways: as a structure or as a JavaBeans object. Either method achieves the goal of binding result set data columns to user-defined class definitions, but the JavaBeans component model is more elegant and facilitates object definition reuse within other frameworks that support the JavaBeans model.

    Listing 1 illustrates code snippets for a simple example to show how the new API is used to create and run SQL queries, define result set data using a user-defined class, and bind the returned result set to the user-defined specifications.

    Listing 1. Employee user-defined type and employeeQueries

    pubic class Employee {
    ?? private int employeeId;
    ?? private String firstName;
    ?? private String lastName;

    ?? public int getEmployeeId() {
    ??????return employeeId;
    ?? }
    ??
    ?? public setEmployeeId(int employeeId) {
    ??????this.employeeId = employeeId;
    ?? }

    ?? public String getFirstName() {
    ??????return firstName;
    ?? }

    ?? public setFirstName(String firstName) {
    ??????this.firstName = firstName;
    ?? }

    ?? pubic String lastName() {
    ??????return lastName;
    ?? }

    ?? public setLastName(String lastName) {
    ??????this.lastName = lastName;
    ?? }
    }


    interface EmployeeQueries extends BaseQuery {
    ?? @Select (sql="SELECT employeeId, firstName, lastName FROM employee")
    ?? DataSet<Employee> getAllEmployees ();

    ?? @Update (sql="delete from employee")
    ?? int deleteAllEmployees ();
    }


    Connection con = ...

    EmployeeQueries empQueries = con.createQueryObject (EmployeeQueries.class);

    DataSet<Employee> empData = empQueries.getAllEmployees ();

    Exception-handling enhancements
    The exception-handling functionality in the JDBC API prior to version 4.0 is limited and often insufficient. SQLException is thrown for all types of errors. There is no classification of exceptions, and no hierarchy defines them. The only way to get some meaningful information is to retrieve and analyze the SQLState value. SQLState values and their corresponding meanings change from datasource to datasource; hence, getting to the root of the problem and efficiently handling exceptions proves to be a tedious task.

    JDBC 4.0 has enhanced exception-handling capability and alleviates some of the mentioned problems. The key changes are as follows:

    • Classification of SQLException into transient and non-transient types
    • Support for chained exceptions
    • Implementation of the Iterable interface

    The SQLTransientException is thrown where a previously failed operation may succeed on retrial. The SQLNonTransientException is thrown where retrial will not lead to a successful operation unless the cause of the SQLException is corrected.

    Figure 1 illustrates the subclasses of SQLTransientException and SQLNonTransientException.


    Figure 1. SQL exception classes: Transient and non-transient

    Support for chained exceptions are now included. New constructors add extra parameters to capture possible causes for the exception. Multiple SQLExceptions could be iterated over in a loop, and getCause() could be called to determine the exception's possible cause. The getCause() method can return non-SQLExceptions if they are the underlying cause of the exceptions.

    The SQLException class now implements the Iterable interface and supports the J2SE 5.0 for each loop for easier and more elegant looping.

    Listing 2 depicts the usage of the new for-each-loop construct:

    Listing 2. For each loop

    catch(SQLException ex) {
    ?? for(Throwable t : ex) {
    ??????System.out.println("exception:" + t);
    ?? }
    }


    SQL/XML
    A large amount of data now exists in the XML format. Databases have extended support for the XML data type by defining a standard XML type in the SQL 2003 specification. Most database vendors have an implementation of the XML data type in their new releases. With the inclusion of such a type, an XML dataset or document could be one of the fields or column values in a row of a database table. Prior to JDBC 4.0, perhaps the best way to manipulate such data within the JDBC framework is to use proprietary extensions from the driver vendors or access it as a CLOB type.

    JDBC 4.0 now defines SQLXML as the Java data type that maps the database SQL XML type. The API supports processing of an XML type as a string or as a StAX stream. Streaming API for XML, which for Java has been adopted via JSR 173, is based on the Iterator pattern, as opposed to the Simple API for XML Processing (SAX), which is based on the Observer pattern.

    Invoking the Connection object's createSQLXML() method can create a SQLXML object. This is an empty object, so the data can be attached to it by either using the setString() method or by associating an XML stream using the createXMLStreamWriter() method with the object. Similarly, XML data can be retrieved from a SQLXML object using getString() or associating an XML stream using createXMLStreamReader() with the object.

    The ResultSet, the PreparedStatement, and the CallableStatement interfaces have getSQLXML() methods for retrieving a SQLXML data type. PreparedStatement and CallableStatement also have setSQLXML() methods to add SQLXML objects as parameters.

    The SQLXML resources can be released by calling their free() methods, which might prove pertinent where the objects are valid in long-running transactions. DatabaseMetaData's getTypeInfo() method can be called on a datasource to check if the database supports the SQLXML data type, since this method returns all the data types it supports.

    Connections and Statements
    The Connection interface definitions have been enhanced to analyze connection state and usage to facilitate efficiency.

    Sometimes database connections are unusable though they may not necessarily be closed and garbage collected. In such situations, the database appears slow and unresponsive. In most of these circumstances, reinitializing the connections is perhaps the only way to resolve the problem. When using the JDBC API prior to version 4.0, there is no way to distinguish between a stale connection and a closed connection. The new API adds an isValid() method to the Connection interface to query if the connection is still valid.

    Also, database connections are often shared among clients, and sometimes some clients tend to use more resources than others, which can lead to starvation-like situations. The Connection interface defines a setClientInfo() method to define client-specific properties, which could be utilized to analyze and monitor resource utilization by the clients.

    RowId
    The RowId in many databases is a unique way to identify a row in a table. Queries using RowId in the search criteria are often the fastest way to retrieve data, especially true in the case of the Oracle and DB2 databases. Since java.sql.RowId is now a built-in type in Java, you could utilize the performance benefits associated with its usage. RowIds are most useful in identifying unique and specific rows when duplicate data exists and some rows are identical. However, it is important to understand that RowIds often are unique only for a table and not for the entire database; they may change and are not supported by all databases. RowIds are typically not portable across datasources and thus should be used with caution when working with multiple datasources.

    A RowId is valid for the lifetime defined by the datasource and as long as the row is not deleted. The DatabaseMetadata.getRowIdLifetime() method is called to determine the RowId's lifetime. The return type is an enumeration type as summarized in the table below.

    RowIdLifetime enum type Definition
    ROWID_UNSUPPORTED Datasource does not support RowId type
    ROWID_VALID_OTHER Implementation-dependent lifetime
    ROWID_VALID_TRANSACTION Lifetime is at least the containing transaction
    ROWID_VALID_SESSION Lifetime is at least the containing session
    ROWID_VALID_FOREVER Unlimited lifetime

    The ROWID_VALID_TRANSACTION, ROWID_VALID_SESSION, and ROWID_VALID_FOREVER definitions are true as long as the row is not deleted. It is important to understand that a new RowId is assigned if a row is deleted and reinserted, which sometimes could happen transparently at the datasource. As an example, in Oracle, if the "enable row movement" clause is set on a partitioned table and an update of the partition key causes the row to move from one partition to another, the RowId will change. Even without the "enable row movement" flag with the "alter table table_name" move, the RowId could change.

    Both the ResultSet and CallableStatement interfaces have been updated to include a method called getRowID(), which returns a javax.sql.RowId type.

    Listing 3 shows how RowId could be retrieved from a ResultSet and from a CallableStatement.

    Listing 3. Get RowId

    //The method signatures to retrieve RowId from a ResultSet is as follows:
    ?? RowId getRowId (int columnIndex)
    ?? RowId getRowId (String columnName)
    ...

    Statement stmt = con.createStatement ();

    ResultSet rs = stmt. ExecuteQuery (…);

    while (rs.next ()) {

    ...

    java.sql.RowId rid = rs.getRowId (1);

    ...

    }

    //The method signatures to retrieve RowId from a CallableStatement is as follows:
    ?? RowId getRowId (int parameterIndex)
    ?? RowId getRowId (String parameterName)

    Connection con;
    ...

    CallableStatement cstmt = con.prepareCall (…);
    ...

    cstmt.registerOutParameter (2, Types.ROWID);

    ...

    cstmt.executeUpdate ();

    ...

    java.sql.RowId rid = cstmt.getRowId (2);

    The RowId can be used to refer uniquely to a row and thus can be used to retrieve the rows or update the row data. When fetching or updating using RowId references, it is important to know the validity of the RowId's lifetime to assure consistent results. It is also advisable to simultaneously use another reference, such as the primary key, to avoid inconsistent results in circumstances where the RowId could change transparently.

    The RowId values can also be set or updated. In the case of an updateable ResultSet, the updateRowId() method could be used to update the RowId for a particular row in a table.

    Both the PreparedStatement and the CallableStatement interfaces support a setRowId() method, with different signatures, to set the RowId as a parameter value. This value could be used to refer to data rows or to update the RowId value for a particular row in a table.

    The facility to set or update the RowId provides the flexibility to control the unique row identifiers and could be used to make such identifiers unique across the tables used. Perhaps, portability of RowId across supporting datasources could also be achieved by explicitly setting consistent values across them. However, because system-generated RowIds are often efficient, and transparent tasks could alter RowIds, they are best used by an application as a read-only attribute.

    Leverage nonstandard vendor implemented resources
    The new JDBC API defines a java.sql.Wrapper interface. This interface provides the ability to access datasource-vendor-specific resources by retrieving the delegate instance using the corresponding wrapped proxy instance.

    This Wrapper interface has 17 sub-interfaces as per the current specification and includes Connection, ResultSet, Statement, CallableStatement, PreparedStatement, DataSource, DatabaseMetaData, and ResultSetMetaData, among others in the list. This is an excellent design as it facilitates datasource-vedor-specific resource implementation at almost all stages of the query-creation and result-set-retrieval lifecycles.

    The unwrap() method returns the object that implements the given interface to allow access to vendor-specific methods. The isWrapperFor() method returns a Boolean value. It returns true if it implements the interface, or if it directly or indirectly is a wrapper for the object.

    As an example, when using Oracle, Oracle JDBC drivers provide update batching extensions that are better performing and more efficient as compared to the standard JDBC batch-updating mechanisms. For earlier JDBC versions, this implies using the Oracle-specific definitions, such as OraclePreparedStatement, in the code. This compromises code portability. With the new API, many such efficient implementations can be wrapped and exposed within the standard JDBC definitions.

    Service provider mechanism for driver loading
    In a nonmanaged or standalone program scenario, prior to JDBC 4.0, you would have to load the JDBC driver class explicitly by invoking the Class.forName method, as shown in Listing 4:

    Listing 4. Class.forName

    Class.forName ("com.driverprovider.jdbc.jdbcDriverImpl");

    With JDBC 4.0, if the JDBC driver vendors package their drivers as services, defined under the server provider mechanism definitions as per the JAR specification, the DriverManager code would implicitly load the driver by searching for it in the classpath. The benefit of this mechanism is that the developer does not need to know about the specific driver class and can write a little less code when using JDBC. Also, since the driver class name is no longer in the code, a name change would not require recompilations. If multiple drivers are specified in the classpath, then DriverManger will try and establish a connection using the first driver it encounters in the classpath and iterate further if required.

    Conclusion
    In this article, I have discussed some of the new and improved features of JDBC 4.0. Many of these new features enhance a developer's productivity and facilitate development. Also, the specification does not eradicate the possible use of extra JDBC frameworks to provide templating facilities and advanced exception-handling capabilities. However, there is some criticism as well. Some believe that annotations effectively lead to hard-coding in code, which causes problems in code maintainability.

    posted @ 2006-05-09 11:17 Leo 閱讀(895) | 評論 (2)編輯 收藏

    2006年5月8日 #

    ANT安裝、配置

    內容摘要:
    ant是一個基于JAVA的自動化腳本引擎,腳本格式為XML。除了做JAVA編譯相關任務外,ANT還可以通過插件實現很多應用的調用。


    ANT的基本概念:
    ANT的安裝:解包,設置路徑
    ANT的使用:最好的學習只不過是一個簡單實用的例子起步……
    ANT的基本概念:Java的Makefile
    當一個代碼項目大了以后,每次重新編譯,打包,測試等都會變得非常復雜而且重復,因此c語言中有make腳本來幫助這些工作的批量完成。在Java 中應用是平臺無關性的,當然不會用平臺相關的make腳本來完成這些批處理任務了,ANT本身就是這樣一個流程腳本引擎,用于自動化調用程序完成項目的編譯,打包,測試等。除了基于JAVA是平臺無關的外,腳本的格式是基于XML的,比make腳本來說還要好維護一些。


    每個ant腳本(缺省叫build.xml)中設置了一系列任務(target):比如對于一個一般的項目可能需要有以下任務。

    任務1:usage 打印本腳本的幫助信息(缺省)
    任務2:clean <-- init 清空初始化環境
    任務3:javadoc <-- build <-- init 生成JAVADOC
    任務4:jar <-- build <-- init 生成JAR
    任務5:all <-- jar + javadoc <-- build <-- init 完成以上所有任務:jar javadoc
    而多個任務之間往往又包含了一定了依賴關系:比如把整個應用打包任務(jar)的這個依賴于編譯任務(build),而編譯任務又依賴于整個環境初始化任務(init)等。

    注:我看到很多項目的ant腳本中的命名基本上都是一致的,比如:編譯一般叫build或者compile;打包一般叫jar或war;生成文檔一般命名為javadoc或javadocs;執行全部任務all。在每個任務的中,ANT會根據配置調用一些外部應用并配以相應參數執行。雖然ANT可調用的外部應用種類非常豐富,但其實最常用的就2,3個:比如javac javadoc jar等。
    ANT的安裝
    解包后在系統可執行路徑中加入指向ant的bin的路徑就可以了,比如可以在GNU/Linux上把以下配置加入/etc/profile中:
    export ANT_HOME=/home/ant
    export JAVA_HOME=/usr/java/j2sdk1.4.1
    export PATH=$PATH:$JAVA_HOME/bin:$ANT_HOME/bin

    這樣執行ant 后,如果不指定配置文件ant會缺省找build.xml這個配置文件,并根據配置文件執行任務,缺省的任務設置可以指向最常用的任務,比如: build,或指向打印幫助信息:usage,告訴用戶有那些腳本選項可以使用。


    ANT的使用

    最好的學習過程就是看懂那些open source項目中的build.xml腳本,然后根據自己的需要簡化成一個更簡單的,ANT和APACHE上很多非常工程派的項目:簡單易用,而且適應性非常強,因為這些項目的建立往往來源于開發人員日常最直接的需求。
    以下是的一個WebLucene應用的例子:修改自JDOM的build.xml:

    <project default="usage" basedir=".">

    <!-- =================================================================== -->
    <!-- Initialization target -->
    <!-- =================================================================== -->
    <target name="init">
    <tstamp/>
    <property file="${basedir}/build.properties" />
    <property name="Name" value="ProjectFullName"/>
    <property name="name" value="project_name"/>
    <property name="version" value="0.2"/>
    <property name="year" value="2003"/>

    <echo message="----------- ${Name} ${version} [${year}] ------------"/>

    <property name="debug" value="off"/>
    <property name="optimize" value="on"/>
    <property name="deprecation" value="on"/>

    <property name="src.dir" value="./src/WEB-INF/src"/>
    <property name="lib.dir" value="./src/WEB-INF/lib"/>
    <property name="packages" value="com.chedong.*,org.apache.lucene.*"/>

    <property name="build.src" value="./src/WEB-INF/build"/>
    <property name="build.dest" value="./src/WEB-INF/classes"/>
    <property name="build.javadocs" value="./src/doc"/>

    <path id="classpath">
    <pathelement path="${jsdk_jar}"/>
    <fileset dir="${lib.dir}">
    <include name="**/*.jar"/>
    </fileset>
    </path>

    <filter token="year" value="${year}"/>
    <filter token="version" value="${version}"/>
    <filter token="date" value="${TODAY}"/>
    <filter token="log" value="true"/>
    <filter token="verbose" value="true"/>
    </target>

    <!-- =================================================================== -->
    <!-- Help on usage -->
    <!-- =================================================================== -->
    <target name="usage" depends="init">
    <echo message="${Name} Build file"/>
    <echo message="-------------------------------------------------------------"/>
    <echo message=""/>
    <echo message=" available targets are:"/>
    <echo message=""/>
    <echo message=" jar --> generates the ${name}.jar file"/>
    <echo message=" build --> compiles the source code"/>
    <echo message=" javadoc --> generates the API documentation"/>
    <echo message=" clean --> cleans up the directory"/>
    <echo message=""/>
    <echo message=" Please rename build.properties.default to build.properties"/>
    <echo message=" and edit build.properties to specify JSDK 2.3 classpath."/>
    <echo message=""/>
    <echo message=" See the comments inside the build.xml file for more details."/>
    <echo message="-------------------------------------------------------------"/>
    <echo message=""/>
    <echo message=""/>
    </target>

    <!-- =================================================================== -->
    <!-- Prepares the source code -->
    <!-- =================================================================== -->
    <target name="prepare-src" depends="init">
    <!-- create directories -->
    <mkdir dir="${build.src}"/>
    <mkdir dir="${build.dest}"/>

    <!-- copy src files -->
    <copy todir="${build.src}">
    <fileset dir="${src.dir}"/>
    </copy>
    </target>

    <!-- =================================================================== -->
    <!-- Compiles the source directory -->
    <!-- =================================================================== -->
    <target name="build" depends="prepare-src">
    <javac srcdir="${build.src}"
    destdir="${build.dest}"
    debug="${debug}"
    optimize="${optimize}">
    <classpath refid="classpath"/>
    </javac>
    </target>

    <!-- =================================================================== -->
    <!-- Creates the class package -->
    <!-- =================================================================== -->
    <target name="jar" depends="build">
    <jar jarfile="${lib.dir}/${name}.jar"
    basedir="${build.dest}"
    includes="**"/>
    </target>

    <!-- =================================================================== -->
    <!-- Creates the API documentation -->
    <!-- =================================================================== -->
    <target name="javadoc" depends="build">
    <mkdir dir="${build.javadocs}"/>
    <javadoc packagenames="${packages}"
    sourcepath="${build.src}"
    destdir="${build.javadocs}"
    author="true"
    version="true"
    use="true"
    splitindex="true"
    windowtitle="${Name} API"
    doctitle="${Name}">
    <classpath refid="classpath"/>
    </javadoc>
    </target>

    <!-- =================================================================== -->
    <!-- Clean targets -->
    <!-- =================================================================== -->
    <target name="clean" depends="init">
    <delete dir="${build.src}"/>
    <delete dir="${build.dest}/org"/>
    <delete dir="${build.dest}/com"/>
    <delete>
    <fileset dir="${build.dest}" includes="**/*.class"/>
    </delete>
    </target>
    </project>
    <!-- End of file -->

    缺省任務:usage 打印幫助文檔,告訴有那些任務選項:可用的有build, jar, javadoc和clean.

    初始化環境變量:init
    所有任務都基于一些基本環境變量的設置初始化完成,是后續其他任務的基礎,在環境初始化過程中,有2點比較可以方便設置:

    1 除了使用卻缺省的property設置了JAVA源路徑和輸出路徑外,引用了一個外部的build.properties文件中的設置,
    <property file="${basedir}/build.properties" />
    這樣大部分簡單配置用戶只要會看懂build.properties就可以了,畢竟XML比起key value的屬性文件還是要可讀性差一些。用build.properties也可以方便其他用戶從編譯的細節中解放出來。

    2 CLASSPATH設置:使用了其中的:
    <path id="classpath">
    <pathelement path="${jsdk_jar}"/>
    <fileset dir="${lib.dir}">
    <include name="**/*.jar"/>
    </fileset>
    </path>
    則相當于設置了:CLASSPATH=/path/to/resin/lib/jsdk23.jar; /path/to/project/lib/*.jar;

    文件復制:prepare-src
    創建臨時SRC存放目錄和輸出目錄。
    <!-- =================================================================== -->
    <!-- Prepares the source code -->
    <!-- =================================================================== -->
    <target name="prepare-src" depends="init">
    <!-- create directories -->
    <mkdir dir="${build.src}"/>
    <mkdir dir="${build.dest}"/>

    <!-- copy src files -->
    <copy todir="${build.src}">
    <fileset dir="${src.dir}"/>
    </copy>
    </target>

    編譯任務:build
    編譯時的CLASSPATH環境通過一下方式找到引用一個path對象
    <classpath refid="classpath"/>

    打包任務:jar
    對應用打包生成項目所寫名的.jar文件
    <!-- =================================================================== -->
    <!-- Creates the class package -->
    <!-- =================================================================== -->
    <target name="jar" depends="build">
    <jar jarfile="${lib.dir}/${name}.jar"
    basedir="${build.dest}"
    includes="**"/>
    </target>

    生成JAVADOC文檔任務: javadoc
    <!-- =================================================================== -->
    <!-- Creates the API documentation -->
    <!-- =================================================================== -->
    <target name="javadoc" depends="build">
    <mkdir dir="${build.javadocs}"/>
    <javadoc packagenames="${packages}"
    sourcepath="${build.src}"
    destdir="${build.javadocs}"
    author="true"
    version="true"
    use="true"
    splitindex="true"
    windowtitle="${Name} API"
    doctitle="${Name}">
    <classpath refid="classpath"/>
    </javadoc>
    </target>

    清空臨時編譯文件:clean
    <!-- =================================================================== -->
    <!-- Clean targets -->
    <!-- =================================================================== -->
    <target name="clean" depends="init">
    <delete dir="${build.src}"/>
    <delete dir="${build.dest}/org"/>
    <delete dir="${build.dest}/com"/>
    <delete>
    <fileset dir="${build.dest}" includes="**/*.class"/>
    </delete>
    </target>

    TODO:
    更多任務/擴展:(樣例)

    測試任務:JUnit測試
    代碼風格檢查任務:CheckStyle,Jalopy等
    郵件警報任務:可以把以上這些任務的輸出警告發送到制定的用戶列表中,這個任務可以設置每天自動運行。

    參考資料:

    Jakarta ANT:
    http://ant.apache.org



    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=707995

    posted @ 2006-05-08 10:54 Leo 閱讀(660) | 評論 (3)編輯 收藏

    最近寫程序已經很少直接用JDBC了,一直都是用ibaits, Hibernate等來招呼,因為現在的集成框架已經很穩定了。不過對JDBC的直接使用還是不可以忽略的,JDBC3.0提供的n多的新特征還是要熟悉了解的,以前學jdbc的時候就是上網找些demo和介紹來學,使用很單一,對JDBC3.0的好多新的特征都忽略了,比如下面一個例子:

    Statement stmt = con.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM user WHERE username='aa'");
    stmt.executeUpdate("UPDATE user SET lastdatetime=now() where username='aa'");

    這是一個用戶登錄時,經常用到的代碼,先是根據用戶名aa查找該用戶的詳細信息,然后再更新該用戶的最后登錄時間(lastdatetime)。這這個里面,我們用了兩個sql語句,這個是我一直用的方法,但是如果用JDBC2.0給我們提供的便利,我們只要寫一條sql就夠了,其他的都交給jdbc,看下面的代碼:

    Statement stmt2 = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
    ResultSet rs2 = stmt.executeQuery("SELECT * FROM user WHERE username='aa'");
    rs2.next();
    rs2.updateDate("lastdatetime", new Date(Calendar.getInstance().getTimeInMillis()));
    rs2.updateRow();

    這里面最主要的特征就是ResultSet.TYPE_FORWARD_ONLY和ResultSet.CONCUR_UPDATABLE,通過初始化Statement時傳不同的參數,可以對ResultSet進行不用的錯作限制。con.createStatement的時候,有三種可以掉用的函數:

    1、createStatement();
    2、createStatement(int resultSetType, int resultSetConcurrency)
    3、createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)

    其中resultSetType可選值是:
    ?? 1、ResultSet.TYPE_FORWARD_ONLY? 在ResultSet中只能先前移動游標,
    ?? 2、ResultSet.TYPE_SCROLL_INSENSITIVE 在ResultSet中可以隨心所欲的先前向后移動游標,
    ?? 3、ResultSet.TYPE_SCROLL_SENSITIVE 在ResultSet中可以隨心所欲的先前向后移動游標,同時ResultSet的值有所改變的時候,他可以得到改變后的最新的值
    其中resultSetConcurrency可選值是:
    ?? 1、ResultSet.CONCUR_READ_ONLY? 在ResultSet中的數據記錄是只讀的,可以修改
    ?? 2、ResultSet.CONCUR_UPDATABLE? 在ResultSet中的數據記錄可以任意修改,然后更新會數據庫
    其中resultSetHoldability可選值是:
    ?? 1、ResultSet.HOLD_CURSORS_OVER_COMMIT 表示修改提交時,不關閉ResultSet的游標
    ?? 2、ResultSet.CLOSE_CURSORS_AT_COMMIT? 表示修改提交時,關閉ResultSet的游標

    對于查詢操作第一種初始化方法createStatement(),相當于第二種方法的createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY),第三種方法的createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT)

    下面寫一段demo的代碼,我把一些特征函數都用出來,但是只是用來查考和說明名靈活性的。

    ?Statement stmt2 = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
    ?ResultSet rs2 = stmt.executeQuery("SELECT * FROM user");
    ?rs2.next();
    ?rs2.updateDate("lastdatetime", new Date(Calendar.getInstance().getTimeInMillis()));
    ?rs2.updateRow();
    ?rs2.afterLast();
    ?while(rs2.previous()){ /**....*/ }
    ?rs.beforeFirst();
    ?while(rs2.next()){? /**....*/ }
    ?rs.last();
    ?rs.first();
    ?rs.absolute(5);?//游標移動到第5條
    ?rs.absolute(-1);? //游標移動到最后一條
    ?rs.relative(-5);? //游標向上移動5條
    ?rs.relative(2);?? //游標向下移動2條
    ?rs.deleteRow();?//刪除當前行
    ?rs.last();? //游標移動到最后
    ?rs.updateString("summary", "This is ..."); //設置更新的字段值
    ?rs.cancelRowUpdates();? //取消剛才輸入的更新
    ?rs.getRow(); //得到當前行號
    ?rs.moveToInsertRow();? //游標移動到要新增的那條記錄上
    ?rs.updateInt("id", 1);
    ?rs.updateString(2, "my name");
    ?rs.insertRow(); //插入新記錄


    JDBC2.0提供的還有一個功能就是數據庫的批量操作

    ??con.setAutoCommit(false);
    ??Statement stmt3 = con.createStatement();
    ??stmt3.addBatch("insert .....");
    ??stmt3.addBatch("insert .....");
    ??int[] rows = stmt3.executeBatch();
    ??con.commit();

    但是有一點要注意,stmt3.executeBatch()他不會自動給你回滾數據操作,當你有5條update語句的時候,如果第三條發生錯誤,那么將無法自動回滾前兩條update語句的影響,所以一定要自己手工進行事務管理。

    在您的事務中使用 Savepoint
    JDBC3.0中最令人興奮的附加特點就是 Savepoint 了。有時候需要的是對事務多一點的控制,而不是在當前的事務中簡單地對每一個改變進行回滾。在JDBC3.0下,您就可以通過 Savepoint 獲得這種控制。Savepoint 接口允許您將事務分割為各個邏輯斷點,以控制有多少事務需要回滾。看下面的代碼:

    conn.setAutoCommit(false);
    conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
    Statement stmt = conn.createStatement();
    int rows = stmt.executeUpdate( "INSERT INTO authors (first_name, last_name) valueS(′Lewis′, ′Carroll′)");
    Savepoint svpt = conn.setSavepoint("NewAuthor");
    try{
    ?rows = stmt.executeUpdate( "UPDATE authors set type = ′fiction′ WHERE last_name = ′Carroll′");
    }catch(Exception e){
    ?conn.rollback(svpt);
    ?rows = stmt.executeUpdate( " update .......... other sql ");
    }
    conn.commit();

    上面代碼顯示,當UPDATE authors失敗的時候,系統事務回滾UPDATE authors的sql的影響,而INSERT INTO authors的sql仍然有效


    檢索自動產生的關鍵字
    為了解決對獲取自動產生的或自動增加的關鍵字的值的需求,JDBC 3.0現在將獲取這種值變得很輕松。要確定任何所產生的關鍵字的值,只要簡單地在語句的 execute() 方法中指定一個可選的標記,Statement.RETURN_GENERATED_KEYS和Statement.NO_GENERATED_KEYS。在執行這條語句后,所產生的關鍵字的值就會通過從 Statement 的實例方法 getGeneratedKeys() 來檢索 ResultSet 而獲得。ResultSet 包含了每個所產生的關鍵字的列。看下面代碼:

    Statement stmt = conn.createStatement();
    stmt.executeUpdate("INSERT INTO authors (first_name, last_name) valueS (′George′, ′Orwell′)", Statement.RETURN_GENERATED_KEYS);
    ResultSet rs = stmt.getGeneratedKeys();
    if ( rs.next() ) {
    ?int key = rs.getInt();
    }

    ?參考資料: http://java.sun.com/j2se/1.5.0/docs/api/java/sql/package-summary.html

    posted @ 2006-05-08 10:49 Leo 閱讀(1538) | 評論 (2)編輯 收藏

    2006年4月29日 #

    < script?language = " javascript " ?type = " text/javascript " >

    function?Hashtable()
    {
    ????
    this ._hash???????? = ? new ?Object();
    ????
    this .add???????? = ?function(key,value){
    ????????????????????????
    if (typeof(key) != " undefined " ){
    ????????????????????????????
    if ( this .contains(key) == false ){
    ????????????????????????????????
    this ._hash[key] = typeof(value) == " undefined " ? null :value;
    ????????????????????????????????
    return ? true ;
    ????????????????????????????}?
    else ?{
    ????????????????????????????????
    return ? false ;
    ????????????????????????????}
    ????????????????????????}?
    else ?{
    ????????????????????????????
    return ? false ;
    ????????????????????????}
    ????????????????????}
    ????
    this .remove???????? = ?function(key){delete? this ._hash[key];}
    ????
    this .count???????? = ?function(){var?i = 0 ; for (var?k?in? this ._hash){i ++ ;}? return ?i;}
    ????
    this .items???????? = ?function(key){ return ? this ._hash[key];}
    ????
    this .contains???? = ?function(key){? return ?typeof( this ._hash[key]) != " undefined " ;}
    ????
    this .clear???????? = ?function(){ for (var?k?in? this ._hash){delete? this ._hash[k];}}

    }

    var?a?
    = ? new ?Hashtable();

    a.add(
    " aa " );
    a.add(
    " bb " , 2342 );
    a.add(
    " bb " , 2342 );

    a.remove(
    " aa " );

    alert(a.count());

    alert(a.contains(
    " bb " ));

    alert(a.contains(
    " aa " ));

    alert(a.items(
    " bb " ));


    </ script >
    posted @ 2006-04-29 20:59 Leo 閱讀(467) | 評論 (2)編輯 收藏

         摘要: < HTML > < HEAD > < TITLE > print </ TITLE > < meta? http-equiv =...  閱讀全文
    posted @ 2006-04-29 20:57 Leo 閱讀(1332) | 評論 (1)編輯 收藏

         摘要: 事件源對象 event.srcElement.tagName event.srcElement.type 捕獲釋放 event.srcElement.setCapture();? event.srcElement.releaseCapture();? 事件按鍵 ...  閱讀全文
    posted @ 2006-04-29 20:50 Leo 閱讀(526) | 評論 (2)編輯 收藏

    2006年4月3日 #

         摘要: 為 我們的項目寫的一個輕量的分頁API。目的在于將分頁與數據查詢的邏輯完全剝離。我以前看過robbin發的通過detachedCriteria實現的 分頁那片貼子,里面把分頁和數據查詢結合在一起了。而我覺得分開更輕量,而且替換也比較容易。但是這個實現中有一個反模式,在邏輯中生成了代碼,無奈之 選,為了簡便。其中字符生成可以自己擴展i18n實現,應該非常容易。分頁實現的接口:package?c...  閱讀全文
    posted @ 2006-04-03 02:22 Leo 閱讀(505) | 評論 (2)編輯 收藏

    2006年2月23日 #


    作者:朱騏

    作者簡介


    朱騏,男,江蘇南京人,講師,主要研究方向:信息系統與集成技術。您可以通過qizhu003@msn.com和作者取得聯系。

    內容摘要


    JMS面向Web的應用與面向桌面的應用相比,有特殊的用戶環境要求:同一個消息必須能被若干未知的用戶消費,因此在消息接收方必須有"接收而不確認"的提交機制;本文以CWNF校務系統為實現案例,討論面向Web的JMS應用系統消息提交原理及采用的關鍵技術。

    消息傳遞是一種在軟件組件或應用之間進行分布式通信的松散耦合方法,與各種緊密耦合通信技術(如CORBA、Java RMI、COM/DCOM)相比,不同之處在于:①消息系統是一種對等實施,通信雙方即消息的發送者和接受者都是該系統中的客戶端,彼此不呈C/S關系; ②通信雙方的工作是異步的;③基于消息格式一致,通信雙方只需一個中介來存儲并管理消息就可以實現通信,而緊密耦合技術則需要知道遠程方法在本地的接口。 因自身特點,消息傳遞技術在企業中和企業間有較廣泛的應用需求。

    JMS(Java Message Service)是J2EE企業平臺的Java消息服務,目前主流J2EE產品的JMS都實現了存儲功能,JMS客戶端通過JMS API創建,彼此間通過目的地(Destination)對象進行通信;可是JMS消息系統多見于桌面應用,而Web應用鮮見,本文以筆者開發的CWNF 校務系統為案例,討論面向Web的JMS應用系統的實現原理及采用的關鍵技術。

    1  面向Web的JMS應用系統實現原理

    1.1  JMS應用系統消息傳遞原理


    JMS應用系統有4個部分:①JMS提供者(JMS Provider),是一個邏輯數據存儲體,并提供管理工具和控制特性;②JMS客戶端,是用Java語言編寫的發送或接收消息的組件或應用;③消息,是 JMS客戶端間被傳遞的承載信息的對象;④被管理對象,是系統管理員為客戶端預置的JMS對象,包括目的地對象和連接工廠對象,其中目的地對象是客戶端間 的消息中介。這4個部分通過JNDI相關聯:管理員通過管理工具把目的地對象和連接工廠對象綁定到一個JNDI API命名空間中,JMS客戶端就可以在命名空間中查找這些對象,并通過JMS提供者建立與這些對象的邏輯連接,從而彼此之間實現通信(圖1)。JMS支 持2種消息傳遞域:點到點、發布/訂閱,與之相對應的消息目的地對象也有2種:隊列、主題。

    1.2  Web應用的消息提交機制


    通常,無論是消息發送方還是接收方,桌面應用都不容許消息丟失或重復,JMS消息提交機制是基于這個要求的,它們從不同方面保證該要求的實現:①在 接收方控制消息的確認。通過確認保證一個接收者對一個消息只消費一次,在非事務性的會話中,消息確認方式取決于create×××Session方法第二 個參數的值;在事務性會話中,無論由Bean管理事務還是由Bean容器管理事務,消息確認都由Bean容器自動完成。②在發送方指定消息的提交模式和生 存期。提交模式有兩種:PERSISTENT(穩定存儲)和NON_PERSISTENT(非穩定存儲),穩定存儲保證在故障情況下消息不會丟失;生存期 決定一個消息在存儲中介中的存在壽命,JMS提供者會自動摧毀到期的消息。③創建持久定閱的接收方。在發布/訂閱系統中,持久訂閱者可以接收到在訂閱者關 閉階段消息發送方發布的消息。

    但是Web應用系統在消息接收方有Web特有的用戶環境要求:①若干個用戶共用一個JMS客戶端組件,因此消息就應向一個消息接收者提交而不需確 認,具有容器自動確認功能的Bean是無法實現這一要求的;在一個組件內如果把會話設置成事務性的,而這個組件的容器又不具有事務管理能力,則這個組件就 能做到"接收而不確認",在Web應用系統中只有Servlet組件符合這一要求。②JMS客戶端的消息接收者經常關閉,為了接收在關閉期間發送來的消 息,消息接收者必定是基于主題的持久定閱者,所以面向Web的JMS應用系統必定采用發布/訂閱消息傳遞域。

    2  CWNF校務系統模型


    CWNF是一個面向Web的JMS校務系統,用于校園發布通知及征求意見等校務工作,通知分為2類:普通通知和征求意見性通知。

    該系統用戶分成3類,用戶不同,處理模型也不同,基本情況如下:①發布用戶,擁有通知發布權,向主題發布通知;②署名用戶,查閱通知,也可發表對征求意見性通知的反饋意見;③匿名用戶,只查閱通知。

    2.1  數據與數據流模型


    系統中的數據因此有2類:通知、反饋。接收方接收的數據將形成一個XML文檔對象,以便發往Web瀏覽器顯示;基于這樣的要求,考察下面2個問題:①系統中各方之間的數據關系,②各方數據的形式。

    主要的數據關系有3個:①通知發送方與通知接收方的數據關系,②反饋發送方與反饋接收方的數據關系,③通知接收方與反饋接收方的數據關系。(如圖 2)在發送方,數據(通知或反饋)是一件一件的發送,在接收方,數據(通知或反饋)則是批接收,是對應發送方數據的集合,因此在發送方沒有必要把數據直接 加工成XML文檔對象形式,只要生成能構成XML文檔對象的元素對象即可;而通知接收方與反饋接收方的數據關系則是:每一條征求意見性通知都有相關的一個 反饋集合。

    系統的數據流模型如下:
    ①通知發送方:表單數據→XML元素(通知)→主題(存儲)
    ②通知接收方:主題(存儲)→XML元素(通知)→XML文檔(通知)→XSL顯示(含表單)
    ③通知接收方到反饋接收方: XSL顯示(含表單)→主題(存儲)
    ④反饋接收方:主題(存儲)→XML元素(反饋)→XML文檔(反饋)→XSL顯示(含表單)
    ⑤反饋發送方:表單數據→XML元素(反饋)→主題(存儲)

    2.2  組件模型


    系統組件模型如圖3:主題CWNFTopic是消息傳遞中介,NoticerServlet組件向發布用戶發送表單,并從表單接收數據,然后生成 XML元素對象,該元素對象和其它一些數據被作為參數調用PublisherBean組件方法,向主題發送以該元素對象為消息體的消息; ReaderServlet組件處理署名用戶和匿名用戶查閱通知的業務,它從表單獲得用戶將查閱什么方面通知的有關信息后,便使用receive方法限時 阻塞地從主題接收消息并對消息進行篩選,把篩選出的若干消息的消息體取出,然后加工成XML文檔對象(根元素是通知集),最后輸出。 FeedbackerPubServlet用于反饋發送方的業務處理,功能與NoticerServlet相似; FeedbackerSubServlet用于反饋接收方的業務處理,功能與ReaderServlet相似;PublisherBean組件被 NoticerServlet組件和FeedbackerPubServlet組件調用,用于發送消息,容器管理發送事務,具有很高的可靠性。

    3  關鍵的實現技術

    3.1  JDOM建立、輸出XML文檔


    JDOM是一個開放源代碼的純Java樹式API,用于分析、建立、處理和序列化XML文檔。在數據流模型中,XML元素和XML文檔都由JDOM API建立,在發送方,通過用戶提交的表單取得名/值對若干,這些數據經過JDOM方法處理生成XML元素對象,元素對象被作為消息的消息體發往主題存 儲;在接收方,持久訂閱者接收到若干XML元素對象后,繼續通過JDOM方法建立XML文檔對象。且XML文檔向Web瀏覽器輸出也依賴于JDOM的 XMLOutputte對象方法:

    XMLOutputter serializer=new XMLOutputter();

    PrintWriter out
    =response.getWriter();    // out 是ServletResponse的輸出流對象
    serializer.output(xmldoc,out);            //通過out把XML文檔輸出到頁面


    3.2  XSL定義XML文檔顯示樣式


    XSL是可擴展的樣式單語言,通知集的XML文檔和反饋集的XML文檔都有相關的XSL文檔決定其頁面顯示,如通知集XML文檔的XSL樣式定義如下:

    <?xml version="1.0" encoding="GBK"?>
    <xsl:stylesheet>
    <xsl:template match="/">

    <HTML>
    <BODY>

    <DIV><xsl:apply-templates select="通知集"/></DIV>    
    </BODY>
    </HTML>

    </xsl:template>
    <xsl:template match="通知集">
    <xsl:for-each select="通知">

    </xsl:for-each>
    </xsl:template>
    </xsl:stylesheet> 

    3.3  Servlet間數據的傳遞

    3.3.1  注冊/登錄


    用戶的一些處理工作需要注冊/登錄后才能進行,因此注冊/登錄的獲準信息必須能在有關Servlet組件之間傳遞。ServletContext 對象可設置和讀取屬性,使不同Servlet之間相互通信,在系統中被用于有關組件對用戶身份的驗證。

    3.3.2  通知與反饋的數據關聯


    每一條征求意見性通知都有一個相關聯的反饋集合,關聯可通過設置消息屬性實現。JMS消息(包括通知類消息)都有系統級JMSMessageID屬 性,其值是唯一的,可用于表征每一條征求意見性通知,因此對任何反饋消息也可以設置一個應用級屬性(CWNF中是FeedbackSN),讓它取與之相關 聯的征求意見性通知的JMSMessageID屬性值。這樣就建立了兩者間的數據關聯。

    因此數據流模型"③通知接收方到反饋接收方: XSL顯示(含表單)→主題(存儲)"的實現流程如下:用戶在頁面上選擇一條征求意見性通知后,該通知的JMSMessageID屬性值將被傳遞給 FeedbackerSubServlet組件,該組件將使用這個屬性值去匹配從主題取出的反饋消息的FeedbackSN屬性,從而篩選出相關聯的反饋 消息。

    那么一條征求意見性通知的JMSMessageID屬性值又如何傳遞給FeedbackerSubServlet組件呢?通過 ServletContext對象只能傳遞可預知信息,CWNF的做法是:由XSL為每一條征求意見性通知設置一個獨立的表單,并把該通知的 JMSMessageID屬性值寫在表單的TEXTAREA元素框內,這樣用戶在表單上選擇一條征求意見性通知后,該通知的JMSMessageID屬性 值就隨表單一起提交給FeedbackerSubServlet組件。XSL有關代碼如下:

    <xsl:if test="string(意見反饋)='on'">
    <FORM method="post" action="http://localhost:6888/Feedbacker/servlet
    /FeedbackerSubServlet"
    >
    <BUTTON type="submit">意見反饋</BUTTON>
    <TEXTAREA name="序列號" rows="1" cols="40">
    <xsl:value-of select="序列號"/>
    </TEXTAREA>
    </FORM>
    </xsl:if>

    4  結束語


    JMS應用系統與數據庫系統有相似性,從數據方面看,JMS消息體的數據類型支持文本和對象,所以JMS更靈活,與XML集成應用的空間更大;但從 管理上看,JMS Provider向管理員提供的管理功能遠遠低于DBMS提供的管理功能,因此在面向Web的應用中,JMS宜作為中小流量、管理員參與度較低的信息系統 解決方案。

    轉載自:http://gceclub.sun.com.cn/yuanchuang/2004_Q4/jms.html

    posted @ 2006-02-23 19:07 Leo 閱讀(619) | 評論 (0)編輯 收藏

    作者:劉學超

    1  Java技術與Java虛擬機

    說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成: Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關系如下圖所示:

    圖1  Java四個方面的關系

    運行期環境代表著Java平臺,開發人員編寫Java代碼(.java文件),然后將之編譯成字節碼(.class 文件)。最后字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。從上圖也可以看出 Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫并編譯的程序可以運行在這個平臺上。 這個平臺的結構如下圖所示:

    在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴于平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

    那么到底什么是Java虛擬機(JVM)呢?通常我們談論JVM時,我們的意思可能是:

    1. 對JVM規范的的比較抽象的說明;
    2. 對JVM的具體實現;
    3. 在程序運行期間所生成的一個JVM實例。

    對JVM規范的的抽象說明是一些概念的集合,它們已經在書《The Java Virtual Machine Specification》(《Java虛擬機規范》)中被詳細地描述了;對JVM的具體實現要么是軟件,要么是軟件和硬件的組合,它已經被許多生產廠 商所實現,并存在于多種平臺之上;運行Java程序的任務由JVM的運行期實例單個承擔。在本文中我們所討論的Java虛擬機(JVM)主要針對第三種情 況而言。它可以被看成一個想象中的機器,在實際的計算機上通過軟件模擬來實現,有自己想象中的硬件,如處理器、堆棧、寄存器等,還有自己相應的指令系統。

    JVM在它的生存周期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了。下面我們從JVM的體系結構和它的運行過程這兩個方面來對它進行比較深入的研究。

    2  Java虛擬機的體系結構

    剛才已經提到,JVM可以由不同的廠商來實現。由于廠商的不同必然導致JVM在實現上的一些不同,然而JVM還是可以實現跨平臺的特性,這就要歸功于設計JVM時的體系結構了。

    我們知道,一個JVM實例的行為不光是它自己的事,還涉及到它的子系統、存儲區域、數據類型和指令這些部分,它們描 述了JVM的一個抽象的內部體系結構,其目的不光規定實現JVM時它內部的體系結構,更重要的是提供了一種方式,用于嚴格定義實現時的外部行為。每個 JVM都有兩種機制,一個是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統;另外的一個負責執行包含在已裝載的類或接口中的指令,叫做運行引擎。 每個JVM又包括方法區、堆、Java棧、程序計數器和本地方法棧這五個部分,這幾個部分和類裝載機制與運行引擎機制一起組成的體系結構圖為:

    圖3  JVM的體系結構

    JVM的每個實例都有一個它自己的方法域和一個堆,運行于JVM內的所有的線程都共享這些區域;當虛擬機裝載類文件 的時候,它解析其中的二進制數據所包含的類信息,并把它們放到方法域中;當程序運行的時候,JVM把程序初始化的所有對象置于堆上;而每個線程創建的時 候,都會擁有自己的程序計數器和Java棧,其中程序計數器中的值指向下一條即將被執行的指令,線程的Java棧則存儲為該線程調用Java方法的狀態; 本地方法調用的狀態被存儲在本地方法棧,該方法棧依賴于具體的實現。

    下面分別對這幾個部分進行說明。

    執行引擎處于JVM的核心位置,在Java虛擬機規范中,它的行為是由指令集所決定的。盡管對于每條指令,規范很詳 細地說明了當JVM執行字節碼遇到指令時,它的實現應該做什么,但對于怎么做卻言之甚少。Java虛擬機支持大約248個字節碼。每個字節碼執行一種基本 的CPU運算,例如,把一個整數加到寄存器,子程序轉移等。Java指令集相當于Java程序的匯編語言。

    Java指令集中的指令包含一個單字節的操作符,用于指定要執行的操作,還有0個或多個操作數,提供操作所需的參數或數據。許多指令沒有操作數,僅由一個單字節的操作符構成。

    虛擬機的內層循環的執行過程如下: 
    do{
    取一個操作符字節;
    根據操作符的值執行一個動作;
    }while(程序未結束)

    由于指令系統的簡單性,使得虛擬機執行的過程十分簡單,從而有利于提高執行的效率。指令中操作數的數量和大小是由操作符決定的。如果操作數比一個字節大,那么它存儲的順序是高位字節優先。例如,一個16位的參數存放時占用兩個字節,其值為:

    第一個字節*256+第二個字節字節碼。

    指令流一般只是字節對齊的。指令tableswitch和lookup是例外,在這兩條指令內部要求強制的4字節邊界對齊。

    對于本地方法接口,實現JVM并不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現Java本地接 口(JNI)是出于可移植性的考慮,當然我們也可以設計出其它的本地接口來代替Sun公司的JNI。但是這些設計與實現是比較復雜的事情,需要確保垃圾回 收器不會將那些正在被本地方法調用的對象釋放掉。

    Java的堆是一個運行時數據區,類的實例(對象)從中分配空間,它的管理是由垃圾回收來負責的:不給程序員顯式釋放對象的能力。Java不規定具體使用的垃圾回收算法,可以根據系統的需求使用各種各樣的算法。

    Java方法區與傳統語言中的編譯后代碼或是Unix進程中的正文段類似。它保存方法代碼(編譯后的 java代碼)和符號表。在當前的Java實現中,方法代碼不包括在垃圾回收堆中,但計劃在將來的版本中實現。每個類文件包含了一個Java類或一個 Java界面的編譯后的代碼。可以說類文件是Java語言的執行代碼文件。為了保證類文件的平臺無關性,Java虛擬機規范中對類文件的格式也作了詳細的 說明。其具體細節請參考Sun公司的Java虛擬機規范。

    Java虛擬機的寄存器用于保存機器的運行狀態,與微處理器中的某些專用寄存器類似。Java虛擬機的寄存器有四種:

    1. pc: Java程序計數器;
    2. optop: 指向操作數棧頂端的指針;
    3. frame: 指向當前執行方法的執行環境的指針;。
    4. vars: 指向當前執行方法的局部變量區第一個變量的指針。

    在上述體系結構圖中,我們所說的是第一種,即程序計數器,每個線程一旦被創建就擁有了自己的程序計數器。當線程執行Java方法的時候,它包含該線程正在被執行的指令的地址。但是若線程執行的是一個本地的方法,那么程序計數器的值就不會被定義。

    Java虛擬機的棧有三個區域:局部變量區、運行環境區、操作數區。

    局部變量區

    每個Java方法使用一個固定大小的局部變量集。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位 的。長整數和雙精度浮點數占據了兩個局部變量的空間,卻按照第一個局部變量的索引來尋址。(例如,一個具有索引n的局部變量,如果是一個雙精度浮點數,那 么它實際占據了索引n和n+1所代表的存儲空間)虛擬機規范并不要求在局部變量中的64位的值是64位對齊的。虛擬機提供了把局部變量中的值裝載到操作數 棧的指令,也提供了把操作數棧中的值寫入局部變量的指令。

    運行環境區

    在運行環境中包含的信息用于動態鏈接,正常的方法返回以及異常捕捉。

    動態鏈接

    運行環境包括對指向當前類和當前方法的解釋器符號表的指針,用于支持方法代碼的動態鏈接。方法的class 文件代碼在引用要調用的方法和要訪問的變量時使用符號。動態鏈接把符號形式的方法調用翻譯成實際方法調用,裝載必要的類以解釋還沒有定義的符號,并把變量 訪問翻譯成與這些變量運行時的存儲結構相應的偏移地址。動態鏈接方法和變量使得方法中使用的其它類的變化不會影響到本程序的代碼。

    正常的方法返回

    如果當前方法正常地結束了,在執行了一條具有正確類型的返回指令時,調用的方法會得到一個返回值。執行環境在正常返回的情況下用于恢復調用者的寄存器,并把調用者的程序計數器增加一個恰當的數值,以跳過已執行過的方法調用指令,然后在調用者的執行環境中繼續執行下去。

    異常捕捉

    異常情況在Java中被稱作Error(錯誤)或Exception(異常),是Throwable類的子類,在程序中的原因是:①動態鏈接錯,如無法找到所需的class文件。②運行時錯,如對一個空指針的引用。程序使用了throw語句。

    當異常發生時,Java虛擬機采取如下措施:

    • 檢查與當前方法相聯系的catch子句表。每個catch子句包含其有效指令范圍,能夠處理的異常類型,以及處理異常的代碼塊地址。
    • 與異常相匹配的catch子句應該符合下面的條件:造成異常的指令在其指令范圍之內,發生的異常類型是其能處理的異常類型的子類型。如 果找到了匹配的catch子句,那么系統轉移到指定的異常處理塊處執行;如果沒有找到異常處理塊,重復尋找匹配的catch子句的過程,直到當前方法的所 有嵌套的catch子句都被檢查過。
    • 由于虛擬機從第一個匹配的catch子句處繼續執行,所以catch子句表中的順序是很重要的。因為Java代碼是結構化的,因此總 可以把某個方法的所有的異常處理器都按序排列到一個表中,對任意可能的程序計數器的值,都可以用線性的順序找到合適的異常處理塊,以處理在該程序計數器值 下發生的異常情況。
    • 如果找不到匹配的catch子句,那么當前方法得到一個"未截獲異常"的結果并返回到當前方法的調用者,好像異常剛剛在其調用者中發 生一樣。如果在調用者中仍然沒有找到相應的異常處理塊,那么這種錯誤將被傳播下去。如果錯誤被傳播到最頂層,那么系統將調用一個缺省的異常處理塊。

    操作數棧區

    機器指令只從操作數棧中取操作數,對它們進行操作,并把結果返回到棧中。選擇棧結構的原因是:在只有少量寄存器或非 通用寄存器的機器(如Intel486)上,也能夠高效地模擬虛擬機的行為。操作數棧是32位的。它用于給方法傳遞參數,并從方法接收結果,也用于支持操 作的參數,并保存操作的結果。例如,iadd指令將兩個整數相加。相加的兩個整數應該是操作數棧頂的兩個字。這兩個字是由先前的指令壓進堆棧的。這兩個整 數將從堆棧彈出、相加,并把結果壓回到操作數棧中。

    每個原始數據類型都有專門的指令對它們進行必須的操作。每個操作數在棧中需要一個存儲位置,除了long 和double型,它們需要兩個位置。操作數只能被適用于其類型的操作符所操作。例如,壓入兩個int類型的數,如果把它們當作是一個long類型的數則 是非法的。在Sun的虛擬機實現中,這個限制由字節碼驗證器強制實行。但是,有少數操作(操作符dupe和swap),用于對運行時數據區進行操作時是不 考慮類型的。

    本地方法棧,當一個線程調用本地方法時,它就不再受到虛擬機關于結構和安全限制方面的約束,它既可以訪問 虛擬機的運行期數據區,也可以使用本地處理器以及任何類型的棧。例如,本地棧是一個C語言的棧,那么當C程序調用C函數時,函數的參數以某種順序被壓入 棧,結果則返回給調用函數。在實現Java虛擬機時,本地方法接口使用的是C語言的模型棧,那么它的本地方法棧的調度與使用則完全與C語言的棧相同。

    3  Java虛擬機的運行過程

    上面對虛擬機的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。

    虛擬機通過調用某個指定類的方法main啟動,傳遞給main一個字符串數組參數,使指定的類被裝載,同時鏈接該類所使用的其它的類型,并且初始化它們。例如對于程序:

    class HelloApp 
    {
        
    public static void main(String[] args) 
        {
            System.out.println(
    "Hello World!"); 
            
    for (int i = 0; i < args.length; i++ )
            {
                System.out.println(args[i]);
            }
        }
    }

    編譯后在命令行模式下鍵入: java HelloApp run virtual machine

    將通過調用HelloApp的方法main來啟動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數組。現在我們略述虛擬機在執行HelloApp時可能采取的步驟。

    開始試圖執行類HelloApp的main方法,發現該類并沒有被裝載,也就是說虛擬機當前不包含該類的二 進制代表,于是虛擬機使用ClassLoader試圖尋找這樣的二進制代表。如果這個進程失敗,則拋出一個異常。類被裝載后同時在main方法被調用之 前,必須對類HelloApp與其它類型進行鏈接然后初始化。鏈接包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則創建類或接 口的靜態域以及把這些域初始化為標準的默認值,解析負責檢查主類對其它類或接口的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態初始化函數 和靜態域的初始化構造方法的執行。一個類在初始化之前它的父類必須被初始化。整個過程如下:

    圖4:虛擬機的運行過程

    4  結束語

    本文通過對JVM的體系結構的深入研究以及一個Java程序執行時虛擬機的運行過程的詳細分析,意在剖析清楚Java虛擬機的機理。

    轉載自:http://gceclub.sun.com.cn/staticcontent/html/2004-04-09/jvm.html

    posted @ 2006-02-23 19:00 Leo 閱讀(727) | 評論 (0)編輯 收藏

    作者:盛戈歆

    作者簡介

    盛戈歆,軟件工程師,你可以通過shenggexin@topwaver.com與他聯系。

    正文

    Java中類的查找與裝載出現的問題總是會時不時出現在Java程序員面前,這并不是什么丟臉的事情,相信沒有一個 Java程序員沒遇到過ClassNotException,因此不要為被人瞅見自己也犯這樣的錯誤而覺得不自然,但是在如果出現了 ClassNotFoundException后異常后一臉的茫然,那我想你該了解一下java的類裝載的體制了,同時為了進行下面的關于類裝載器之間的 隔離性的討論,我們先簡單介紹一下類裝載的體系結構。

    1. Java類裝載體系結構

    裝載類的過程非常簡單:查找類所在位置,并將找到的Java類的字節碼裝入內存,生成對應的Class對象。 Java的類裝載器專門用來實現這樣的過程,JVM并不止有一個類裝載器,事實上,如果你愿意的話,你可以讓JVM擁有無數個類裝載器,當然這除了測試 JVM外,我想不出還有其他的用途。你應該已經發現到了這樣一個問題,類裝載器自身也是一個類,它也需要被裝載到內存中來,那么這些類裝載器由誰來裝載 呢,總得有個根吧?沒錯,確實存在這樣的根,它就是神龍見首不見尾的Bootstrap ClassLoader. 為什么說它神龍見首不見尾呢,因為你根本無法在Java代碼中抓住哪怕是它的一點點的尾巴,盡管你能時時刻刻體會到它的存在,因為java的運行環境所需 要的所有類庫,都由它來裝載,而它本身是C++寫的程序,可以獨立運行,可以說是JVM的運行起點,偉大吧。在Bootstrap完成它的任務后,會生成 一個AppClassLoader(實際上之前系統還會使用擴展類裝載器ExtClassLoader,它用于裝載Java運行環境擴展包中的類),這個 類裝載器才是我們經常使用的,可以調用ClassLoader.getSystemClassLoader() 來獲得,我們假定程序中沒有使用類裝載器相關操作設定或者自定義新的類裝載器,那么我們編寫的所有java類通通會由它來裝載,值得尊敬吧。 AppClassLoader查找類的區域就是耳熟能詳的Classpath,也是初學者必須跨過的門檻,有沒有靈光一閃的感覺,我們按照它的類查找范圍 給它取名為類路徑類裝載器。還是先前假定的情況,當Java中出現新的類,AppClassLoader首先在類傳遞給它的父類類裝載器,也就是 Extion ClassLoader,詢問它是否能夠裝載該類,如果能,那AppClassLoader就不干這活了,同樣Extion ClassLoader在裝載時,也會先問問它的父類裝載器。我們可以看出類裝載器實際上是一個樹狀的結構圖,每個類裝載器有自己的父親,類裝載器在裝載 類時,總是先讓自己的父類裝載器裝載(多么尊敬長輩),如果父類裝載器無法裝載該類時,自己就會動手裝載,如果它也裝載不了,那么對不起,它會大喊一聲: Exception,class not found。有必要提一句,當由直接使用類路徑裝載器裝載類失敗拋出的是NoClassDefFoundException異常。如果使用自定義的類裝載 器loadClass方法或者ClassLoader的findSystemClass方法裝載類,如果你不去刻意改變,那么拋出的是 ClassNotFoundException。

    我們簡短總結一下上面的討論:

    1.JVM類裝載器的體系結構可以看作是樹狀結構。

    2.父類裝載器優先裝載。在父類裝載器裝載失敗的情況下再裝載,如果都裝載失敗則拋出ClassNotFoundException或者NoClassDefFoundError異常。

    那么我們的類在什么情況下被裝載的呢?

    2. 類如何被裝載

    在java2中,JVM是如何裝載類的呢,可以分為兩種類型,一種是隱式的類裝載,一種式顯式的類裝載。

    2.1 隱式的類裝載

    隱式的類裝載是編碼中最常用得方式:

    A b = new A();

    如果程序運行到這段代碼時還沒有A類,那么JVM會請求裝載當前類的類裝器來裝載類。 問題來了,我把代碼弄得復雜一點點,但依舊沒有任何難度,請思考JVM得裝載次序:

     1 package test;
     2 Public class A{
     3     public void static main(String args[]){
     4         B b = new B();
     5     }
     6 }
     7 
     8 class B{C c;}
     9 
    10 class C{}

    揭曉答案,類裝載的次序為A->B,而類C根本不會被JVM理會,先不要驚訝,仔細想想,這不正是我們最需要 得到的結果。我們仔細了解一下JVM裝載順序。當使用Java A命令運行A類時,JVM會首先要求類路徑類裝載器(AppClassLoader)裝載A類,但是這時只裝載A,不會裝載A中出現的其他類(B類),接 著它會調用A中的main函數,直到運行語句b = new B()時,JVM發現必須裝載B類程序才能繼續運行,于是類路徑類裝載器會去裝載B類,雖然我們可以看到B中有有C類的聲明,但是并不是實際的執行語句, 所以并不去裝載C類,也就是說JVM按照運行時的有效執行語句,來決定是否需要裝載新類,從而裝載盡可能少的類,這一點和編譯類是不相同的。

    2.2 顯式的類裝載

    使用顯示的類裝載方法很多,我們都裝載類test.A為例。

    使用Class類的forName方法。它可以指定裝載器,也可以使用裝載當前類的裝載器。例如:

    Class.forName("test.A");

    它的效果和
    Class.forName("test.A",true,this.getClass().getClassLoader());

    是一樣的。

    使用類路徑類裝載裝載.

    ClassLoader.getSystemClassLoader().loadClass("test.A");

    使用當前進程上下文的使用的類裝載器進行裝載,這種裝載類的方法常常被有著復雜類裝載體系結構的系統所使用。

    Thread.currentThread().getContextClassLoader().loadClass("test.A")

    使用自定義的類裝載器裝載類

    1 public class MyClassLoader extends URLClassLoader{
    2 public MyClassLoader() {
    3         super(new URL[0]);
    4     }
    5 }
    6 MyClassLoader myClassLoader = new MyClassLoader();
    7 myClassLoader.loadClass("test.A");

    MyClassLoader繼承了URLClassLoader類,這是JDK核心包中的類裝載器,在沒有指定父類裝載器的情況下,類路徑類裝載器就是它的父類裝載器,MyClassLoader并沒有增加類的查找范圍,因此它和類路徑裝載器有相同的效果。

    我們已經知道Java的類裝載器體系結構為樹狀,多個類裝載器可以指定同一個類裝載器作為自己的父類,每個子類裝載 器就是樹狀結構的一個分支,當然它們又可以個有子類裝載器類裝載器,類裝載器也可以沒有父類裝載器,這時Bootstrap類裝載器將作為它的隱含父類, 實際上Bootstrap類裝載器是所有類裝載器的祖先,也是樹狀結構的根。這種樹狀體系結構,以及父類裝載器優先的機制,為我們編寫自定義的類裝載器提 供了便利,同時可以讓程序按照我們希望的方式進行類的裝載。例如某個程序的類裝載器體系結構圖如下:

    圖2:某個程序的類裝載器的結構

    解釋一下上面的圖,ClassLoaderA為自定義的類裝載器,它的父類裝載器為類路徑裝載器,它有兩個子類裝載 器ClassLoaderAA和ClassLaderAB,ClassLoaderB為程序使用的另外一個類裝載器,它沒有父類裝載器,但有一個子類裝載 器ClassLoaderBB。你可能會說,見鬼,我的程序怎么會使用這么復雜的類裝載器結構。為了進行下面的討論,暫且委屈一下。

    3. 奇怪的隔離性

    我們不難發現,圖2中的類裝載器AA和AB, AB和BB,AA和B等等位于不同分支下,他們之間沒有父子關系,我不知道如何定義這種關系,姑且稱他們位于不同分支下。兩個位于不同分支的類裝載器具有 隔離性,這種隔離性使得在分別使用它們裝載同一個類,也會在內存中出現兩個Class類的實例。因為被具有隔離性的類裝載器裝載的類不會共享內存空間,使 得使用一個類裝載器不可能完成的任務變得可以輕而易舉,例如類的靜態變量可能同時擁有多個值(雖然好像作用不大),因為就算是被裝載類的同一靜態變量,它 們也將被保存不同的內存空間,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很簡單,編寫自定義的類裝載器。類裝載器的這種隔離性在許多 大型的軟件應用和服務程序得到了很好的應用。下面是同一個類靜態變量為不同值的例子。

     1 package test;
     2 public class A {
     3   public static void main( String[] args ) {
     4     try {
     5       //定義兩個類裝載器
     6       MyClassLoader aa= new MyClassLoader();
     7       MyClassLoader bb = new MyClassLoader();
     8 
     9       //用類裝載器aa裝載testb.B類
    10       Class clazz=aa.loadClass("testb. B");
    11       Constructor constructor= 
    12         clazz.getConstructor(new Class[]{Integer.class});
    13       Object object = 
    14         constructor.newInstance(new Object[]{new Integer(1)});
    15       Method method = 
    16         clazz.getDeclaredMethod("printB",new Class[0]);
    17 
    18       //用類裝載器bb裝載testb.B類
    19       Class clazz2=bb.loadClass("testb. B");
    20       Constructor constructor2 = 
    21         clazz2.getConstructor(new Class[]{Integer.class});
    22       Object object2 = 
    23         constructor2.newInstance(new Object[]{new Integer(2)});
    24       Method method2 = 
    25         clazz2.getDeclaredMethod("printB",new Class[0]);
    26 
    27       //顯示test.B中的靜態變量的值 
    28       method.invoke( object,new Object[0]);
    29       method2.invoke( object2,new Object[0]);
    30     } catch ( Exception e ) {
    31       e.printStackTrace();
    32     }
    33   }
    34 }
    35 
    36 
    37 //Class B 必須位于MyClassLoader的查找范圍內,
    38 //而不應該在MyClassLoader的父類裝載器的查找范圍內。
    39 package testb;
    40 public class B {
    41     static int b ;
    42 
    43     public B(Integer testb) {
    44         b = testb.intValue();
    45     }
    46 
    47     public void printB() {
    48         System.out.print("my static field b is ", b);
    49     }
    50 }
    51 
    52 
    53 public class MyClassLoader extends URLClassLoader{
    54   private static File file = new File("c:\\classes ");
    55   //該路徑存放著class B,但是沒有class A
    56 
    57   public MyClassLoader() {
    58     super(getUrl());
    59   }
    60 
    61   public static URL[] getUrl() {
    62     try {
    63       return new URL[]{file.toURL()};
    64     } catch ( MalformedURLException e ) {
    65       return new URL[0];
    66     }
    67   }
    68 }

    程序的運行結果為:
    my static field b is 1
    my static field b is 2

    程序的結果非常有意思,從編程者的角度,我們甚至可以把不在同一個分支的類裝載器看作不同的java虛擬機,因為它 們彼此覺察不到對方的存在。程序在使用具有分支的類裝載的體系結構時要非常小心,弄清楚每個類裝載器的類查找范圍,盡量避免父類裝載器和子類裝載器的類查 找范圍中有相同類名的類(包括包名和類名),下面這個例子就是用來說明這種情況可能帶來的問題。

    假設有相同名字卻不同版本的接口 A,

    版本 1:
    package test;
    Intefer Same{ 
    public String getVersion(); }

    版本 2:
    Package test;
    Intefer Same{ 
    public String getName(); }

    接口A兩個版本的實現:

    版本1的實現
    package test;
    public class Same1Impl implements Same {
    public String getVersion(){ return "A version 1";}
    }

    版本2的實現
    public class Same 2Impl implements Same {
    public String getName(){ return "A version 2";}
    }

    我們依然使用圖2的類裝載器結構,首先將版本1的Same和Same的實現類Same1Impl打成包 same1.jar,將版本2的Same和Same的實現類Same1Impl打成包same2.jar。現在,做這樣的事情,把same1.jar放入 類裝載器ClassLoaderA的類查找范圍中,把same2.jar放入類裝器ClassLoaderAB的類查找范圍中。當你興沖沖的運行下面這個 看似正確的程序。

    實際上這個錯誤的是由父類載器優先裝載的機制造成,當類裝載器ClassLoaderAB在裝載Same2Impl 類時發現必須裝載接口test.Same,于是按規定請求父類裝載器裝載,父類裝載器發現了版本1的test.Same接口并興沖沖的裝載,但是卻想不到 Same2Impl所希望的是版本2 的test.Same,后面的事情可想而知了,異常被拋出。

    我們很難責怪Java中暫時并沒有提供區分版本的機制,如果使用了比較復雜的類裝載器體系結構,在出現了某個包或者類的多個版本時,應特別注意。

    掌握和靈活運用Java的類裝載器的體系結構,對程序的系統設計,程序的實現,已經程序的調試,都有相當大的幫助。希望以上的內容能夠對您有所幫助.

    轉載自:http://gceclub.sun.com.cn/yuanchuang/week-9/classloader.html
    posted @ 2006-02-23 18:43 Leo 閱讀(475) | 評論 (0)編輯 收藏

    僅列出標題  下一頁
    主站蜘蛛池模板: 色网站在线免费观看| 免费观看在线禁片| 亚洲第一AAAAA片| 人与禽交免费网站视频| 亚洲国产精品美女久久久久| 亚洲国产成人VA在线观看| 日韩免费电影网站| 亚洲国产成人久久精品大牛影视 | 一区二区三区免费视频网站| 亚洲AV美女一区二区三区| 永久在线毛片免费观看| 国产成人精品免费久久久久| 亚洲精品美女网站| 国产亚洲综合网曝门系列| 天天看片天天爽_免费播放| a毛片免费全部播放完整成| 亚洲日韩精品无码专区加勒比☆| 中文字幕一精品亚洲无线一区| 久九九精品免费视频| 中出五十路免费视频| 亚洲av永久无码精品秋霞电影秋 | 曰韩无码AV片免费播放不卡| 亚洲国产精品久久网午夜| 4338×亚洲全国最大色成网站| 麻豆最新国产剧情AV原创免费| 两性色午夜视频免费网| 久久精品亚洲日本波多野结衣| 中文字幕亚洲精品资源网| 国产国拍精品亚洲AV片| 免费的一级黄色片| 97免费人妻无码视频| 国产成人AV片无码免费| 国产精品免费观看视频| 亚洲GV天堂GV无码男同| 亚洲av午夜精品无码专区| 亚洲av无码国产精品夜色午夜| 亚洲?v女人的天堂在线观看 | 日韩精品亚洲人成在线观看| 亚洲中文字幕不卡无码| 亚洲Av无码乱码在线播放| 四虎成人免费网站在线|