<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>jxh118</title>
    <description></description>
    <link>http://jxh118.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>使用dom4j的xPath解析XML</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/209769" style="color:red;">http://jxh118.javaeye.com/blog/209769</a>&nbsp;
          发表时间: 2008年06月30日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp;</p>
<p><span style="font-size: x-small;">books.xml:</span></p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;books&gt;
    &lt;!--This is a test for dom4j, jakoes, 2007.7.19--&gt;
    &lt;book show="yes" url="lucene.net"&gt;
        &lt;title id="456"&gt;Lucene Studing&lt;/title&gt;
    &lt;/book&gt;
    &lt;book show="yes" url="dom4j.com"&gt;
        &lt;title id="123"&gt;Dom4j Tutorials&lt;/title&gt;
    &lt;/book&gt;
    &lt;book show="no" url="spring.org"&gt;
        &lt;title id="789"&gt;Spring in Action&lt;/title&gt;
    &lt;/book&gt;
    &lt;owner&gt;O'Reilly&lt;/owner&gt;
&lt;/books&gt;
</pre>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">下面我们使用dom4j的xPath来解析：</span></p>
<pre name="code" class="java">public void parseBooks(){
       
        SAXReader reader = new SAXReader();
        try {
            Document doc = reader.read("books.xml");
            Node root = doc.selectSingleNode("/books");
            List list = root.selectNodes("book[@url='dom4j.com']");
           
            for(Object o:list){
               
                Element e = (Element) o;
                String show=e.attributeValue("show");
                System.out.println("show = " + show);
            }
          
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-size: x-small;">Document doc = reader.read("books.xml");的意思是加载XML文档，此是可以用doc.asXML()来查看，它将打印整个xml文档。<br /><br />&nbsp; Node root = doc.selectSingleNode("/books");是读取刚才加载的xml文档内的books节点下的所有内容，对于本例也是整个xml文档。<br />&nbsp; 当然我们也可以加载/books下的某一个节点，如：book节点<br />Node</span> <span style="font-size: x-small;">root</span> <span style="font-size: x-small;">= doc.selectSingleNode("/books/book");<br />或：</span><span style="font-size: x-small;">Node</span> <span style="font-size: x-small;">root</span> <span style="font-size: x-small;">= doc.selectSingleNode("/books/*");<br />注意：如果有多个book节点，它只会读取第一个<br /></span><span style="font-size: x-small;">root.asXML()将打印：<br /><span style="color: #0000ff;">&lt;book show="yes" url="lucene.net"&gt;</span><br /><span style="color: #0000ff;">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;title id="456"&gt;Lucene Studing&lt;/title&gt;</span><br /><span style="color: #0000ff;">&lt;/book&gt;</span><br /><br />&nbsp; 既然加载了这么多，那我怎么精确的得到我想要的节点呢，别急，看下面：<br /></span><span style="font-size: x-small;">List list = root.selectNodes("book[@url='dom4j.com']");<br />它的意思就是读取books节点下的</span><span style="font-size: x-small;">book节点，且book的节点的url属性为</span><span style="font-size: x-small;">dom4j.com<br />为什么使用list来接收呢，如果有两个book节点，且它们的url属性都为dom4j.com，此时就封闭到list里了。<br /><br />&nbsp; 如果想读取books下的所有book节点，可以这样：<br />List list = root.selectNodes("book");<br /><br />&nbsp; 如果想读取books节点下的book节点下的title节点，可以这样：<br />List list2 = root.selectNodes("book[@url='dom4j.com']/title[@id='123']");<br /><br />&nbsp; 注意：</span><span style="font-size: x-small;">selectNodes()参数的格式：<br />&nbsp; 节点名[@属性名='属性值']，如：</span><span style="font-size: x-small;">book[@url='dom4j.com']<br />&nbsp; 如果有多个节点，用&ldquo;/&rdquo;分开，如：</span><span style="font-size: x-small;">book[@url='dom4j.com']/title[@id='123']<br /><br />&nbsp; 最近就是读取封闭在List里的内容了，可以用Node来读取，也可以用Element来转换。<br /></span><span style="font-size: x-small;">attributeValue("属性")是读取该节点的属性值<br />getText()是读取节点的的内容。<br /></span></p>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/209769#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 30 Jun 2008 14:53:18 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/209769</link>
        <guid>http://jxh118.javaeye.com/blog/209769</guid>
      </item>
      <item>
        <title>Spring+JPA+MySQL的配置文件</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/207751" style="color:red;">http://jxh118.javaeye.com/blog/207751</a>&nbsp;
          发表时间: 2008年06月25日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>一直在整合spring+hibernate，今天整合一下JPA +Spring</p>
<p>persistence.xml <br />默认放在&nbsp;META-INF&nbsp;目录下 </p>
<pre name="code" class="xml">&lt;persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd" version="1.0"&gt; 
&lt;persistence-unit name="java2000" transaction-type="RESOURCE_LOCAL"&gt; 
&lt;provider&gt;org.hibernate.ejb.HibernatePersistence&lt;/provider&gt; 
&lt;class&gt;MyClass&lt;/class&gt; 
&lt;properties&gt; 
&lt;property name="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider" /&gt; 
&lt;property name="show_sql" value="true" /&gt; 
&lt;property name="hibernate.cache.use_second_level_cache" value="true" /&gt; 
&lt;property name="hibernate.cache.use_query_cache" value="false" /&gt; 
&lt;/properties&gt; 
&lt;/persistence-unit&gt; 
&lt;/persistence&gt; 
</pre>
<p>&nbsp;applicationContext.xml</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt; 
&lt;beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd 
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd 
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"&gt; 

&lt;bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /&gt; 

&lt;bean id="Myervice" class="MyService" /&gt; 
&lt;bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"&gt; 
&lt;property name="driverClassName"&gt; 
&lt;value&gt;com.mysql.jdbc.Driver&lt;/value&gt; 
&lt;/property&gt; 
&lt;property name="url"&gt; 
&lt;value&gt;jdbc:mysql://localhost:3306&lt;/value&gt; 
&lt;/property&gt; 
&lt;property name="username"&gt; 
&lt;value&gt;########&lt;/value&gt; 
&lt;/property&gt; 
&lt;property name="password"&gt; 
&lt;value&gt;########&lt;/value&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

&lt;!-- JPA EntityManagerFactoryBean for EntityManager--&gt; 
&lt;bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"&gt; 
&lt;property name="persistenceXmlLocation" value="persistence.xml" /&gt; 
&lt;property name="persistenceUnitName" value="java2000" /&gt; 
&lt;property name="dataSource" ref="dataSource" /&gt; 
&lt;/bean&gt; 

&lt;!-- Transaction manager for JPA --&gt; 
&lt;bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"&gt; 
&lt;property name="entityManagerFactory"&gt; 
&lt;ref bean="entityManagerFactory" /&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

&lt;tx:annotation-driven transaction-manager="transactionManager" /&gt; 

&lt;/beans&gt; 
</pre>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/207751#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 25 Jun 2008 10:17:48 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/207751</link>
        <guid>http://jxh118.javaeye.com/blog/207751</guid>
      </item>
      <item>
        <title>Spring 事务管理</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/207733" style="color:red;">http://jxh118.javaeye.com/blog/207733</a>&nbsp;
          发表时间: 2008年06月25日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>通常建议采用声明式事务管理。声明式事务管理的优势非常明显：代码中无需关于关注事务逻辑，让Spring声明式事务管理负责事务逻辑，声明式事务管理无需与具体的事务逻辑耦合，可以方便地在不同事务逻辑之间切换。 <br />声明式事务管理的配置方式，通常有如下三种： <br />1.使用TransactionProxyFactoryBean为目标bean生成事务代理的配置。此方式是最传统，配置文件最臃肿、难以阅读的方式。 <br />2.采用bean继承的事务代理配置方式，比较简洁，但依然是增量式配置。 <br />3.使用BeanNameAutoProxyCreator，根据bean name自动生成事务代理的方式，这是直接利用Spring的AOP框架配置事务代理的方式，需要对Spring的AOP框架有所理解。但这种方式避免了增量式配置，效果非常不错。 <br />4.DefaultAdvisorAutoProxyCreator：这也是直接利用Spring的AOP框架配置事务代理的方式，效果也非常不多，只是这种配置方式的可读性不如第三种方式。 <br />一. 利用TransactionProxyFactoryBean生成事务代理 <br />采用这种方式的配置时候，配置文件的增加非常快，每个bean有需要两个bean配置，一个目标，另外还需要使用TransactionProxyFactoryBean配置一个代理bean。 <br />这是一种最原始的配置方式，下面是使用TransactionProxyFactoryBean的配置文件：</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="gb2312"?&gt; 
&lt;!-- Spring配置文件的文件头，包含DTD等信息--&gt; 
&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd"&gt; 
&lt;beans&gt; 
&lt;!--定义数据源--&gt; 
&lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt; 
&lt;!-- 定义数据库驱动--&gt; 
&lt;property name="driverClassName"&gt;&lt;value&gt;com.mysql.jdbc.Driver&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库url--&gt; 
&lt;property name="url"&gt;&lt;value&gt;jdbc:mysql://localhost:3306/spring&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库用户名--&gt; 
&lt;property name="username"&gt;&lt;value&gt;root&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库密码--&gt; 
&lt;property name="password"&gt;&lt;value&gt;32147&lt;/value&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!--定义一个hibernate的SessionFactory--&gt; 
&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt; 
&lt;!-- 定义SessionFactory必须注入DataSource--&gt; 
&lt;property name="dataSource"&gt;&lt;ref local="dataSource"/&gt;&lt;/property&gt; 
&lt;property name="mappingResources"&gt; 
&lt;list&gt; 
&lt;!--以下用来列出所有的PO映射文件--&gt; 
&lt;value&gt;Person.hbm.xml&lt;/value&gt; 
&lt;/list&gt; 
&lt;/property&gt; 
&lt;property name="hibernateProperties"&gt; 
&lt;props&gt; 
&lt;!--此处用来定义hibernate的SessionFactory的属性： 
不同数据库连接，启动时选择create,update,create-drop--&gt; 
&lt;prop key="hibernate.dialect"&gt;org.hibernate.dialect.MySQLDialect&lt;/prop&gt; 
&lt;prop key="hibernate.hbm2ddl.auto"&gt;update&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 定义事务管理器，使用适用于Hibernte的事务管理器--&gt; 
&lt;bean id="transactionManager" 
class="org.springframework.orm.hibernate3.HibernateTransactionManager"&gt; 
&lt;!-- HibernateTransactionManager bean需要依赖注入一个SessionFactory bean的引用--&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!--定义DAO Bean , 作为事务代理的目标--&gt; 
&lt;bean id="personDaoTarget" class="lee.PersonDaoHibernate"&gt; 
&lt;!-- 为DAO bean注入SessionFactory引用--&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 定义DAO bean的事务代理--&gt; 
&lt;bean id="personDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt; 
&lt;!-- 为事务代理bean注入事务管理器--&gt; 
&lt;property name="transactionManager"&gt;&lt;ref bean="transactionManager"/&gt;&lt;/property&gt; 
&lt;!-- 设置事务属性--&gt; 
&lt;property name="transactionAttributes"&gt; 
&lt;props&gt; 
&lt;!-- 所有以find开头的方法，采用required的事务策略，并且只读--&gt; 
&lt;prop key="find*"&gt;PROPAGATION_REQUIRED,readOnly&lt;/prop&gt; 
&lt;!-- 其他方法，采用required的事务策略 -&gt; 
&lt;prop key="*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;!-- 为事务代理bean设置目标bean --&gt; 
&lt;property name="target"&gt; 
&lt;ref local="personDaoTarget"/&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;/beans&gt; 
</pre>
<p>&nbsp;在上面的配置文件中，personDao需要配置两个部分，一个是personDao的目标bean，该目标bean是实际DAO bean，以实际的DAO bean为目标，建立事务代理。一个组件，需要来个bean组成，一个目标bean，一个事务代理。 <br />这种配置方式还有一个坏处：目标bean直接暴露在Spring容器中，可以直接引用，如果目标bean被误引用，将导致业务操作不具备事务性。 <br />为了避免这种现象，可将目标bean配置成嵌套bean，下面是目标bean和事务代理的配置片段：</p>
<pre name="code" class="xml">&lt;!-- 定义DAO bean的事务代理--&gt; 
&lt;bean id="personDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt; 
&lt;!-- 为事务代理bean注入事务管理器--&gt; 
&lt;property name="transactionManager"&gt;&lt;ref bean="transactionManager"/&gt;&lt;/property&gt; 
&lt;!-- 设置事务属性--&gt; 
&lt;property name="transactionAttributes"&gt; 
&lt;props&gt; 
&lt;!-- 所有以find开头的方法，采用required的事务策略，并且只读--&gt; 
&lt;prop key="find*"&gt;PROPAGATION_REQUIRED,readOnly&lt;/prop&gt; 
&lt;!-- 其他方法，采用required的事务策略 -&gt; 
&lt;prop key="*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;!-- 为事务代理bean设置目标bean --&gt; 
&lt;property name="target"&gt; 
&lt;!-- 采用嵌套bean配置目标bean--&gt; 
&lt;bean class="lee.PersonDaoHibernate"&gt; 
&lt;!-- 为DAO bean注入SessionFactory引用--&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

</pre>
<p>&nbsp;二. 利用继承简化配置 <br />大部分情况下，每个事务代理的事务属性大同小异，事务代理的实现类都是TransactionProxyFactoryBean，事务代理bean都必须注入事务管理器。 <br />对于这种情况，Spring提供了bean与bean之间的继承，可以简化配置。将大部分的通用配置，配置成事务模板，而实际的事务代理bean，则继承事务模板。这种配置方式可以减少部分配置代码，下面是采用继承的配置文件：</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="gb2312"?&gt; 
&lt;!-- Spring配置文件的文件头，包含DTD等信息--&gt; 
&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd"&gt; 
&lt;beans&gt; 
&lt;!--定义数据源--&gt; 
&lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt; 
&lt;!-- 定义数据库驱动--&gt; 
&lt;property name="driverClassName"&gt;&lt;value&gt;com.mysql.jdbc.Driver&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库url--&gt; 
&lt;property name="url"&gt;&lt;value&gt;jdbc:mysql://localhost:3306/spring&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库用户名--&gt; 
&lt;property name="username"&gt;&lt;value&gt;root&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库密码--&gt; 
&lt;property name="password"&gt;&lt;value&gt;32147&lt;/value&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!--定义一个hibernate的SessionFactory--&gt; 
&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt; 
&lt;!-- 定义SessionFactory必须注入DataSource--&gt; 
&lt;property name="dataSource"&gt;&lt;ref local="dataSource"/&gt;&lt;/property&gt; 
&lt;property name="mappingResources"&gt; 
&lt;list&gt; 
&lt;!--以下用来列出所有的PO映射文件--&gt; 
&lt;value&gt;Person.hbm.xml&lt;/value&gt; 
&lt;/list&gt; 
&lt;/property&gt; 
&lt;property name="hibernateProperties"&gt; 
&lt;props&gt; 
&lt;!--此处用来定义hibernate的SessionFactory的属性： 
不同数据库连接，启动时选择create,update,create-drop--&gt; 
&lt;prop key="hibernate.dialect"&gt;org.hibernate.dialect.MySQLDialect&lt;/prop&gt; 
&lt;prop key="hibernate.hbm2ddl.auto"&gt;update&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 定义事务管理器，使用适用于Hibernte的事务管理器--&gt; 
&lt;bean id="transactionManager" 
class="org.springframework.orm.hibernate3.HibernateTransactionManager"&gt; 
&lt;!-- HibernateTransactionManager bean需要依赖注入一个SessionFactory bean的引用--&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 配置事务模板，模板bean被设置成abstract bean，保证不会被初始化--&gt; 
&lt;bean id="txBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
lazy-init="true" abstract="true"&gt; 
&lt;!-- 为事务模板注入事务管理器--&gt; 
&lt;property name="transactionManager"&gt;&lt;ref bean="transactionManager"/&gt;&lt;/property&gt; 
&lt;!-- 设置事务属性--&gt; 
&lt;property name="transactionAttributes"&gt; 
&lt;props&gt; 
&lt;prop key="find*"&gt;PROPAGATION_REQUIRED,readOnly&lt;/prop&gt; 
&lt;prop key="*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 实际的事务代理bean--&gt; 
&lt;bean id="personDao" parent="txBase"&gt; 
&lt;!-- 采用嵌套bean配置目标bean --&gt; 
&lt;property name="target"&gt; 
&lt;bean class="lee.PersonDaoHibernate"&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;/beans&gt; 
</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>这种配置方式，相比前面直接采用TransactionProxyFactoryBean的事务代理配置方式，可以大大减少配置文件的代码量。每个事务代理的配置都继承事务模板，无需重复指定事务代理的实现类，无需重复指定事务传播属性&mdash;&mdash;当然，如果新的事务代理有额外的事务属性，也可指定自己的事务属性，此时，子bean的属性覆盖父bean的属性。当然每个事务代理bean都必须配置自己的目标bean，这不可避免。 <br />上面的配置可看出，事务代理的配置依然是增量式的，每个事务代理都需要单独配置&mdash;&mdash;虽然增量已经减少，但每个事务代理都需要单独配置。 <br /><br />三.用BeanNameAutoProxyCreator自动创建事务代理 <br /><br />下面介绍一种优秀的事务代理配置策略：采用这种配置策略，完全可以避免增量式配置，所有的事务代理由系统自动创建。容器中的目标bean自动消失，避免需要使用嵌套bean来保证目标bean不可被访问。 <br />这种配置方式依赖于Spring提供的bean后处理器，该后处理器用于为每个bean自动创建代理，此处的代理不仅可以是事务代理，也可以是任意的代理，只需要有合适的拦截器即可。这些是AOP框架的概念，笔者在此处不对AOP进行深入介绍。读者只需了解这种事务代理的配置方式即可。 <br />下面是采用BeanNameAutoProxyCreator配置事务代理的配置文件： </p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="gb2312"?&gt; 
&lt;!-- Spring配置文件的文件头，包含DTD等信息--&gt; 
&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd"&gt; 
&lt;beans&gt; 
&lt;!--定义数据源--&gt; 
&lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt; 
&lt;!-- 定义数据库驱动--&gt; 
&lt;property name="driverClassName"&gt;&lt;value&gt;com.mysql.jdbc.Driver&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库url--&gt; 
&lt;property name="url"&gt;&lt;value&gt;jdbc:mysql://localhost:3306/spring&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库用户名--&gt; 
&lt;property name="username"&gt;&lt;value&gt;root&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库密码--&gt; 
&lt;property name="password"&gt;&lt;value&gt;32147&lt;/value&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!--定义一个hibernate的SessionFactory--&gt; 
&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt; 
&lt;!-- 定义SessionFactory必须注入DataSource--&gt; 
&lt;property name="dataSource"&gt;&lt;ref local="dataSource"/&gt;&lt;/property&gt; 
&lt;property name="mappingResources"&gt; 
&lt;list&gt; 
&lt;!--以下用来列出所有的PO映射文件--&gt; 
&lt;value&gt;Person.hbm.xml&lt;/value&gt; 
&lt;/list&gt; 
&lt;/property&gt; 
&lt;property name="hibernateProperties"&gt; 
&lt;props&gt; 
&lt;!--此处用来定义hibernate的SessionFactory的属性： 
不同数据库连接，启动时选择create,update,create-drop--&gt; 
&lt;prop key="hibernate.dialect"&gt;org.hibernate.dialect.MySQLDialect&lt;/prop&gt; 
&lt;prop key="hibernate.hbm2ddl.auto"&gt;update&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 定义事务管理器，使用适用于Hibernte的事务管理器--&gt; 
&lt;bean id="transactionManager" 
class="org.springframework.orm.hibernate3.HibernateTransactionManager"&gt; 
&lt;!-- HibernateTransactionManager bean需要依赖注入一个SessionFactory bean的引用--&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 

&lt;!-- 配置事务拦截器--&gt; 
&lt;bean id="transactionInterceptor" 
class="org.springframework.transaction.interceptor.TransactionInterceptor"&gt; 
&lt;!-- 事务拦截器bean需要依赖注入一个事务管理器 --&gt; 
&lt;property name="transactionManager" ref="transactionManager"/&gt; 
&lt;property name="transactionAttributes"&gt; 
&lt;!-- 下面定义事务传播属性--&gt; 
&lt;props&gt; 
&lt;prop key="insert*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;prop key="find*"&gt;PROPAGATION_REQUIRED,readOnly&lt;/prop&gt; 
&lt;prop key="*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 定义BeanNameAutoProxyCreator,该bean是个bean后处理器，无需被引用，因此没有id属性 
这个bean后处理器，根据事务拦截器为目标bean自动创建事务代理 
&lt;bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"&gt; 
指定对满足哪些bean name的bean自动生成业务代理 --&gt; 
&lt;property name="beanNames"&gt; 
&lt;!-- 下面是所有需要自动创建事务代理的bean--&gt; 
&lt;list&gt; 
&lt;value&gt;personDao&lt;/value&gt; 
&lt;/list&gt; 
&lt;!-- 此处可增加其他需要自动创建事务代理的bean--&gt; 
&lt;/property&gt; 
&lt;!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器--&gt; 
&lt;property name="interceptorNames"&gt; 
&lt;list&gt; 
&lt;value&gt;transactionInterceptor&lt;/value&gt; 
&lt;!-- 此处可增加其他新的Interceptor --&gt; 
&lt;/list&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!--定义DAO Bean ,由于BeanNameAutoProxyCreator自动生成事务代理--&gt; 
&lt;bean id="personDao" class="lee.PersonDaoHibernate"&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;/beans&gt; 
</pre>
<p>&nbsp;</p>
<p>TranscationInterceptor是一个事务拦截器bean，需要传入一个TransactionManager的引用。配置中使用Spring依赖注入该属性，事务拦截器的事务属性通过transactionAttributes来指定，该属性有props子元素，配置文件中定义了三个事务传播规则： <br />所有以insert开始的方法，采用PROPAGATION_REQUIRED的事务传播规则。程序抛出MyException异常及其子异常时，自动回滚事务。所有以find开头的方法，采用PROPAGATION_REQUIRED事务传播规则，并且只读。其他方法，则采用PROPAGATION_REQUIRED的事务传播规则。 <br />BeanNameAutoProxyCreator是个根据bean名生成自动代理的代理创建器，该bean通常需要接受两个参数。第一个是beanNames属性，该属性用来设置哪些bean需要自动生成代理。另一个属性是interceptorNames，该属性则指定事务拦截器，自动创建事务代理时，系统会根据这些事务拦截器的属性来生成对应的事务代理。 <br />为了让读者对这种配置方式有信息，对PersonDaoHibernate的save方法进行简单 修改，修改后的save方法如下： <br /><br />/** *//** <br />* 保存人实例 <br />* @param person 需要保存的Person实例 <br />*/ <br />public void save(Person person) <br />...{ <br />getHibernateTemplate().save(person); <br />//下面两行代码没有实际意义，仅仅为了引发数据库异常 <br />DataSource ds = null; <br />DataSourceUtils.getConnection(ds); <br />} <br />在主程序中调用该save方法，主程序调用save方法的片段如下： <br /><br />for (int i = 0 ; i &lt; 10 ; i++ ) <br />...{ <br />//保存Person实例 <br />pdao.save(new Person(String.valueOf(i) , i + 10)); <br />} <br />执行完主程序的该片段后，数据表不会插入任何记录。如果BeanNameAutoProxyCreator的配置修改成如下格式： <br /><br />&lt;!-- 定义BeanNameAutoProxyCreator,该bean是个bean后处理器，无需被引用，因此没有id属性 <br />这个bean后处理器，根据事务拦截器为目标bean自动创建事务代理 <br />&lt;bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"&gt; <br />指定对满足哪些bean name的bean自动生成业务代理 --&gt; <br />&lt;property name="beanNames"&gt; <br />&lt;!-- 下面是所有需要自动创建事务代理的bean--&gt; <br />&lt;list&gt; <br />&lt;!-- value&gt;personDao&lt;/value--&gt; <br />&lt;/list&gt; <br />&lt;!-- 此处可增加其他需要自动创建事务代理的bean--&gt; <br />&lt;/property&gt; <br />&lt;!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器--&gt; <br />&lt;property name="interceptorNames"&gt; <br />&lt;list&gt; <br />&lt;value&gt;transactionInterceptor&lt;/value &gt; <br />&lt;!-- 此处可增加其他新的Interceptor --&gt; <br />&lt;/list&gt; <br />&lt;/property&gt; <br />&lt;/bean&gt; <br />注意配置文中beanNames属性的变化，将所有personDao项注释，即不再为该bean生成事务代理。再次执行主程序，程序虽然抛出了数据库异常，但数据记录依然被插入数据库。 <br />对比两次结果，这就是事务代理在其中的作用。 <br />这种配置方式相当简洁，每次增加了新的bean，如果需要该bean的方法具有事务性，只需在BeanNameAutoProxyCreator的beanNames属性下增加一行即可，该行告诉bean后处理需要为哪个bean生成事务代理。 <br /><br />四. 用DefaultAdvisorAutoProxyCreator自动创建事务代理 <br /><br />这种配置方式与BeanNameAutoProxyCreator自动创建代理的方式非常相似，都是使用bean后处理器为目标bean创建实物代理，区别是前者使用事务拦截器创建代理，后者需要使用Advisor创建事务代理。 <br />事实上，采用DefaultAdvisorAutoProxyCreator的事务代理配置方式更加简洁，这个代理生成器自动搜索Spring容器中的Advisor，并为容器中所有的bean创建代理。 <br />相对前一种方式，这种方式的可读性不如前一种直观，笔者还是推荐采用第三种配置方式，下面是使用DefaultAdvisorAutoProxyCreator的配置方式：</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="gb2312"?&gt; 
&lt;!-- Spring配置文件的文件头，包含DTD等信息--&gt; 
&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd"&gt; 
&lt;beans&gt; 
&lt;!--定义数据源--&gt; 
&lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt; 
&lt;!-- 定义数据库驱动--&gt; 
&lt;property name="driverClassName"&gt;&lt;value&gt;com.mysql.jdbc.Driver&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库url--&gt; 
&lt;property name="url"&gt;&lt;value&gt;jdbc:mysql://localhost:3306/spring&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库用户名--&gt; 
&lt;property name="username"&gt;&lt;value&gt;root&lt;/value&gt;&lt;/property&gt; 
&lt;!-- 定义数据库密码--&gt; 
&lt;property name="password"&gt;&lt;value&gt;32147&lt;/value&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!--定义一个hibernate的SessionFactory--&gt; 
&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt; 
&lt;!-- 定义SessionFactory必须注入DataSource--&gt; 
&lt;property name="dataSource"&gt;&lt;ref local="dataSource"/&gt;&lt;/property&gt; 
&lt;property name="mappingResources"&gt; 
&lt;list&gt; 
&lt;!--以下用来列出所有的PO映射文件--&gt; 
&lt;value&gt;Person.hbm.xml&lt;/value&gt; 
&lt;/list&gt; 
&lt;/property&gt; 
&lt;property name="hibernateProperties"&gt; 
&lt;props&gt; 
&lt;!--此处用来定义hibernate的SessionFactory的属性： 
不同数据库连接，启动时选择create,update,create-drop--&gt; 
&lt;prop key="hibernate.dialect"&gt;org.hibernate.dialect.MySQLDialect&lt;/prop&gt; 
&lt;prop key="hibernate.hbm2ddl.auto"&gt;update&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 定义事务管理器，使用适用于Hibernte的事务管理器--&gt; 
&lt;bean id="transactionManager" 
class="org.springframework.orm.hibernate3.HibernateTransactionManager"&gt; 
&lt;!-- HibernateTransactionManager bean需要依赖注入一个SessionFactory bean的引用--&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 配置事务拦截器--&gt; 
&lt;bean id="transactionInterceptor" 
class="org.springframework.transaction.interceptor.TransactionInterceptor"&gt; 
&lt;!-- 事务拦截器bean需要依赖注入一个事务管理器 --&gt; 
&lt;property name="transactionManager" ref="transactionManager"/&gt; 
&lt;property name="transactionAttributes"&gt; 
&lt;!-- 下面定义事务传播属性--&gt; 
&lt;props&gt; 
&lt;prop key="insert*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;prop key="find*"&gt;PROPAGATION_REQUIRED,readOnly&lt;/prop&gt; 
&lt;prop key="*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
&lt;!-- 定义事务Advisor--&gt; 
&lt;bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"&gt; 
&lt;!-- 定义advisor时，必须传入Interceptor--&gt; 
&lt;property name="transactionInterceptor" ref="transactionInterceptor"/&gt; 
&lt;/bean&gt; 
&lt;!-- DefaultAdvisorAutoProxyCreator搜索容器中的 advisor,并为每个bean创建代理 --&gt; 
&lt;bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/&gt; 
&lt;!--定义DAO Bean ,由于BeanNameAutoProxyCreator自动生成事务代理--&gt; 
&lt;bean id="personDao" class="lee.PersonDaoHibernate"&gt; 
&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; 
&lt;/bean&gt; 
&lt;/beans&gt; 
</pre>
<p>&nbsp;</p>
<p>在这种配置方式下，配置文件变得更加简洁，增加目标bean，不需要增加任何额外的代码，容器自动为目标bean生成代理。但这种方式的可读性相对较差。 <br /><br />事务传播行为种类 <br /><br />Spring在TransactionDefinition接口中规定了7种类型的事务传播行为，它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播： <br /><br />表1事务传播行为类型 <br /><br />事务传播行为类型 <br />说明 <br /><br />PROPAGATION_REQUIRED <br />如果当前没有事务，就新建一个事务，如果已经存在一个事务中，加入到这个事务中。这是最常见的选择。 <br /><br />PROPAGATION_SUPPORTS <br />支持当前事务，如果当前没有事务，就以非事务方式执行。 <br /><br />PROPAGATION_MANDATORY <br />使用当前的事务，如果当前没有事务，就抛出异常。 <br /><br />PROPAGATION_REQUIRES_NEW <br />新建事务，如果当前存在事务，把当前事务挂起。 <br /><br />PROPAGATION_NOT_SUPPORTED <br />以非事务方式执行操作，如果当前存在事务，就把当前事务挂起。 <br /><br />PROPAGATION_NEVER <br />以非事务方式执行，如果当前存在事务，则抛出异常。 <br /><br />PROPAGATION_NESTED <br />如果当前存在事务，则在嵌套事务内执行。如果当前没有事务，则执行与PROPAGATION_REQUIRED类似的操作。 <br /><br /><br />当使用PROPAGATION_NESTED时，底层的数据源必须基于JDBC 3.0，并且实现者需要支持保存点事务机制。</p>
<pre name="code" class="xml">&lt;!--Hibernate事务管理器--&gt; 
&lt;bean id="transactionManager" 
class="org.springframework.orm.hibernate3.HibernateTransactionManager"&gt; 
&lt;property name="sessionFactory"&gt; 
&lt;ref bean="sessionFactory" /&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

&lt;!-- 定义事务拦截器bean--&gt; 
&lt;bean id="transactionInterceptor" 
class="org.springframework.transaction.interceptor.TransactionInterceptor"&gt; 
&lt;!-- 事务拦截器bean需要依赖注入一个事务管理器--&gt; 
&lt;property name="transactionManager" ref="transactionManager" /&gt; 
&lt;property name="transactionAttributes"&gt; 
&lt;!-- 下面定义事务传播属性--&gt; 
&lt;props&gt; 
&lt;prop key="save*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;prop key="find*"&gt;PROPAGATION_REQUIRED,readOnly&lt;/prop&gt; 
&lt;prop key="delete*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;prop key="update*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;prop key="*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt; 
&lt;/props&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

&lt;bean id="managerTemplate" abstract="true" lazy-init="true"&gt; 
&lt;property name="teamDao"&gt; 
&lt;ref bean="teamDao" /&gt; 
&lt;/property&gt; 
&lt;property name="studentDao"&gt; 
&lt;ref bean="studentDao" /&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

&lt;bean id ="manager" class="com.zd.service.impl.Manager" parent="managerTemplate" /&gt; 

&lt;!-- 定义BeanNameAutoProxyCreator--&gt; 
&lt;bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"&gt; 
&lt;!-- 指定对满足哪些bean name的bean自动生成业务代理 --&gt; 
&lt;property name="beanNames"&gt; 
&lt;!-- 下面是所有需要自动创建事务代理的bean--&gt; 
&lt;list&gt; 
&lt;value&gt;manager&lt;/value&gt; 
&lt;/list&gt; 
&lt;!-- 此处可增加其他需要自动创建事务代理的bean--&gt; 
&lt;/property&gt; 
&lt;!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器--&gt; 
&lt;property name="interceptorNames"&gt; 
&lt;list&gt; 
&lt;!-- 此处可增加其他新的Interceptor --&gt; 
&lt;value&gt;transactionInterceptor&lt;/value&gt; 
&lt;/list&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

&lt;!-- 基本数据库操作 --&gt; 
&lt;bean id="baseDao" class="com.zd.service.impl.BaseDao"&gt; 
&lt;property name="hibernateTemplate"&gt; 
&lt;ref bean="hibernateTemplate"/&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

&lt;!-- 班级 --&gt; 
&lt;bean id="teamDao" class="com.zd.service.impl.TeamDao"&gt; 
&lt;property name="baseDao"&gt; 
&lt;ref bean="baseDao" /&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 

&lt;!-- 学生 --&gt; 
&lt;bean id="studentDao" class="com.zd.service.impl.StudentDao"&gt; 
&lt;property name="baseDao"&gt; 
&lt;ref bean="baseDao" /&gt; 
&lt;/property&gt; 
&lt;/bean&gt; 
</pre>
<p>&nbsp;public void testSaveTeam() { <br />Team team = new Team(); <br />team.setTeamId(DBKeyCreator.getRandomKey(12)); <br />team.setTeamName("Class CCC"); <br />IManager manager = (IManager) SpringContextUtil.getContext().getBean("manager"); <br /><br /><br />Student student = new Student(); <br />student.setStudentId(DBKeyCreator.getRandomKey(13)); <br />student.setSex(Student.SEX_FEMALE); <br />student.setStudentName("Tom"); <br />student.setTeamId("60FHDXDIG5JQ"); <br />manager.saveTeamAndStu(team, student); <br />System.out.println("Save Team and Student Success"); </p>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/207733#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 25 Jun 2008 09:20:05 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/207733</link>
        <guid>http://jxh118.javaeye.com/blog/207733</guid>
      </item>
      <item>
        <title>使用JMeter 完成常用的压力测试</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/207364" style="color:red;">http://jxh118.javaeye.com/blog/207364</a>&nbsp;
          发表时间: 2008年06月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>本文介绍了 <a href="javascript:;" onclick="tagshow(event, 'JMeter');" target="_self"><span style="text-decoration: underline;"><strong>JMeter</strong></span></a> 相关的基本概念。并以 JMeter 为例，介绍了使用它来完成最常用的三种类型服务器，即 Web 服务器、<a href="javascript:;" onclick="tagshow(event, '%CA%FD%BE%DD%BF%E2');" target="_self"><span style="text-decoration: underline;"><strong>数据库</strong></span></a>服务器和消息中间件，<a href="javascript:;" onclick="tagshow(event, '%D1%B9%C1%A6%B2%E2%CA%D4');" target="_self"><span style="text-decoration: underline;"><strong>压力测试</strong></span></a>的方法、步骤以及注意事项。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 讲到测试，人们脑海中首先浮现的就是针对软件正确性的测试，即常说的<a href="javascript:;" onclick="tagshow(event, '%B9%A6%C4%DC%B2%E2%CA%D4');" target="_self"><span style="text-decoration: underline;"><strong>功能测试</strong></span></a>。但是软件仅仅只是功能正确是不够的。在实际开发中，还有其它的非功能因素也起着决定性的因素，例如软件的响应速度。影响软件响应速度的因素有很多，有些是因为算法不够高效；还有些可能受用户并发数的影响。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在众多类型的<a href="javascript:;" onclick="tagshow(event, '%C8%ED%BC%FE%B2%E2%CA%D4');" target="_self"><span style="text-decoration: underline;"><strong>软件测试</strong></span></a>中，压力测试正是以软件响应速度为测试目标，尤其是针对在较短时间内大量并发用户的访问时，软件的抗压能力。本文以 JMeter 为例，介绍了如何使用它来完成常用的压力测试：Web 测试、数据库测试和 JMS 测试。</p>
<p>概述</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JMeter 最早是为了测试 Tomcat 的前身 JServ 的执行效率而诞生的。到目前为止，它的最新版本是2.1.1，它的测试能力也不再仅仅只局限于对于Web服务器的测试，而是涵盖了数据库、JMS、Web Service、LDAP等多种对象的测试能力。在最新的 2.1.1 中，它还提供了对于 JUNIT 的测试。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JMeter 的安装非常简单，从官方网站上下载，解压之后即可使用。运行命令在%JMETER_HOME%/bin 下，对于 Windows 用户来说，命令是 jmeter.bat。运行前请检查JMeter 的文档，查看是否具备相关的运行条件。对于最新版（即2.1.1），需要JDK的版本要求是JDK 1.4。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JMeter 的主要测试组件总结如下：</p>
<p>1. 测试计划是使用 JMeter 进行测试的起点，它是其它 JMeter 测试元件的容器。</p>
<p>2. 线程组代表一定数量的并发用户，它可以用来模拟并发用户发送请求。实际的请求内容在Sampler中定义，它被线程组包含。</p>
<p>3. 监听器负责收集测试结果，同时也被告知了结果显示的方式。</p>
<p>4. 逻辑控制器可以自定义JMeter发送请求的行为逻辑，它与Sampler结合使用可以模拟复杂的请求序列。</p>
<p>5. 断言可以用来判断请求响应的结果是否如用户所期望的。它可以用来隔离问题域，即在确保功能正确的前提下执行压力测试。这个限制对于有效的测试是非常有用的。</p>
<p>6. 配置元件维护Sampler需要的配置信息，并根据实际的需要会修改请求的内容。</p>
<p>7. 前置处理器和后置处理器负责在生成请求之前和之后完成<a href="javascript:;" onclick="tagshow(event, '%B9%A4%D7%F7');" target="_self"><span style="text-decoration: underline;"><strong>工作</strong></span></a>。前置处理器常常用来修改请求的设置，后置处理器则常常用来处理响应的数据。</p>
<p>8. 定时器负责定义请求之间的延迟间隔。</p>
<p>JMeter的使用非常的容易，在 ONJava.com 上的文章 Using JMeter 提供了一个非常好的入门。</p>
<p>常用测试</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 压力测试不同于功能测试，软件的正确性并不是它的测试重点。它所看重的是软件的执行效率，尤其是短时间内访问用户数爆炸性增长时软件的响应速度，压力测试往往是在功能测试之后进行的。在实际的开发过程中，软件潜在的效率瓶颈一般都是那些可能有多个用户同时访问的节点。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 就目前 <a href="javascript:;" onclick="tagshow(event, 'Java');" target="_self"><span style="text-decoration: underline;"><strong>Java</strong></span></a> EE 的平台下开发的软件来说，这种节点通常可能是：Web 服务器、数据库服务器和 JMS 服务器。它们都是请求主要发生的地点，请求频率较其它的节点要高，而且处于请求序列的关键路径之上。如果它们效率无法提高的话，对于整个软件的效率有致命的影响。而且在这些节点上一般都会发生较大规模的数据交换，有时其中还包含有业务逻辑处理，它们正是在进行压力测试时首先需要考虑的。</p>
<p>本文以这三种节点为例，介绍如何使用 JMeter 来完成针对于它们的压力测试。</p>
<p>Web 服务器</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于大多数的项目来说，并不会自行开发一个Web服务器，因此Web服务器压力测试的对象实际就是--发布到Web服务器中的软件。最简单的<a href="javascript:;" onclick="tagshow(event, 'Web%B2%E2%CA%D4');" target="_self"><span style="text-decoration: underline;"><strong>Web测试</strong></span></a>计划只需要三个 JMeter 的测试元件，如下图：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.51testing.com/batch.download.php?aid=4975" target="_blank"><img src="http://www.51testing.com/attachments/2007/07/2_200707161509491.jpg" border="0" alt="" /></a></p>
<p>其中：</p>
<p>在线程组中定义线程数、产生线程发生的时间和测试循环次数。 <br />在http请求中定义服务器、端口、协议和方法、请求路径等。 <br />表格监听器负责收集和显示结果。 <br />这种设置对于包含了安全机制的 web 应用是不够的，典型的 web 应用一般都会：</p>
<p>1. 有一个登录页，它是整个应用的入口。当用户登录之后，应用会将用户相关的安全信息放到 session 中。</p>
<p>2. 有一个 filter，它拦截请求，检查每个请求相关的 session 中是否包含有用户安全信息。如果没有，那么请求被重定向到登录页，要求用户提供安全信息。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在这种配置下应用上面的测试计划，那么除了登录页之外的其它请求都将因为缺少用户安全信息，而使请求实际定位到登录页。如果不加断言，那么在监听器看来所有的请求都是成功。而实际上，这些请求最终都没有到达它们应该去的地方。显然，这种测试结果不是我们所期望的。</p>
<p>为了成功的测试，至少有2种方法：</p>
<p>方法一，去掉程序的安全设置，如filter，使得不需要用户安全信息也能访问受限内容； <br />方法二，不修改程序，使用JMeter提供的"Http URL重写修饰符"或"Http Cookie管理器"。 <br />对于第一种方法，有其局限性：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 需要修改程序配置，如去掉web.xml中关于安全filter的设置。需要维护多个版本的web.xml，如压力测试和功能测试分别各自的web.xml，增加了维护成本，而且有可能会在测试之后忘记将web.xml修改回来。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于一些需要用户安全信息的页面无能为力，如某些业务审计操作需要用户安全信息来记录。因为缺少这样的信息，注定了测试的失败。如果解决为了这个问题进一步的修改程序，那么因为存在多个版本的程序，那么其维护难度将大大增加。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 虽然，第二种方法配置难度增加了，但是它不用修改程序。而且还可将测试计划保存成文件，以便重复使用。因此，选用第二种方法是较为理想的做法。下面以一个简化的例子说明使用方法二的配置步骤。</p>
<p>1. 例子由以下几个文件组成：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AuthorizenFilter.java，过滤器负责检验session中是否存在用户信息。如果没有，那么就转向到 login.jsp。它的主要方法 doFilter 内容如下：</p>
<p>public void doFilter(ServletRequest request,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ServletResponse response,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FilterChain chain)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throws IOException, ServletException {<br />&nbsp;&nbsp;&nbsp; HttpServletRequest req = (HttpServletRequest)request;<br />&nbsp;&nbsp;&nbsp; HttpServletResponse res = (HttpServletResponse)response;<br />&nbsp;&nbsp;&nbsp; HttpSession session= req.getSession();<br />&nbsp;&nbsp;&nbsp; User user = (User)session.getAttribute("user");<br />&nbsp;&nbsp;&nbsp; if(null == user){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String uri= req.getRequestURI();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //如果请求页是登录页，不转向<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( uri.equalsIgnoreCase("/gWeb/login.jsp")){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chain.doFilter(request, response);<br />&nbsp;&nbsp;} else{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; res.sendRedirect("/gWeb/login.jsp");<br />&nbsp;&nbsp;}<br />&nbsp;}else{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chain.doFilter(request, response);<br />&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp; <br />}<br />&nbsp;</p>
<p><br />User.java，用户类负责记录用户的信息。为了简化，这里的登录操作只允许指定用户名和密码。主要内容如下：</p>
<p>public class User {<br />&nbsp;private String user;<br />&nbsp;private String pwd;<br />&nbsp;public User(String user, String pwd) {<br />&nbsp;&nbsp;this.user = user;<br />&nbsp;&nbsp;this.pwd = pwd;<br />&nbsp;}<br />&nbsp;public boolean login(){<br />&nbsp;&nbsp;return user.equals("foxgem") &amp;&amp; pwd.equals("12345678");<br />&nbsp;}<br />&nbsp;public String getUser() {<br />&nbsp;&nbsp;return user;<br />&nbsp;}<br />&nbsp;public void setUser(String user) {<br />&nbsp;&nbsp;this.user = user;<br />&nbsp;}<br />}<br />&nbsp;</p>
<p><br />Login.jsp 和welcome.jsp。其中 login.jsp 负责生成 User 对象，并调用 User 的login。当 login 返回为 true 时转向到 welcome.jsp。其验证部分的代码：</p>
<p>&lt;%<br />&nbsp; if( request.getParameter("Submit") != null) {<br />&nbsp;&nbsp; User ur= new User( request.getParameter("user"), request.getParameter("pwd"));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( ur.login()){<br />&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; session.setAttribute("user", ur);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.sendRedirect("/gWeb/welcome.jsp");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; session.setAttribute( "LOGIN_ERROR_MSG", <br />"无效的用户，可能原因：用户不存在或被禁用。");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; response.sendRedirect("/gWeb/index.jsp");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp; }<br />%&gt;<br />&nbsp;</p>
<p><br />web.xml，配置 filter 拦截所有访问 JSP 页面的请求：</p>
<p>&lt;filter&gt;<br />&nbsp;&nbsp;&nbsp; &lt;filter-name&gt;authorizen&lt;/filter-name&gt;<br />&nbsp;&nbsp;&nbsp; &lt;filter-class&gt;org.foxgem.jmeter.AuthorizenFilter&lt;/filter-class&gt;<br />&lt;/filter&gt;<br />&lt;filter-mapping&gt;<br />&nbsp;&nbsp;&nbsp; &lt;filter-name&gt;authorizen&lt;/filter-name&gt;<br />&nbsp;&lt;url-pattern&gt;*.jsp&lt;/url-pattern&gt;<br />&lt;/filter-mapping&gt;</p>
<p>2. 创建如下结构的<a href="javascript:;" onclick="tagshow(event, 'Web%B2%E2%CA%D4');" target="_self"><span style="text-decoration: underline;"><strong>Web测试</strong></span></a>计划：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.51testing.com/batch.download.php?aid=4976" target="_blank"><img src="http://www.51testing.com/attachments/2007/07/2_200707161516521.jpg" border="0" alt="" /></a></p>
<p>其中主要<a href="javascript:;" onclick="tagshow(event, '%B2%E2%CA%D4');" target="_self"><span style="text-decoration: underline;"><strong>测试</strong></span></a>元件说明如下：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http请求默认值负责记录请求的默认值，如服务器、协议、端口等。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 第一个http请求，请求login.jsp，并附加验证所需要的参数（user=foxgem，pwd=12345678，Submit=Submit）；其包含的响应断言验证url中包含"welcome.jsp"，这一点可以从程序中反应。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 第二个http请求，请求是welcome.jsp；其包含的响应断言验证响应文本中包含"foxgem"，它是welcome.jsp页面逻辑的一部分。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http cookie管理器负责管理整个测试过程中使用的cookie，它不需要设置任何属性。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 循环控制器设置发送第二个请求的循环次数，表格监听器负责收集和显示第二个请求的测试结果。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 启动测试计划之后，执行的顺序是：首先，第一个请求登录页进行登录；成功登录之后，使用循环控制器执行第二个请求。请求welcome.jsp时，响应断言用来验证是否确实是welocme.jsp来处理请求，而不是因为其它页。在这个测试计划中需要注意的是http cookie管理器。正是由于它的作用，使得第二个请求能顺利的发送到welcome.jsp进行处理，而不是因为缺少用户安全信息转发到login.jsp。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在这个例子中，我们并没有在程序中使用cookie（使用的是session），那么http cookie管理器怎么会起作用呢？这是因为在servlet/jsp规范中对于session的状态跟踪有2种方式：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用cookie，保留和传递sessionid。它不要求程序对于url有什么特殊的处理，但是要求浏览器允许cookie。在这个例子中，就是这种情形。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用url重写，每次显式的在浏览器和服务器之间传递sessionid。它要求程序对url进行编码，对浏览器没有要求。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于第二种情形，可以使用<a href="javascript:;" onclick="tagshow(event, 'JMeter');" target="_self"><span style="text-decoration: underline;"><strong>JMeter</strong></span></a>前置管理器中的http url重写修饰符来完成。对于Tomcat，Session参数是jsessionid，路径扩展使用"；"。使用url编码时需要注意，必须将浏览器的cookie功能关闭。因为url编码函数，如encodeURL，会判断是否需要将sessionid编码到url中。当浏览器允许cookie时，就不会进行编码。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果cookie而不是session来保存用户安全信息，那么直接使用http cookie管理器就行了。此时，需要将使用的cookie参数和值直接写到管理器中，由它负责管理。对于其它的cookie使用，也是如此操作。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 登录问题解决之后，对于 Web 服务器的测试就没什么难点了。剩下的就是根据实际需要，灵活运用相关的测试组件搭建编写的测试计划。（当然，对于安全问题还有其它的使用情景。在使用时需要明确：JMeter 是否支持，如果支持使用哪种测试组件解决。）</p>
<p><a href="javascript:;" onclick="tagshow(event, '%CA%FD%BE%DD%BF%E2');" target="_self"><span style="text-decoration: underline;"><strong>数据库</strong></span></a>服务器</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 数据库服务器在大多数企业项目中是不可缺少的，对于它进行<a href="javascript:;" onclick="tagshow(event, '%D1%B9%C1%A6%B2%E2%CA%D4');" target="_self"><span style="text-decoration: underline;"><strong>压力测试</strong></span></a>是为了找出：数据库对象是否可以有效地承受来自多个用户的访问。这些对象主要是：索引、触发器、存储过程和锁。通过对于<a href="javascript:;" onclick="tagshow(event, 'SQL');" target="_self"><span style="text-decoration: underline;"><strong>SQL</strong></span></a>语句和存储过程的测试，JMeter 可以间接的反应数据库对象是否需要优化。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JMeter 使用 JDBC 发送请求，完成对于数据库的测试。一个数据库测试计划，建立如下结构即可：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.51testing.com/batch.download.php?aid=4977" target="_blank"><img src="http://www.51testing.com/attachments/2007/07/2_200707161516571.jpg" border="0" alt="" /></a></p>
<p><br />其中：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JDBC连接配置，负责配置数据库连接相关的信息。如：数据库url、数据库驱动类名、用户名和密码等等。在这些配置中，"绑定到池的变量名"（Variable Name Bound to Pool）是一个非常重要的属性，这个属性会在JDBC请求中被引用。通过它， JDBC请求和JDBC连接配置建立关联。（测试前，请将所需要的数据库驱动放到JMeter的classpath中）。 <br />JDBC请求，负责发送请求进行测试。 <br />图形结果，收集显示测试结果。 <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在实际的项目中，至少有2种类型的JDBC请求需要关注：select语句和存储过程。前者反应了select语句是否高效，以及表的索引等是否需要优化；后者则是反应存储过程的算法是否高效。它们如果效率低下，必然会带来响应上的不尽如人意。对于这两种请求，JDBC请求的配置略有区别：</p>
<p>Select语句</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.51testing.com/batch.download.php?aid=4978" target="_blank"><img src="http://www.51testing.com/attachments/2007/07/2_200707161518191.jpg" border="0" alt="" /></a></p>
<p>存储过程</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="http://www.51testing.com/batch.download.php?aid=4979" target="_blank"><img src="http://www.51testing.com/attachments/2007/07/2_200707161519271.jpg" border="0" alt="" /></a></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果对于<a href="javascript:;" onclick="tagshow(event, 'Oracle');" target="_self"><span style="text-decoration: underline;"><strong>Oracle</strong></span></a>，如果测试的是函数，那么也可以使用select语句来进行配置，此时可以使用：select 函数(入参) from dual形式的语句来测试，其中dual是oracle的关键字，表示哑表。对于其它厂商的数据库产品，请查找手册。</p>
<p>JMS服务器</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MOM 作为消息数据交换的平台，也是影响应用执行效率的潜在环节。在 <a href="javascript:;" onclick="tagshow(event, 'Java');" target="_self"><span style="text-decoration: underline;"><strong>Java</strong></span></a> 程序中，是通过 JMS 与 MOM 进行交互的。作为 Java 实现的压力测试工具，JMeter 也能使用 JMS 对应用的消息交换和相关的数据处理能力进行测试。这一点应该不难理解，因为在整个测试过程中，JMeter 测试的重点应该是消息的产生者和消费者的本身能力，而不是 MOM本身。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 根据 JMS 规范，消息交换有2种方式：发布/订阅和点对点。JMeter针对这两种情形，分别提供了不同的Sampler进行支持。以下MOM我们使用ActiveMQ 3.2.1，分别描述这两种消息交换方式是如何使用 JMeter 进行测试。</p>
<p>1. 测试前的准备（两种情况都适用）</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JMeter 虽然能使用 JMS 对 MOM 进行测试，但是它本身并没有提供JMS需要使用的包。因此，在测试之前需要将这些包复制到 %JMETER_HOME%/lib 下。对于 ActiveMQ 来说，就是复制 %ACTIVEMQ_HOME%/lib。%ACTIVEMQ_HOME%/optional 是可选包，可根据实际情况来考虑是否复制。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JMeter 在测试时使用了 JNDI，为了提供 JNDI 提供者的信息，需要提供 jndi.properties。同时需要将 jndi.properties 放到 JMeter 的 classpath 中，建议将它与 bin下的 ApacheJMeter.jar 打包在一起。对于 ActiveMQ，jndi.properties 的示例内容如下：</p>
<p><br />java.naming.factory.initial = org.activemq.jndi.ActiveMQInitialContextFactory<br />java.naming.provider.url = tcp://localhost:61616</p>
<p>#指定connectionFactory的jndi名字，多个名字之间可以逗号分隔。<br />#以下为例：<br />#对于topic，使用(TopicConnectionFactory)context.lookup("connectionFactry")<br />#对于queue，(QueueConnectionFactory)context.lookup("connectionFactory")<br />connectionFactoryNames = connectionFactory</p>
<p>#注册queue，格式：<br />#queue.[jndiName] = [physicalName]<br />#使用时：(Queue)context.lookup("jndiName")，此处是MyQueue<br />queue.MyQueue = example.MyQueue</p>
<p>#注册topic，格式：<br /># topic.[jndiName] = [physicalName]<br />#使用时：(Topic)context.lookup("jndiName")，此处是MyTopic<br />topic.MyTopic = example.MyTopic<br />&nbsp;</p>
<p><br />2. 发布/订阅</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在实际测试时，发布者和订阅者并不是需要同时出现的。例如，有时我们可能想测试单位时间内消息发布者的消息产生量，此时就不需要消息发布者，只需要订阅者就可以了。本例为了说明这两种Sampler的使用，因此建立如下的测试计划：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.51testing.com/batch.download.php?aid=4980" target="_blank"><img src="http://www.51testing.com/attachments/2007/07/2_200707161521191.jpg" border="0" alt="" /></a></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其中JMS Publisher和JMS Subscriber的属性：选择"使用jndi.properties"，连接工厂是connectionFactory，主题是MyTopic，其它使用默认配置。对于JMS Publisher，还需提供测试用的文本消息。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 启动ActiveMQ，运行测试计划。如果配置正确，那么与ActiveMQ成功连接之后，在JMeter的后台会打印出相关信息。在测试过程中，JMeter 后台打印可能会出现java.lang.InterruptedException 信息，这个是正常现象，不会影响测试过程和结果。这一点可以从 bin 下的 jmeter.log 看出。</p>
<p>3. 点对点</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于点对点，JMeter只提供了一种Sampler：JMS Point-to-Point。在例子中，建立如下图的测试计划：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.51testing.com/batch.download.php?aid=4981" target="_blank"><img src="http://www.51testing.com/attachments/2007/07/2_200707161522241.jpg" border="0" alt="" style="width: 500px;" /></a></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其中：Communication style是Request Only。对于另一种风格：Request Response，会验证收到消息的JMS Header中的JMSCorrelationID，以判断是否是对请求消息的响应。</p>
<p>结论</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 本文介绍了如何使用JMeter完成最常用的三种类型服务器的压力测试，这三种类型的压力测试涵盖了很大一部分的使用情形，然而需要记住的是工具毕竟是工具。效果好不好，关键还是在于使用的人。而且，对于压力测试，测试计划的好坏是关键。针对不同的情况，分析后有针对的进行测试，比起拿枪乱打、无的放矢显然要高效得多。</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/207364#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 24 Jun 2008 10:47:26 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/207364</link>
        <guid>http://jxh118.javaeye.com/blog/207364</guid>
      </item>
      <item>
        <title>spring和quartz进行定时邮件发送</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/206884" style="color:red;">http://jxh118.javaeye.com/blog/206884</a>&nbsp;
          发表时间: 2008年06月22日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>一 &nbsp;发送邮件的基类:</p>
<pre name="code" class="java">public abstract class BaseMailSender {
 protected String to; 

 protected String from; 

 protected String subject; 

 protected MailSender sender;
 //当发送的邮件为简单邮件时用MailSender即可;
 
 //protected JavaMailSender sender;
 //当发送的邮件为MIMI时用JavaMailSender
 public void setTo(String to) {
  this.to = to;
 } 

 public void setFrom(String from) {
  this.from = from;
 } 

 public void setSubject(String subject) {
  this.subject = subject;
 } 

 public void setJavaMailSender (MailSender sender) {//(JavaMailSender sender) {
  this.sender = sender;
 }
}</pre>
<p>&nbsp;</p>
<p>发送邮件调用的类:</p>
<pre name="code" class="java">public class SimpleHtmlMailSender extends BaseMailSender{ 

 public void sendMessage() throws MessagingException {
  String textStr = "Helo!!!!!!!!!!!";//发送邮件的消息主体  
  SimpleMailMessage msg = new SimpleMailMessage();  
  msg.setTo(to);
  msg.setFrom(from);
  msg.setSubject(subject);
  msg.setText(textStr);  
  sender.send(msg);
 } 

 public void doIt() throws Exception { 

  ClassPathXmlApplicationContext ctx1 = new ClassPathXmlApplicationContext(
    new String[] { "ApplicationContext.xml" });
  SimpleHtmlMailSender sender = (SimpleHtmlMailSender) ctx1.getBean("messageSender");
  
  sender.sendMessage();
  System.out.println("发送成功");
 }
}</pre>
<p>&nbsp;配置文件ApplicationContext:</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt; 

&lt;!DOCTYPE beans public "-//SPRING//DTD BEAN//EN" " http://www.springframework.org/dtd/spring-beans.dtd "&gt;
&lt;beans&gt;
 &lt;bean id="sender" class="org.springframework.mail.javamail.JavaMailSenderImpl"&gt;
  &lt;property name="host"&gt;
   &lt;value&gt;smtp.163.com&lt;/value&gt;
  &lt;/property&gt;
   &lt;property name="username"&gt;
    &lt;value&gt;jxh3023&lt;/value&gt;
  &lt;/property&gt;
  &lt;property name="password"&gt;
   &lt;value&gt;password&lt;/value&gt;
  &lt;/property&gt;
         &lt;property name="javaMailProperties"&gt;
    &lt;props&gt;
     &lt;prop key="mail.smtp.auth"&gt;true&lt;/prop&gt;
    &lt;/props&gt;
         &lt;/property&gt;
  &lt;/bean&gt;
  &lt;bean id="messageSender" class="com.spring.mail.SimpleHtmlMailSender"&gt;
  &lt;property name="javaMailSender"&gt;
   &lt;ref bean="sender"/&gt;
    &lt;/property&gt;
    &lt;property name="to"&gt;
   &lt;value&gt;hu_wei118@126.com&lt;/value&gt;
    &lt;/property&gt;
    &lt;property name="from"&gt;
    &lt;value&gt;jxh3023@163.com&lt;/value&gt;
    &lt;/property&gt;
  &lt;property name="subject"&gt;
   &lt;value&gt;test&lt;/value&gt;
    &lt;/property&gt;
  &lt;/bean&gt; 


 &lt;bean id="methodInvokingJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"&gt;       
  
  
  &lt;!-- &lt;property name="jobClass"&gt;
   &lt;value&gt;com.spring.mail.SimpleHtmlMailSender&lt;/value&gt;
  &lt;/property&gt;--&gt; 

  &lt;property name="targetObject"&gt;&lt;ref bean="messageSender"/&gt;&lt;/property&gt;        
  &lt;property name="targetMethod"&gt;&lt;value&gt;doIt&lt;/value&gt;&lt;/property&gt; 

 &lt;/bean&gt; 

 &lt;bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"&gt;
  &lt;property name="jobDetail"&gt;&lt;ref bean="methodInvokingJobDetail"/&gt;&lt;/property&gt;
  &lt;property name="startDelay"&gt;
   &lt;!-- 3 seconds --&gt;
   &lt;value&gt;3000&lt;/value&gt;
  &lt;/property&gt;
  &lt;property name="repeatInterval"&gt;
   &lt;!-- repeat every 6 seconds --&gt;
   &lt;value&gt;6000&lt;/value&gt;
  &lt;/property&gt;
 &lt;/bean&gt; 


 &lt;bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"&gt;
  &lt;property name="jobDetail"&gt; &lt;ref bean="methodInvokingJobDetail"/&gt;&lt;/property&gt;
  &lt;property name="cronExpression"&gt; &lt;value&gt;0 0 6,12,20 * * ?&lt;/value&gt;  &lt;/property&gt;  
 &lt;/bean&gt; 

 &lt;bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"&gt; 
  &lt;property name="triggers"&gt;
    &lt;list&gt;
    &lt;ref local="simpleTrigger"/&gt;
   &lt;/list&gt; 
  &lt;/property&gt;   
 &lt;/bean&gt;
&lt;/beans&gt; </pre>
<p>&nbsp;二 &nbsp;mime邮件发送 &nbsp;<br /><br /><br />在编写发送邮件程序时需要用到的类有一下几个:<br /><br />&nbsp;org.springframework.mail.javamail.JavaMailSender;<br />&nbsp;//此类继承了spring里面的<br />&nbsp;//MailSender接口--定义了两个简单的sender方法,用于发送简单的邮件信息,参数类型为:SimpleMailMessage([])<br />&nbsp;//JavaMailSender类继承了上面方法后支持MIMI邮件,可以发MIMI消息<br />&nbsp;org.springframework.context.support.ClassPathXmlApplicationContext;<br />&nbsp;//spring里面读取配置文件时需要用到的类<br />&nbsp;org.springframework.mail.javamail.MimeMessageHelper;<br />&nbsp;//在发送MIMI消息时需要用到的辅助类 &nbsp;<br /><br />&nbsp;javax.mail.MessagingException; &nbsp;<br />&nbsp;javax.mail.internet.MimeMessage;<br />&nbsp;//J2EE里面支持发送MIME信息需要的类<br /><br />例子:</p>
<p>发送邮件的基类</p>
<pre name="code" class="java">public abstract class BaseMailSender {
 protected String to;
 protected String from;
 protected String subject;
 protected JavaMailSender sender;//当发送MIMI消息时用到的类

 public void setTo(String to) {
  this.to = to;
 }
 public void setFrom(String from) {
  this.from = from;
 }
 public void setSubject(String subject) {
  this.subject = subject;
 }
 public void setJavaMailSender(JavaMailSender sender) {
  this.sender = sender;
 }
}</pre>
<p>&nbsp;<br /><br />发送邮件的类:</p>
<pre name="code" class="java">public class SimpleHtmlMailSender extends BaseMailSender{

 public void sendMessage() throws MessagingException {
  
  //String htmlHead = "&lt;html&gt;&lt;head&gt;&lt;meta http-equiv=\"Content-Type\" content=\"text/html; charset=gb2312\"&gt;&lt;/head&gt;&lt;body&gt;";
  //String htmlBody = "";
  //String htmlEnd = "";
 // StringBuffer mailMessage = new StringBuffer();
  //mailMessage.append(htmlHead);
  //mailMessage.append(htmlBody);
  //mailMessage.append(htmlEnd);
  
  
  //当发送的邮件为简单邮件时注销此句  
  String textStr = "&lt;html&gt;&lt;head&gt;&lt;meta http-equiv=\"Content-Type\" content=\"text/html; charset=gb2312\"&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;&lt;a href='#'&gt;^_^!&lt;/a&gt;&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;";

  //String textStr = "Helo could you see this email?";//发送邮件的消息主体
  
  //SimpleMailMessage msg = new SimpleMailMessage();
  
  //当发送邮件为简单邮件时把此句注销即可
  
  MimeMessage msg = sender.createMimeMessage();
  MimeMessageHelper helper = new MimeMessageHelper(msg, true, "UTF-8");
  
  helper.setTo(to);
  helper.setFrom(from);
  helper.setSubject(subject);
  helper.setText(textStr, true);
  /*
  msg.setTo(to);
  msg.setFrom(from);
  msg.setSubject(subject);
  msg.setText(textStr);
  */
  sender.send(msg);
 }

 public void doIt() throws Exception{//static void main(String[] args) throws Exception {//
  //ApplicationContext ctx = new FileSystemXmlApplicationContext(new String[] { "springexample-creditaccount.xml" });

  ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] { "ApplicationContext.xml" });

  SimpleHtmlMailSender sender = (SimpleHtmlMailSender) ctx
    .getBean("messageSender");
  sender.sendMessage();
  System.out.println("发送成功");
 }
}</pre>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/206884#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 22 Jun 2008 18:45:31 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/206884</link>
        <guid>http://jxh118.javaeye.com/blog/206884</guid>
      </item>
      <item>
        <title>Spring的JMSTemplate的使用</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/206702" style="color:red;">http://jxh118.javaeye.com/blog/206702</a>&nbsp;
          发表时间: 2008年06月21日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>1:在web.xml文件中配置一个spring用的上下文:</p>
<pre name="code" class="xml">&lt;context-param&gt;
  &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
  &lt;param-value&gt;
   /WEB-INF/jmsconfig.xml
  &lt;/param-value&gt;
 &lt;/context-param&gt;
</pre>
<p>&nbsp;</p>
<p>jmsconfig.xml用来装配jms,内容如下:</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd"&gt;
&lt;beans&gt;
  &lt;bean id="jmsConnectionFactory"
    class="org.springframework.jndi.JndiObjectFactoryBean"&gt;
    &lt;property name="jndiName"&gt;
      &lt;value&gt;jms/Ntelagent/RequestQCF&lt;/value&gt;
    &lt;/property&gt;
  &lt;/bean&gt;
  &lt;bean id="destination"
    class="org.springframework.jndi.JndiObjectFactoryBean"&gt;
    &lt;property name="jndiName"&gt;
      &lt;value&gt;jms/Ntelagent/RequestQ&lt;/value&gt;
    &lt;/property&gt;
  &lt;/bean&gt;  
  
   &lt;bean id="jmsConnectionFactoryForReceive"
    class="org.springframework.jndi.JndiObjectFactoryBean"&gt;
    &lt;property name="jndiName"&gt;
      &lt;value&gt;jms/Ntelagent/ResponseQCF&lt;/value&gt;
    &lt;/property&gt;
  &lt;/bean&gt; 
  &lt;bean id="destinationForReceive"
    class="org.springframework.jndi.JndiObjectFactoryBean"&gt;
    &lt;property name="jndiName"&gt;
      &lt;value&gt;jms/Ntelagent/ResponseQ&lt;/value&gt;
    &lt;/property&gt;
  &lt;/bean&gt;


  &lt;bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate102"&gt;
    &lt;property name="connectionFactory"&gt;
      &lt;ref bean="jmsConnectionFactory"/&gt;
    &lt;/property&gt;
    &lt;property name="defaultDestination"&gt;
      &lt;ref bean="destination"/&gt;
    &lt;/property&gt;
    &lt;property name="messageConverter"&gt;
      &lt;ref bean="jmsTrxConverter"/&gt;
    &lt;/property&gt;
    &lt;property name="receiveTimeout"&gt;
     &lt;value&gt;1&lt;/value&gt;
    &lt;/property&gt;
  &lt;/bean&gt;
  
    &lt;bean id="jmsTemplateForReceive" class="org.springframework.jms.core.JmsTemplate102"&gt;
    &lt;property name="connectionFactory"&gt;
      &lt;ref bean="jmsConnectionFactoryForReceive"/&gt;
    &lt;/property&gt;
    &lt;property name="defaultDestination"&gt;
      &lt;ref bean="destinationForReceive"/&gt;
    &lt;/property&gt;
    &lt;property name="messageConverter"&gt;
      &lt;ref bean="jmsTrxConverter"/&gt;
    &lt;/property&gt;
    &lt;property name="receiveTimeout"&gt;
     &lt;value&gt;1&lt;/value&gt;
    &lt;/property&gt;
  &lt;/bean&gt;
  
  &lt;bean id="jmsTrxConverter" class="co.transport.jms.JmsTransactionConverter"&gt;
     &lt;property name="rspQueue"&gt;
      &lt;ref bean="destinationForReceive"/&gt;
    &lt;/property&gt;  
  &lt;/bean&gt;
  
  &lt;bean id="jmsRequestTransport" class="co.transport.jms.JmsRequestTransport"&gt;
    &lt;property name="jmsTemplate"&gt;
      &lt;ref bean="jmsTemplate"/&gt;
    &lt;/property&gt;
    &lt;property name="jmsTemplateForReceive"&gt;
      &lt;ref bean="jmsTemplateForReceive"/&gt;
    &lt;/property&gt;    
  &lt;/bean&gt;

&lt;/beans&gt;

</pre>
<p>&nbsp;</p>
<p>其中:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jmsConnectionFactory为jms连接工厂,属性jndiName的value为server服务配置的jndi名称.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; destination为消息队列,属性jndiName为消息队列的jndi名称.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jmsTemplate为配置spring消息模版:采用JmsTemplate102(如果你的JMS实现符合JMS规范1.1版，则应该使用JmsTemplate)，其中的messageConverter属性配置了一个消息转换器，因为通常消息在发送前和接收后都需要进行一个前置和后置处理，转换器便进行这个工作。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 由于我的sendQueue很receiveRueue是不同的queue，所以我配置了两个jmsTemplate：&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; jmsTemplateForReceive负责接收消息,jmsTemplate负责发发送消息.</p>
<p><br />发送消息的代码很简单:<br />jmsTemplate.convertAndSend(request);<br />接收消息也很简单:<br />Object obj = jmsTemplate.receiveAndConvert();&nbsp;<br />如果需要用一个过滤器接收特定的消息,则:<br />Object obj = this.jmsTemplateForReceive.receiveSelectedAndConvert(this.messageSelector);</p>
<p>发送和接收消息的class如下:</p>
<p>&nbsp;</p>
<pre name="code" class="java">public class JmsRequestTransport implements RequestTransport {
 private JmsTemplate jmsTemplate;
 
 private JmsTemplate jmsTemplateForReceive;
 
 private String messageSelector;
 
 private String destinationName; 
 
 public void setJmsTemplate(JmsTemplate template){
  this.jmsTemplate = template;
 }

 public void request(Object request) {
  jmsTemplate.convertAndSend(request);
 }
 
 public Object receive() { 
  System.out.println("in JmsRequestTransport: destinationName = " + this.jmsTemplateForReceive.getDefaultDestinationName());
  System.out.println("in JmsRequestTransport: messageSelector = " + messageSelector);
  Object obj = this.jmsTemplateForReceive.receiveSelectedAndConvert(this.messageSelector);  
  return obj;  
 }

 public String getMessageSelector() {
  return messageSelector;
 }

 public void setMessageSelector(String string) {
  messageSelector = string;
 }

 public String getDestinationName() {
  return destinationName;
 }

 public void setDestinationName(String string) {
  destinationName = string;
 }

 public JmsTemplate getJmsTemplateForReceive() {
  return jmsTemplateForReceive;
 }

 public void setJmsTemplateForReceive(JmsTemplate template) {
  jmsTemplateForReceive = template;
 }

}

</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>要实现一个消息转换器,只需要实现MessageConverter接口,MessageConverter很简单,它只有两个方法需要实现:<br />public Object fromMessage(Message msg){}<br />public Message toMessage(Object obj, Session session) throws JMSException{}<br />fromMessage为接收消息后,对消息进行的转换(通常是把一个message转化为一个Object对象)<br />toMessage为发送消息前需要的转化(通常为把一个Object转化为一个message对象)<br />我的JmsTransactionConverter转换器如下:</p>
<pre name="code" class="java">public class JmsTransactionConverter implements MessageConverter {
 
 private Queue rspQueue; 

 public JmsTransactionConverter(){}
  
 public Object fromMessage(Message msg){
  MessageBean msgBean = new MessageBean();
  TextMessage massage = (TextMessage)msg;

  try {
   String str = massage.getText();   
   msgBean.setHead("HeadTest");
   msgBean.setOutput(str);
   msgBean.setStatus("success");
   
  } catch (JMSException e) {
   // TODO Auto-generated catch block
   msgBean = null;
   e.printStackTrace();
  }
  finally {
   return msgBean;
  }
   
 }
 
 public Message toMessage(Object obj, Session session) throws JMSException{

  String s = (String)obj;

  TextMessage message = session.createTextMessage(s);  
  message.setStringProperty("mytype","java");  
  return message;
 }

 public Queue getRspQueue() {
  return rspQueue;
 }

 public void setRspQueue(Queue queue) {
  rspQueue = queue;
 }

}

其中:MessageBean是一个简单javabean:
public class MessageBean {
 
 private String head;
 
 private String output;
 
 private String status;

 public String getHead() {
  return head;
 }

 public String getOutput() {
  return output;
 }

 public String getStatus() {
  return status;
 }


 public void setHead(String string) {
  head = string;
 }

 public void setOutput(String string) {
  output = string;
 }

 public void setStatus(String string) {
  status = string;
 }

}

然后就可以用两个servlet来测试发送和接收消息:
发送消息:
public class JMSTestServlet extends HttpServlet {

 /**
 * @see javax.servlet.http.HttpServlet#void (javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
 */
 
 public void doPost(HttpServletRequest req, HttpServletResponse resp)
  throws ServletException, IOException
 {
  doGet(req, resp);
 }
 
 public void doGet(HttpServletRequest req, HttpServletResponse resp)
  throws ServletException, IOException{
  ServletOutputStream out = resp.getOutputStream();
  try {
   ServletContext servletContext = this.getServletContext();
   WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
   JmsRequestTransport transport = (JmsRequestTransport)wac.getBean("jmsRequestTransport");
   System.out.println("in JMSTestServlet.doGet:begin send message");
   String messageToSend = req.getParameter("message");
   if(messageToSend == null)
   {
    messageToSend = "Default Message";
   }
   transport.request(messageToSend);
   String s = "in JMSTestServlet.doGet:after send message and message is: " + messageToSend;
   
   out.println("&lt;HTML&gt;&lt;BODY&gt;");
   out.println(s);
   out.println("&lt;br&gt;&lt;input type=button name=back value=back  onclick=history.back()&gt;");
   out.println("&lt;br&gt;&lt;a href=receiveServlet&gt;receive&lt;/a&gt;");
   out.println("&lt;/HTML&gt;&lt;/BODY&gt;");
 
  } catch(Exception e) {
   out.println("&lt;HTML&gt;&lt;BODY&gt;");
   out.println(e.toString());
   out.println("&lt;/HTML&gt;&lt;/BODY&gt;");  
  }
 }

接收消息:
public class receiveServlet extends HttpServlet {

 /**
 * @see javax.servlet.http.HttpServlet#void (javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
 */
 public void doPost(HttpServletRequest req, HttpServletResponse resp)
  throws ServletException, IOException
 {
  doGet(req, resp);
 } 

 public void doGet(HttpServletRequest req, HttpServletResponse resp)
  throws ServletException, IOException {
  ServletOutputStream out = resp.getOutputStream();
  String selector = req.getParameter("selector");
  String distination = req.getParameter("distination");
  try {   
   String s = "nothing";
   ServletContext servletContext = this.getServletContext();
   WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
   JmsRequestTransport transport = (JmsRequestTransport)wac.getBean("jmsRequestTransport");

   System.out.println("selector = " + selector);
   System.out.println("distination = " + distination);
   
   transport.setDestinationName(distination);
   transport.setMessageSelector(selector);
   
   Object obj = transport.receive();
   if(obj != null) {
    MessageBean msgBean = (MessageBean)obj;
    //System.out.println("in receiveServlet:");
    s = "receive the message is: " + msgBean.getHead() + "," +
     msgBean.getOutput() + ", " +
     msgBean.getStatus();    
   } else {
    s = "there is no message where " + selector;
   }
  
   out.println("&lt;HTML&gt;&lt;BODY&gt;");
   out.println(s);
   out.println("&lt;br&gt;selector is :" + selector);
   out.println("&lt;br&gt;distination is :" + distination);
   out.println("&lt;br&gt;&lt;a href=jsp/sendMessage.jsp&gt;sendMessage&lt;/a&gt;");
   out.println("&lt;/HTML&gt;&lt;/BODY&gt;");
   
  } catch(Exception e) {
   out.println("&lt;HTML&gt;&lt;BODY&gt;");
   out.println("&lt;br&gt;selector is :" + selector);
   out.println("&lt;br&gt;distination is :" + distination);
   out.println("&lt;br&gt;" + e.toString());
   out.println("&lt;/HTML&gt;&lt;/BODY&gt;");  
   
  }
 

 }

}



</pre>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/206702#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sat, 21 Jun 2008 18:48:37 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/206702</link>
        <guid>http://jxh118.javaeye.com/blog/206702</guid>
      </item>
      <item>
        <title>如何让你的网站也支持OpenID(转载)</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/206439" style="color:red;">http://jxh118.javaeye.com/blog/206439</a>&nbsp;
          发表时间: 2008年06月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>这是一个非常详细的教程，它将指导那些已经拥有了用户的网站，如何来一步一步实现对OpenID的支持。它将解释如何让新用户使用OpenID的URL来注册帐号，并让原有用户轻松绑定他们的OpenID。这个教程看起来有些长，因为我的目的是希望你不用做任何思考，在看完文章的时候就可以轻松搞定这些事情。<br /><br /><span style="font-weight: bold;">概况<br /><br /></span>首先我假定你的网站已经具备了以下的条件：<br />1.一个用户信息数据库；</p>
<ul>
<li>每个用户有唯一的内部ID，用户通过他们的帐号（e-mail）和密码登陆。</li>
</ul>
<p>2.完善的注册新用户和收集基本信息的流程；<br />3.有验证用户的登陆页面；</p>
<ul>
<li>通过用户的帐号（e-mail)和密码验证用户身份，并在网站其他地方依赖用户ID作为用户身份的象征。</li>
</ul>
<p>4.有专门的页面用来管理账户信息。<br /><br />如果你的网站不像以上所说的，你首先应该将它改造成这样，虽然某些细节可能有些出入。<br /><br /><span style="font-weight: bold;">下面是需要给你的网站增加的大体内容：</span><br />1.一个存储OpenID和用户的内部ID关系的数据表；</p>
<ul>
<li>它们之间是多对一的关系（每个用户可以绑定多个OpenID，但是一个OpenID只能属于一个用户）。 </li>
<li>这个表将用来通过OpenID查询所有的用户，因此必须是单独的一个数据表。</li>
</ul>
<p>2.需要在注册界面上增加一些有关OpenID的内容；</p>
<ul>
<li>新用户将输入他们的OpenID，通过其对应的提供者来验证，并返回到你的注册页面，选择性提供他们想在你的网站上共享的资料。</li>
</ul>
<p>3.对现有用户，也需要在登陆页面增加一点内容；</p>
<ul>
<li>已经绑定OpenID的用户可以直接输入OpenID，通过对应提供者验证用户身份，并返回到你的网站的验证页面，并通过OpenID和用户ID的对应关系找到对应的帐号，就像他们输入了帐号（e-mail）和密码一样。</li>
</ul>
<p>4.一个OpenID的设置界面，需要提供对绑定到用户帐号的OpenID的浏览、添加、删除的功能。<br /><br /><span style="font-weight: bold;">下面是你将创建的简要内容：</span><br />1.一个OpenID数据表</p>
<ul>
<li>数据列：(openid_url,user_id)。openid_url是字符串，user_id是你当前用户标识身份的ID。 </li>
<li>把openid_url设为主键（唯一，用来通过OpenID查找用户） </li>
<li>创建user_id的索引（用来对指定帐号列举其绑定的OpenID）</li>
</ul>
<p>2.对用户输入的OpenID进行查询并重定向到OpenID提供者的动态页面</p>
<ul>
<li>检查用户输入的OpenID已经被站内用户绑定过了 </li>
<li>重定向到OpenID提供者（使用OpenID库），这样用户才能被授权（详情稍候讲解）</li>
</ul>
<p>3.处理OpenID提供的应答的动态页面。</p>
<ul>
<li>需要核实应答（使用OpenID库） </li>
<li>对于新用户，你需要按照注册流程先预先填充从OpenID提供者返回的注册信息，并且需要绕过注册流程，使他们不需要提供密码（因为他们以后将通过OpenID直接登陆）。 </li>
<li>对于已经注册的用户，你需要绑定已经被验证过的OpenID到他们的账户上（如果是他们是第一次使用它）。</li>
</ul>
<p>4、用来管理用户绑定的OpenID的动态页面</p>
<ul>
<li>能获取和浏览所有绑定到当前登陆帐号的所有OpenID。 </li>
<li>能让用户绑定新的OpenID。 </li>
<li>能让用户删除已经绑定过的OpenID。</li>
</ul>
<p>5.在删除用户的代码中增加删除该用户绑定过的OpenID的功能。<br /><br /><span style="font-weight: bold;">在正式开始之前你可能需要下面的资源：</span><br />1.选择一种程序语言的OpenID用户库，<a href="http://openid.net/wiki/index.php/Libraries">点击</a>查看各种语言的用户库。<br />2.放置在你的网站界面的标准OpenID图形。比如<a href="http://www.plaxo.com/images/openid/login-bg.gif">小图标</a>和logo（<a href="http://www.plaxo.com/images/openid/openid-logo-small.png">小</a>、<a href="http://www.plaxo.com/images/openid/openid-logo.png">正常</a>、<a href="http://www.plaxo.com/images/openid/openid-logo.pdf">PDF</a>）。<br />3.利用一些OpenID提供者来进行测试。比如<a href="http://www.myopenid.com/">MyOpenID.com</a>、<a href="http://www.livejournal.com/">LiveJournal</a>、和<a href="http://www.claimid.com/">ClaimID</a>。另外，如果你有AOL/AIM的帐号，你可以用http://openid.aol.com/USERNAME作为一个OpenID（USERNAME代表你的帐号）。<br /><br /><span style="font-weight: bold;">实现OpenID用户支持能立刻享受以下这些好处：</span><br />1.1.2亿使用OpenID的用户将更容易、迅速地注册到你的网站，因为他们不再需要创建并记住心得帐号和密码。<br />2.当你开始收集你用户的OpenID时，你网站的用户将能更容易地享受到很多基于OpenID的服务。<br />3.通过支持这个举足轻重的在线身份的开放标准，你将证明你的想法和对会员的承诺。<br /><br /><span style="font-weight: bold;">另外，随着对OpenID支持的不断增多，你将可能享受到更多的好处：</span><br />1.自动链接到会员在别的网站的帐号，从而共享信息给Mashup，避免用户在不同的地方多次输入重复的信息。<br />2.当会员在别的网站更改信息时，你能更轻松地获取到他们的最新动态。<br />3.接受别的网站或服务提供的可信服务，比如年龄确认，e-mail<a href="http://jxh118.javaeye.com/articles/tag/认证" class="bodytag" target="_blank"><em>认证</em></a>，群组会员资格验证等等</p>
<p>实现细节<br />1.安装一个OpenID用户库</p>
<ul>
<li>目前已经有很多流行编程语言的OpenID库，它们替你解决了绝大部分的负担。 </li>
<li>在库的基础上，你需要建立和提供者网站之间的联系，实质上，就是建立把服务和处理器处理成连接字符串。你至少需要把这种联系存储在session中，用来验证一个OpenID的授权，但比较完美的情况还是你长期存储他们，这样就不用每次重定向到OpenID提供者时都重新建立他们之间的联系。你可以用 memcached、数据库或者其他你能访问到的存储介质。</li>
</ul>
<p>2.创建一个OpenID数据表</p>
<ul>
<li>你可以使用下面的schema： </li>
<li>create table user_openids ( </li>
<li>&nbsp; openid_url varchar(255) not null, </li>
<li>&nbsp; primary key (openid_url), </li>
<li>&nbsp; user_id int not null,</li>
</ul>
<ul>
<li>&nbsp; index (user_id) </li>
<li>);</li>
</ul>
<p>&nbsp;</p>
<ul>
<li>保持一个单独的全局表，这样你就能通过OpenID找到所有的用户（即使你的用户信息分布在多个数据库中） </li>
<li>存取OpenID URL在一个规范化映射表，可以使查找更加健壮（也就是说如果用户下次输入的OpenID有一些细微的差别，你同样可以映射到他的账户）。大部分 OpenID库将提供纠错的功能，不过你还是应该在缺少http://时毫不犹豫地加上去，另外，应该把协议和域都变成小写（但不包括剩下的部分）。比如 "WWW.AOL.Com/myOpenID"，应该变成http://www.aol.com/myOpenID。你应该把URL后面多余的/也去掉。 </li>
<li>如果你经常使用数据访问层的代码，你应该把下面的函数提供给程序（在每个函数下边我都提供了对应的sql语句）。还要提醒一下，所有用OpenID作为参数的函数都需要提前纠错一下。</li>
</ul>
<p><br />&nbsp;&nbsp;&nbsp; * GetUserId(openid_url)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select user_id from user_openids where openid_url = openid_url<br />&nbsp;&nbsp;&nbsp; * GetOpenIDsByUser(user_id)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select openid_url from user_openids where user_id = user_id<br />&nbsp;&nbsp;&nbsp; * AttachOpenID(openid_url, user_id)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insert into user_openids values (openid_url, user_id)<br />&nbsp;&nbsp;&nbsp; * DetachOpenID(openid_url, user_id)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete from user_openids where openid_url = openid_url and user_id = user_id<br />&nbsp;&nbsp;&nbsp; * DetachOpenIDsByUser(user_id)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete from user_openids where user_id = user_id<br />2.添加OpenID的内容到注册页面</p>
<ul>
<li>添加内容使得OpenID用户可以通过OpenID登陆。这部分UI的目标是是OpenID用户很容易识别你的网站支持OpenID，但非OpenID用户又不会因此感到迷惑。你可以添加一个OpenID输入框或者一个到可以输入OpenID的页面的链接。 </li>
<li>无论你放到哪，你都必须使输入框符合OpenID的命名规范：</li>
</ul>
<ul>
<li>使用&ldquo;openid_url&rdquo;作为文本框的id和name属性（这将能使插件更加容易识别和处理来自不同网站的OpenID的输入框）。 </li>
<li>添加<a href="http://www.myopenid.com/static/openid-icon-small.gif"><span style="color: #2e6ab1;">小OpenID logo</span></a>作为输入框的北京，使用类似的css样式：<br />
<pre>background: #FFFFFF url('/images/openid-icon-small.gif') no-repeat scroll 0pt 50%;
padding-left: 18px;</pre>
</li>
<li>如果提供对OpenID的介绍以及在你的网站如何使用它的简短说明，将会是个不错的注意，这样能吸引那些好奇的人过来一探究竟。 </li>
<li>将输入框放入到一个form中，设置form的action为处理OpenID的页面，接下来我们就着手建立这样的页面。 </li>
<li>在提供了OpenID并在OpenID提供者登陆以后，用户将返回到注册页面，这里有两个需要注意的地方。第一，你应该显示该用户注册的OpenID，把小OpenID logo放在后面会更好。第二，你不应该再次要求用户输入在你的网站的密码，因为他们将通过OpenID直接登陆。所以需要吧密码框隐藏起来，并确保你的代码能允许这样（如果你的程序一定要求要有密码的话，你可以生成一些随机字符串，只不过不让用户看到而已）。请注意：也可以在帐户设置页面让用户输入一个你网站的密码，不过需要注意使用OpenID的最大好处是用户不用再维护他们在各个网站不同的注册信息。</li>
</ul>
<p>3.添加OpenID相关内容到登陆页面</p>
<ul>
<li>添加OpenID用户登陆的内容到登陆页面。这对已经注册并绑定了OpenID的用户和用OpenID注册的新用户都是有用的。想注册页面一样，UI的设计目标是需要保持非OpenID用户和OpenID用户之间的平衡。你应该使OpenID输入框的命名和样式和注册页面保持一致。包围输入框的form也应该只想你将创建的动态页面。 </li>
<li>在网站其他任何有传统登陆方式的地方，你都应该增加OpenID的登陆方式。</li>
</ul>
<p>4.创建一个OpenID登陆的动态页面</p>
<ul>
<li>必须提供两个基本的访问参数给动态页面</li>
</ul>
<ul style="margin-left: 40px;">
<li>openid_url:用户指定的OpenID，包括注册、登陆、绑定等。 </li>
<li>action_type:用户要采用的操作类型。这个参数的只有可能是login、complete、attach、list和delete。（如果你用Rails或者类似的体系，这些可能是一个控制器方法或者url本身的一部分）</li>
</ul>
<ul>
<li>实现login动作（这是在注册和登陆流程里边都要提交到的地方）</li>
</ul>
<ol>
<li>用上文提到的GetUserId函数获取openid_url。 </li>
<li>如果OpenID已经被你网站的用户绑定过了，检查这个用户是不是已经在你的网站登陆了。</li>
</ol><ol>
<li>如果这个用户还没有登陆，则用户是试图用这个帐号登陆，所以需要准备重定向到对应的OpenID提供者，但是需要设置一个标记表明不是用来注册。 </li>
<li>如果用户已经登陆了，而且这个OpenID属于用户（也就是说，这个OpenID的URL已经关联到这个登陆用户的user_id），你就不需要做任何事情（这个用户已经登陆而且已经绑定了这个OpenID，所以这是相当于没操作），这是一个特殊情况。 </li>
<li>如果这个用户已经登陆了，但OpenID属于另一个用户，则提示这个账户已经被另外的用户绑定过了。你同事可以提供注销和再试一次的选项。这是另一种特殊情况。</li>
</ol>
<p>如果这个OpenID还没有存在你的数据库中，那么这个用户是尝试用它注册新帐户，把页面重定向到OpenID提供者并请求注册信息即可。保存openid_url在session，因为当OpenID提供者返回时有可能并不返回这个值给你。如果你的网站不支持session，你可以使用数据库，但最重要的是这个保存的地方不能收到用户的干预（比如cookie或者可以被用户改变或&ldquo;杜撰&rdquo;内容的地方）。（需要你保存请求的OpenID是因为OpenID允许用户使用其他网站的代理。举例来说，如果我视图用OpenID josephsmarr.com来登陆，我可能会用选择另一个OpenID jsmarr.myopenid.com作为代理，当提供者验证完毕返回时，你需要记住我是想用josephsmarr.com登陆而不是jsmarr.myopenid.com。幸运的是，大多数OpenID库已经处理这个问题了，但是你还是需要保存原始请求的OpenID在session里边。这种问题有可能在OpenID2.0中得以解决。）用户验证通过后的返回地址参数return_to需要由这个动态页面提供，这个页面将用来处理complete动作。如果你判断用户准备注册一个新帐户，就需要确定他提供哪些额外的注册信息。大部分的OpenID提供者都支持这些简单注册内容，这些信息是一些通用的内容，包括姓名、email、昵称、性别、生日、邮政编码、国家、语言以及时区，你可以随取所需，从而减少注册流程的时间以及一些额外的负担。如果你的OpenID用户库并不支持对简单注册参数的请求，可以看看库是不是提供对扩展的支持，即使是最坏的情况，你也可以在重定向之前添加请求参数到转发地址。调用OpenID库中checkid_setup函数产生OpenID提供者的地址。需要传递给OpenID提供者用户名和第5步创建的返回地址，另外，还有你想获取的简单注册信息的相关参数。你可能需要获取和处理这个函数的某些错误，但如果假定一切OK的话，你将得到需要重定向的地址。通过发出服务器端的重定向应答，让动态页面重定向到上面获取的地址。用户将会被重定向到OpenID提供者的网站，他们需要登陆（除非已经登陆过了），然后需要决定是否新人你的网站，如果有注册信息请求的话，也需要决定共享哪些信息给你。如果他们完成了这些过程，OpenID提供者将会把页面转到你提供的return_to所指向的URL地址，然后你将启用你的complete动作完成整个处理流程了。实现complete动作（当用户已经登陆OpenID提供者的网站并返回的时候） </p>
<ol>
<li>当OpenID提供者返回到你的return_to地址时，他们会在地址上添加一些用来验证用户授权信息的请求参数。通过你使用的OpenID库，你可能需要收集这些信息为指定的数据结构，用来通过验证函数，也有可能它自己就帮你做好这些事情了。 </li>
<li>从session中获取用户最初请求的OpenID。 </li>
<li>调用OpenID库中的id_res来验证你发给OpenID提供者的授权数据。传给用户请求的原始OpenID，以及一些必要的请求参数。这个函数将检查是不是一切都有效。如果它返回错误信息，给用户提示适当的一些信息；否则你就可以正式确认这个用户的合法性了。 </li>
<li>在验证成功后，你也可以在你的网站给这个OpenID设置一个永久的cookie，下次这个用户进入网站的时候就可以在OpenID输入框预先填充好这个OpenID了。如果这样做的话，需要确保当用户注销时清除掉这个cookie。 </li>
<li>用GetUserId函数重新获取验证过的OpenID。如果你不能从数据库中获取，检查用户是不是这会已经登陆了。如果是，执行attach动作绑定OpenID到用户已经存在的帐号。否则，是时候启动注册进程创建一个使用改OpenID的新帐户了。保存OpenID到session，这样账户创建的代码将能确保这个用户已经验证过这个OpenID了。（不要使用存取请求过的OpenID的session变量了，因为用户可以输入任何内容给那个变量）然后将用户重定向到注册流程，并填充你获取的简单注册信息（如果有的话），因此你需要想办法把返回的这些注册信息和你网站平常使用的注册信息关联起来。</li>
</ol>
<ul>
<li>像上面所描述的一样，注册页面应该非常显著地显示OpenID，并且不要在此要求用户为你的网站输入密码，因为他们已经使用OpenID登陆了。另外，你应该把从OpenID提供者获取的简单注册信息预先填充好。你可以继续要求额外的一些注册信息并且保持你网站当前对信息的要求，比如哪些必填，哪些可选填。（使用OpenID将加快注册速度，但并不是要求你你改变对注册信息的要求直至影响网站的正常行为）最后，你应该提供给已经注册用户绑定OpenId的链接。 </li>
<li>当用户完成注册流程，创建了一个新用户帐号，将可以使用AttachOpenID函数来绑定OpenID到新帐号上了。如果你的用户表和OpenID表分布在不同的数据库，不能同步进行访问，可能会导致绑定命令失败，从而导致鼓励帐号存在。这种情况不可能完美地组织，但也是非常罕见地，况且，用户可以重新登陆，你大可以忽视这种情况。</li>
</ul>
<p>如果你已被验证的OpenID绑定到了一个已经存在的帐号上，通过传统的方法你也可以正常地登陆网站。如果用户正好用别的帐号登陆，注销掉，以OpenID绑定的帐号登陆。</p>
<ul>
<li>实现attach方法（帮助已经注册的用户绑定新的OpenID到账户上） </li>
</ul>
<div style="margin-left: 80px;">这个方法可以看作是complete方法的一部分，因为它发生在用户已经登陆而且已经验证过了一个新的OpenID，所以在调用此方法时需要确保这个用户已经登陆了。<br /></div>
<div style="margin-left: 40px;"><ol>
<li>用AttachOpenID函数绑定已经验证过的OpenID到登陆的用户账户。 </li>
<li>提示用户这个OpenID已经版定成功，今后可以它直接登陆。接下来可以考虑调用list方法，这样用户就能从列表中看到这个OpenID了。 </li>
</ol></div>
<ul>
<li>实现list方法（用来展示用户账户上绑定的所有OpenID） </li>
</ul>
<ol>
<li>需要用户登陆。 </li>
<li>用GetOpenIDsByUser函数获取绑定的OpenID列表。 </li>
<li>对应每个绑定的OpenID提供链接用来解除绑定，用户点击后将传入<a href="http://jxh118.javaeye.com/articles/tag/openid" class="bodytag" target="_blank"><span style="color: #335533;">openid</span></a>_url参数执行delete的动作。 </li>
<li>提供增加OpenID的输入框或链接。这将把用户带入到登陆和绑定流程，最后将返回这个列表页面。 </li>
<li>如果你的网站已经有一般设置页面，你应该提供类似&ldquo;管理你的OpenID&rdquo;的链接到列表页面。 </li>
</ol>
<p>实现delete方法（用来从用户账户中解除一个OpenID）</p>
<ol>
<li>需要用户已经登陆。 </li>
<li>可选：检查这个OpenID是不是用户最后一个在你的网站的身份凭证。如果用户没有设置你网站的密码而且这是他唯一绑定到他账户的OpenID，删除这个OpenID将会使用户完全脱离这个账户。如果没有好的办法是用户能恢复他的帐号，而且用户试图删除这个最后的身份凭证，那么就显示一条错误信息吧&mdash;&mdash;&ldquo;你不能删除你帐号上的最后一个OpenID，这样你将没办法再登陆本站。你可以先绑定另一个OpenID或者创建在本站的密码&rdquo;。 </li>
<li>传递<a href="http://jxh118.javaeye.com/articles/tag/openid" class="bodytag" target="_blank"><span style="color: #335533;">openid</span></a>_url参数给DetachOpenID函数，正常的话，就可以解除对它的绑定了。如果提供的OpenID并未绑定到用户的账户，你可以提示错误或者无视它即可。 </li>
<li>显示确认信息，表明这个OpenID已经被解除绑定，不能在你的网站继续使用。告诉用户如果他想再次绑定这个OpenID，则需要通过正常的验证过程重新绑定。然后可以考虑重新定向到OpenID列表页面，用户就能看到列表的更新情况。 </li>
</ol>
<p>如果要删除一个用户帐号，就需要删除这个帐号绑定的所有OpenID。</p>
<ol>
<li>如果你的网站支持用户删除账户，删除该账户绑定的任何OpenID也相当重要，因为这样，这些OpenID才有机会绑定到其他账户上。你可以在删除用户账户的时候调用DetachOpenIDsByUser函数来实现这个操作。 </li>
</ol>
<p><span style="font-weight: bold;">呵呵，你终于做完了!</span><br />如果你现在做完这些了，那么恭喜你了，你现在已经拥有让你的网站实现对OpenID支持的十八般武艺。上面的步骤已经非常彻底和完善，但是如果在实现的时候遵循下面的规则，将对你更加有帮助：</p>
<ul>
<li>无论何时将OpenID显示在页面上，把OpenID的小图标显示在前面，使其意义非常明显。 </li>
<li>一般来说，在你将OpenID公之于众的时候，需要征求用户的同意。也许有得用户愿意把它作为一个公用的身份，但你最后还是假定他们只是愿意作为一个私有的象征。 </li>
</ul>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/206439#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 20 Jun 2008 18:53:28 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/206439</link>
        <guid>http://jxh118.javaeye.com/blog/206439</guid>
      </item>
      <item>
        <title>Ajax4jsf 和 &lt;h:message&gt; 一起使用时候的问题</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/206397" style="color:red;">http://jxh118.javaeye.com/blog/206397</a>&nbsp;
          发表时间: 2008年06月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-size: small;">&nbsp;如果在页面中使用了&lt;a4j:commandButton&gt;来提交表单，并且使用&lt;h:message for="componentid"&gt;来显示一些组件的报错信息，比如：</span></p>
<p><span style="font-size: small; color: #993300;">&lt;h:inputSecret id="password" value="#{userBean.user.password}" size="11" required="true"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&lt;f:validateLength minimum="6" maximum="30" /&gt;<br />&lt;/h:inputSecret&gt;</span></p>
<p><span style="font-size: small; color: #0000ff;">&lt;h:message for="password" showSummary="true" showDetail="false" /&gt;</span></p>
<p><span style="font-size: small; color: #993300;">&lt;a4j:commandButton value="#{msg['common.create']}" action="#{userBean.saveUser}"/&gt;</span></p>
<p><span style="font-size: small;">&nbsp;&nbsp;&nbsp; 如果输入密码不够六位，应该会在页面中显示报错信息，但是实际上却没有，只是在控制台打印出来了这样的消息：</span></p>
<p><span style="font-size: small; color: #ff0000;">WARNING: FacesMessage(s) have been enqueued, but may not have been displayed.</span></p>
<p><span style="font-size: small;">&nbsp;&nbsp;&nbsp; 实际上在这种情况下，只需要用&lt;a4j:outputPanel&gt;来和&lt;h:message&gt;配合使用即可解决问题，如下所示：</span></p>
<p><span style="font-size: small; color: #0000ff;">&lt;a4j:outputPanel ajaxRendered="true"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&lt;h:message for="password" showSummary="true" showDetail="false" /&gt;<br />&lt;/a4j:outputPanel&gt;</span></p>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/206397#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 20 Jun 2008 17:24:14 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/206397</link>
        <guid>http://jxh118.javaeye.com/blog/206397</guid>
      </item>
      <item>
        <title>使用JavaMail发邮件</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/206366" style="color:red;">http://jxh118.javaeye.com/blog/206366</a>&nbsp;
          发表时间: 2008年06月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <pre name="code" class="java">public class EmailUtil {
	public static void sendEmail(String smtpHost, String senderAddress, String senderName,
					String receiverAddress, String sub, String content) throws Exception {
		List&lt;String&gt; recipients = new ArrayList&lt;String&gt;();
		recipients.add(receiverAddress);

		sendEmail(smtpHost, senderAddress, senderName, recipients, sub, content);
	}

	public static void sendEmail(String smtpHost, String senderAddress, String senderName,
					List&lt;String&gt; recipients, String sub, String content) throws Exception {
		if (smtpHost == null) {
			String errMsg = "Could not send email: smtp host address is null";
			
			throw new Exception(errMsg);
		}

		try {
			Properties props = System.getProperties();
			props.put("mail.smtp.host", smtpHost);
			props.put("mail.smtp.port", "25");
		    props.put("mail.smtp.auth", "true");

			Session session = Session.getDefaultInstance(props,null );
			MimeMessage message = new MimeMessage( session );
			message.setSubject(sub,"UTF-8");			
			message.setFrom(new InternetAddress(senderAddress, senderName));
			for (String recipient : recipients) {
				message.addRecipients(Message.RecipientType.TO, recipient);
			}		
			
			Multipart mp = new MimeMultipart("related");    
	        MimeBodyPart mbp = new MimeBodyPart();    
	   
	        // 设定邮件内容的类型为 text/html    
	        mbp.setContent(content, "text/html;charset=UTF-8");    
	        mp.addBodyPart(mbp);    
	        
	     // 新建一个存放附件的BodyPart
	        mbp = new MimeBodyPart(); 
	        byte[]  bytes=new byte[]{};
	        FileInputStream  fis=new FileInputStream("F:/shu.jpg");
	        DataHandler dh = new DataHandler(new ByteArrayDataSource(fis,"application/octet-stream"));
	        mbp.setDataHandler(dh); 
	        // 加上这句将作为附件发送,否则将作为信件的文本内容
	        mbp.setFileName("1.jpg"); 
	        mbp.setHeader("Content-ID", "IMG1" );
	        // 将含有附件的BodyPart加入到MimeMultipart对象中
	        mp.addBodyPart(mbp); 
	        message.setContent(mp);  
			message.setSentDate( new Date() );		
			message.saveChanges();
			 Transport   transport=session.getTransport("smtp");   
			  transport.connect(smtpHost,"hu_wei118","password");   
			  transport.sendMessage(message,message.getAllRecipients());
			  transport.close();
		 } catch (Exception e) {
				throw new Exception("errorMsg", e);
		 }
	}
}
</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>所需包：activation.jar&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mail.jar</p>
<p>给一个人发邮件</p>
<p>&nbsp;</p>
<p>web应用中调用EmailUtil.sendEmail("smtp.163.com", "<a href="mailto:hu_wei118@163.com">hu_wei118@163.com</a>", "XX",<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"<a href="mailto:hu_wei118@126.com">hu_wei118@126.com</a>", "Test", "你好!");</p>
<p>给N个人发邮件</p>
<p>web应用中调用</p>
<p>List&lt;String&gt;&nbsp;&nbsp;&nbsp; list=new ArrayList&lt;String&gt;();</p>
<p>list.add(<a href="mailto:hu_wei118@126.com">hu_wei118@126.com</a>);</p>
<p>list.add(<a href="mailto:xxxx@xxx.com">xxxx@xxx.com</a>);</p>
<p>list.add(<a href="mailto:xxxxx@xxxx.cn">xxxxx@xxxx.cn</a>);</p>
<p>EmailUtil.sendEmail("smtp.163.com", "<a href="mailto:hu_wei118@163.com">hu_wei118@163.com</a>", "XX", list,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Test", "你好!");</p>
          <br/>
          <span style="color:red;">
            <a href="http://jxh118.javaeye.com/blog/206366#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 20 Jun 2008 16:45:21 +0800</pubDate>
        <link>http://jxh118.javaeye.com/blog/206366</link>
        <guid>http://jxh118.javaeye.com/blog/206366</guid>
      </item>
      <item>
        <title>获取实时股票数据</title>
        <author>jxh118</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxh118.javaeye.com">jxh118</a>&nbsp;
          链接：<a href="http://jxh118.javaeye.com/blog/205961" style="color:red;">http://jxh118.javaeye.com/blog/205961</a>&nbsp;
          发表时间: 2008年06月19日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Verdana;">
<p><span style="font-size: small; font-family: Verdana;">股票数据的获取目前有如下两种方法可以获取:</span></p>
<p><span style="font-size: small; font-family: Verdana;">1.http/javascript接口取数据<br />2.web-service接口</span></p>
</span></p>
<p><span style="font-size: small;">&nbsp;</span></p>
<p><span style="font-size: small; font-family: Verdana;">1.http/javascript接口取数据</span></p>
<p><span style="font-size: small; font-family: Verdana;">1.1Sina股票数据接口<br />以大秦铁路（股票代码：601006）为例，如果要获取它的最新行情，只需访问新浪的股票数据<br />接口：http://hq.sinajs.cn/list=sh601006这个url会返回一串文本，例如：</span></p>
<p><span style="font-size: small; font-family: Verdana;">var hq_str_sh601006="大秦铁路, 27.55, 27.25, 26.91, 27.55, 26.20, 26.91, 26.92, <br />22114263, 589824680, 4695, 26.91, 57590, 26.90, 14700, 26.89, 14300,<br />&nbsp;26.88, 15100, 26.87, 3100, 26.92, 8900, 26.93, 14230, 26.94, 25150, 26.95, 15220, 26.96, 2008-01-11, 15:05:32";</span></p>
<p><span style="font-size: small; font-family: Verdana;">这个字符串由许多数据拼接在一起，不同含义的数据用逗号隔开了，按照程序员的思路，顺序号从0开始。<br />0：&rdquo;大秦铁路&rdquo;，股票名字；<br />1：&rdquo;27.55&Prime;，今日开盘价；<br />2：&rdquo;27.25&Prime;，昨日收盘价；<br />3：&rdquo;26.91&Prime;，当前价格；<br />4：&rdquo;27.55&Prime;，今日最高价；<br />5：&rdquo;26.20&Prime;，今日最低价；<br />6：&rdquo;26.91&Prime;，竞买价，即&ldquo;买一&rdquo;报价；<br />7：&rdquo;26.92&Prime;，竞卖价，即&ldquo;卖一&rdquo;报价；<br />8：&rdquo;22114263&Prime;，成交的股票数，由于股票交易以一百股为基本单位，所以在使用时，通常把该值除以一百；<br />9：&rdquo;589824680&Prime;，成交金额，单位为&ldquo;元&rdquo;，为了一目了然，通常以&ldquo;万元&rdquo;为成交金额的单位，所以通常把该值除以一万；<br />10：&rdquo;4695&Prime;，&ldquo;买一&rdquo;申请4695股，即47手；<br />11：&rdquo;26.91&Prime;，&ldquo;买一&rdquo;报价；<br />12：&rdquo;57590&Prime;，&ldquo;买二&rdquo;<br />13：&rdquo;26.90&Prime;，&ldquo;买二&rdquo;<br />14：&rdquo;14700&Prime;，&ldquo;买三&rdquo;<br />15：&rdquo;26.89&Prime;，&ldquo;买三&rdquo;<br />16：&rdquo;14300&Prime;，&ldquo;买四&rdquo;<br />17：&rdquo;26.88&Prime;，&ldquo;买四&rdquo;<br />18：&rdquo;15100&Prime;，&ldquo;买五&rdquo;<br />19：&rdquo;26.87&Prime;，&ldquo;买五&rdquo;<br />20：&rdquo;3100&Prime;，&ldquo;卖一&rdquo;申报3100股，即31手；<br />21：&rdquo;26.92&Prime;，&ldquo;卖一&rdquo;报价<br />(22, 23), (24, 25), (26,27), (28, 29)分别为&ldquo;卖二&rdquo;至&ldquo;卖四的情况&rdquo;<br />30：&rdquo;2008-01-11&Prime;，日期；<br /