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

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

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

    Leo's Blog

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

    2006年5月9日 #

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

    轉(zhuǎn)載自: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 閱讀(906) | 評論 (2)編輯 收藏

    2006年5月8日 #

    ANT安裝、配置

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


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


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

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

    注:我看到很多項目的ant腳本中的命名基本上都是一致的,比如:編譯一般叫build或者compile;打包一般叫jar或war;生成文檔一般命名為javadoc或javadocs;執(zhí)行全部任務all。在每個任務的中,ANT會根據(jù)配置調(diào)用一些外部應用并配以相應參數(shù)執(zhí)行。雖然ANT可調(diào)用的外部應用種類非常豐富,但其實最常用的就2,3個:比如javac javadoc jar等。
    ANT的安裝
    解包后在系統(tǒng)可執(zhí)行路徑中加入指向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

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


    ANT的使用

    最好的學習過程就是看懂那些open source項目中的build.xml腳本,然后根據(jù)自己的需要簡化成一個更簡單的,ANT和APACHE上很多非常工程派的項目:簡單易用,而且適應性非常強,因為這些項目的建立往往來源于開發(fā)人員日常最直接的需求。
    以下是的一個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.

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

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

    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
    創(chuàng)建臨時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環(huán)境通過一下方式找到引用一個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等
    郵件警報任務:可以把以上這些任務的輸出警告發(fā)送到制定的用戶列表中,這個任務可以設置每天自動運行。

    參考資料:

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



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

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

    最近寫程序已經(jīng)很少直接用JDBC了,一直都是用ibaits, Hibernate等來招呼,因為現(xiàn)在的集成框架已經(jīng)很穩(wěn)定了。不過對JDBC的直接使用還是不可以忽略的,JDBC3.0提供的n多的新特征還是要熟悉了解的,以前學jdbc的時候就是上網(wǎng)找些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'");

    這是一個用戶登錄時,經(jīng)常用到的代碼,先是根據(jù)用戶名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時傳不同的參數(shù),可以對ResultSet進行不用的錯作限制。con.createStatement的時候,有三種可以掉用的函數(shù):

    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中的數(shù)據(jù)記錄是只讀的,可以修改
    ?? 2、ResultSet.CONCUR_UPDATABLE? 在ResultSet中的數(shù)據(jù)記錄可以任意修改,然后更新會數(shù)據(jù)庫
    其中resultSetHoldability可選值是:
    ?? 1、ResultSet.HOLD_CURSORS_OVER_COMMIT 表示修改提交時,不關(guān)閉ResultSet的游標
    ?? 2、ResultSet.CLOSE_CURSORS_AT_COMMIT? 表示修改提交時,關(guān)閉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的代碼,我把一些特征函數(shù)都用出來,但是只是用來查考和說明名靈活性的。

    ?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提供的還有一個功能就是數(shù)據(jù)庫的批量操作

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

    但是有一點要注意,stmt3.executeBatch()他不會自動給你回滾數(shù)據(jù)操作,當你有5條update語句的時候,如果第三條發(fā)生錯誤,那么將無法自動回滾前兩條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失敗的時候,系統(tǒng)事務回滾UPDATE authors的sql的影響,而INSERT INTO authors的sql仍然有效


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

    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 閱讀(1549) | 評論 (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 閱讀(481) | 評論 (2)編輯 收藏

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

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

    2006年4月3日 #

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

    2006年2月23日 #


    作者:朱騏

    作者簡介


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

    內(nèi)容摘要


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

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

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

    1  面向Web的JMS應用系統(tǒng)實現(xiàn)原理

    1.1  JMS應用系統(tǒng)消息傳遞原理


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

    1.2  Web應用的消息提交機制


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

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

    2  CWNF校務系統(tǒng)模型


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

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

    2.1  數(shù)據(jù)與數(shù)據(jù)流模型


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

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

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

    2.2  組件模型


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

    3  關(guān)鍵的實現(xiàn)技術(shù)

    3.1  JDOM建立、輸出XML文檔


    JDOM是一個開放源代碼的純Java樹式API,用于分析、建立、處理和序列化XML文檔。在數(shù)據(jù)流模型中,XML元素和XML文檔都由JDOM API建立,在發(fā)送方,通過用戶提交的表單取得名/值對若干,這些數(shù)據(jù)經(jīng)過JDOM方法處理生成XML元素對象,元素對象被作為消息的消息體發(fā)往主題存 儲;在接收方,持久訂閱者接收到若干XML元素對象后,繼續(xù)通過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文檔都有相關(guān)的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間數(shù)據(jù)的傳遞

    3.3.1  注冊/登錄


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

    3.3.2  通知與反饋的數(shù)據(jù)關(guān)聯(lián)


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

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

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

    <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  結(jié)束語


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

    轉(zhuǎn)載自:http://gceclub.sun.com.cn/yuanchuang/2004_Q4/jms.html

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

    作者:劉學超

    1  Java技術(shù)與Java虛擬機

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

    圖1  Java四個方面的關(guān)系

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

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

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

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

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

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

    2  Java虛擬機的體系結(jié)構(gòu)

    剛才已經(jīng)提到,JVM可以由不同的廠商來實現(xiàn)。由于廠商的不同必然導致JVM在實現(xiàn)上的一些不同,然而JVM還是可以實現(xiàn)跨平臺的特性,這就要歸功于設計JVM時的體系結(jié)構(gòu)了。

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

    圖3  JVM的體系結(jié)構(gòu)

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

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

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

    Java指令集中的指令包含一個單字節(jié)的操作符,用于指定要執(zhí)行的操作,還有0個或多個操作數(shù),提供操作所需的參數(shù)或數(shù)據(jù)。許多指令沒有操作數(shù),僅由一個單字節(jié)的操作符構(gòu)成。

    虛擬機的內(nèi)層循環(huán)的執(zhí)行過程如下: 
    do{
    取一個操作符字節(jié);
    根據(jù)操作符的值執(zhí)行一個動作;
    }while(程序未結(jié)束)

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

    第一個字節(jié)*256+第二個字節(jié)字節(jié)碼。

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

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

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

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

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

    1. pc: Java程序計數(shù)器;
    2. optop: 指向操作數(shù)棧頂端的指針;
    3. frame: 指向當前執(zhí)行方法的執(zhí)行環(huán)境的指針;。
    4. vars: 指向當前執(zhí)行方法的局部變量區(qū)第一個變量的指針。

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

    Java虛擬機的棧有三個區(qū)域:局部變量區(qū)、運行環(huán)境區(qū)、操作數(shù)區(qū)。

    局部變量區(qū)

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

    運行環(huán)境區(qū)

    在運行環(huán)境中包含的信息用于動態(tài)鏈接,正常的方法返回以及異常捕捉。

    動態(tài)鏈接

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

    正常的方法返回

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

    異常捕捉

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

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

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

    操作數(shù)棧區(qū)

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

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

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

    3  Java虛擬機的運行過程

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

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

    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

    將通過調(diào)用HelloApp的方法main來啟動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數(shù)組?,F(xiàn)在我們略述虛擬機在執(zhí)行HelloApp時可能采取的步驟。

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

    圖4:虛擬機的運行過程

    4  結(jié)束語

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

    轉(zhuǎn)載自:http://gceclub.sun.com.cn/staticcontent/html/2004-04-09/jvm.html

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

    作者:盛戈歆

    作者簡介

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

    正文

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

    1. Java類裝載體系結(jié)構(gòu)

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

    我們簡短總結(jié)一下上面的討論:

    1.JVM類裝載器的體系結(jié)構(gòu)可以看作是樹狀結(jié)構(gòu)。

    2.父類裝載器優(yōu)先裝載。在父類裝載器裝載失敗的情況下再裝載,如果都裝載失敗則拋出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理會,先不要驚訝,仔細想想,這不正是我們最需要 得到的結(jié)果。我們仔細了解一下JVM裝載順序。當使用Java A命令運行A類時,JVM會首先要求類路徑類裝載器(AppClassLoader)裝載A類,但是這時只裝載A,不會裝載A中出現(xiàn)的其他類(B類),接 著它會調(diào)用A中的main函數(shù),直到運行語句b = new B()時,JVM發(fā)現(xiàn)必須裝載B類程序才能繼續(xù)運行,于是類路徑類裝載器會去裝載B類,雖然我們可以看到B中有有C類的聲明,但是并不是實際的執(zhí)行語句, 所以并不去裝載C類,也就是說JVM按照運行時的有效執(zhí)行語句,來決定是否需要裝載新類,從而裝載盡可能少的類,這一點和編譯類是不相同的。

    2.2 顯式的類裝載

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

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

    Class.forName("test.A");

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

    是一樣的。

    使用類路徑類裝載裝載.

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

    使用當前進程上下文的使用的類裝載器進行裝載,這種裝載類的方法常常被有著復雜類裝載體系結(jié)構(gòu)的系統(tǒng)所使用。

    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并沒有增加類的查找范圍,因此它和類路徑裝載器有相同的效果。

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

    圖2:某個程序的類裝載器的結(jié)構(gòu)

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

    3. 奇怪的隔離性

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

     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中的靜態(tài)變量的值 
    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的查找范圍內(nèi),
    38 //而不應該在MyClassLoader的父類裝載器的查找范圍內(nèi)。
    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 }

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

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

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

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

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

    接口A兩個版本的實現(xiàn):

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

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

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

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

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

    掌握和靈活運用Java的類裝載器的體系結(jié)構(gòu),對程序的系統(tǒng)設計,程序的實現(xiàn),已經(jīng)程序的調(diào)試,都有相當大的幫助。希望以上的內(nèi)容能夠?qū)δ兴鶐椭?

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

    僅列出標題  下一頁
    主站蜘蛛池模板: 成人a视频片在线观看免费| 亚洲a一级免费视频| 日韩视频在线免费| 国产日本亚洲一区二区三区| 亚洲一区二区免费视频| 亚洲一区影音先锋色资源| 久久久久久AV无码免费网站| 亚洲天堂一区二区| 亚洲成人免费网站| 中文字幕亚洲综合久久综合| 好先生在线观看免费播放| 亚洲精品久久无码av片俺去也| 女人张开腿给人桶免费视频| 亚洲码和欧洲码一码二码三码| 国产色爽女小说免费看| 美女视频黄a视频全免费网站色| 亚洲色偷偷狠狠综合网| a级毛片在线免费| 亚洲理论精品午夜电影| 成年女人午夜毛片免费视频| 亚洲国产成人无码AV在线| 亚洲国产精品激情在线观看| 在线观看免费视频一区| 亚洲欧洲另类春色校园小说| 全免费一级毛片在线播放| 免费无码专区毛片高潮喷水| 亚洲中文字幕无码不卡电影| 91大神免费观看| 亚洲欧美成aⅴ人在线观看| 亚洲国产精品嫩草影院久久| 久久精品成人免费看| 亚洲高清日韩精品第一区| 免费高清av一区二区三区| 亚洲精品视频免费| 亚洲精品国产福利在线观看| 在线观看免费亚洲| 久久久久久国产精品免费免费男同| 亚洲综合小说另类图片动图| 久久精品夜色噜噜亚洲A∨| 6080午夜一级毛片免费看| 黄网站色视频免费看无下截|