當柳上原的風吹向天際的時候...真正的快樂來源于創造 |
Web Service定義了一套標準的調用過程:
WSDL的目的是告訴外界自己能提供什么樣的服務,有點類似于java的接口。 WSDLd的全稱是Web Service Description Language,是一種基于XML的關于Web服務的描述,主要目的在于將自己的Web服務的所有相關內容如提供服務的傳輸方式,服務方法接口,接口參數,服務路徑等,生成相應的完全的文檔,發布給使用者。使用者可以通過這個WSDL文檔,創建相應的SOAP請求消息,通過HTTP傳遞給Web服務提供者;Web服務提供者在完成請求服務后,將SOAP返回消息傳回給請求者,服務請求者再根據WSDL文檔將SOAP返回消息解析成程序能夠理解的內容。 SOAP:標準的傳輸協議 SOAP是標準化的消息協議,是客戶端送給服務器希望調用的類和方法的一種消息格式,也包括服務返回的消息格式。之所以有SOAP是因為只有大家都遵守一套消息格式的標準,相互之間才能明白對方想要干什么。 SOAP是Web Service的標準傳輸協議,是Simple Object Application Propotol的簡寫,是一種標準化的傳輸消息的XML格式。 SOAP請求消息將客戶端的服務請求消息發給服務器,比如想調用什么接口,以及接口的參數值等。SOAP答復消息是從服務器返回給客戶端的消息,如接口實現類調用后的返回值或是調用服務時的錯誤信息等。定義WSDL是很重要的,一旦WSDL定義好了,再根據WSDL的輸入變量和輸出變量的結構就可以知道SOAP的請求消息和響應消息的格式了。 UDDI:服務的公共網址 UDDI是Universal Description Discovery and Intergretion的縮寫,是一種創建注冊服務的規范,以便大家將自己的Web Service進行注冊發布供使用者查找。 當服務提供者想將自己的Web Service發布,以便外部能找到其服務時,那么服務提供這可以將自己的Web Service注冊到相應的UDDI商用注冊網站。 UDDI并非一個必須的Web Service組件,服務方完全可以不進行UDDI的注冊。因為WSDL文件中已經給出了Web Service的地址URI,外部可以通過它進行相應的Web Service調用。 以下是一個Web Service示例程序,主要參考了梁愛虎的《SOA 思想,技術與系統集成應用詳解》中的例子: 發布Web服務的類接口: package com.heyang;
/** * 生成序列號的接口 * @author: 何楊(heyang78@gmail.com) * @date: 2009-9-29-下午12:37:55 */ public interface ISerial{ /** * 傳入類型,返回序列號 * @param type * @return */ public String getSN(String type); } 發布web服務的類: package com.heyang;
import java.text.MessageFormat; /** * ISerial的實現類 * @author: 何楊(heyang78@gmail.com) * @date: 2009-9-29-下午12:40:05 */ public class SerialService implements ISerial{ private static int number; /** * 產生SN:CD-000001的序列號 * MessageFormat的用法可參考http://hi.baidu.com/gacmotor/blog/item/372b4a3a0b010de314cecb0b.html */ public String getSN(String type) { number++; Object[] arr=new Object[]{type,number}; String result=MessageFormat.format("SN:{0}-{1,number,000000}",arr); return result; } public static void main(String[] args){ SerialService s=new SerialService(); System.out.println(s.getSN("CD")); } } Web.xml: <?xml version="1.0" encoding="UTF-8"?>
<web-app > <servlet> <servlet-name>AxisServlet</servlet-name> <servlet-class> org.apache.axis.transport.http.AxisServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>AxisServlet</servlet-name> <url-pattern>/servlet/AxisServlet</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>AxisServlet</servlet-name> <url-pattern>*.jws</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>AxisServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> </web-app> wsdd文件server-config.wsdd <?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <handler type="java:org.apache.axis.handlers.http.URLMapper" name="URLMapper" /> <service name="fetchSerialNumber" provider="java:RPC"> <parameter name="className" value="com.heyang.SerialService" /> <parameter name="allowedMethods" value="getSN" /> </service> <transport name="http"> <requestFlow> <handler type="URLMapper" /> </requestFlow> </transport> </deployment> 測試類: package com.heyang.client;
import java.net.MalformedURLException; import java.rmi.RemoteException; import javax.xml.rpc.ServiceException; import org.apache.axis.client.Call; import org.apache.axis.client.Service; /** * WebServiceClientTest * @author: 何楊(heyang78@gmail.com) * @date: 2009-9-29-下午01:03:05 */ public class WebServiceClientTest { public static void main(String[] args) throws ServiceException, MalformedURLException, RemoteException { // 標識Web Service的具體路徑 /** * SerialNumber:發布到Tomcat上的war的名稱,采用工程名 * services:固定寫法,與Web.xml中設定對應 * fetchSerialNumber:server-config.wsdd中設定的service名 */ String endpoint = "http://localhost:8080/SerialNumber/services/fetchSerialNumber"; // 創建 Service實例 Service service = new Service(); // 通過Service實例創建Call的實例 Call call = (Call) service.createCall(); // 將Web Service的服務路徑加入到call實例之中. call.setTargetEndpointAddress(new java.net.URL(endpoint));// 為Call設置服務的位置 // 調用Web Service的方法 call.setOperationName("getSN"); // 調用Web Service,傳入參數 String retval = (String) call.invoke(new Object[] { "CD" }); System.out.println(retval); } } 輸出示例: SN:CD-000004
例程下載(使用Axis,注意Tomcat的lib目錄中要包括mail.jar和activation.jar): http://www.aygfsteel.com/Files/heyang/SerialNumber20090929130453.rar 使用說明: 使用Ant腳本將jar包打好,再部署到Tomcat中,可以用http://localhost:8080/SerialNumber/services來測試一下是否有輸出,有則表示部署成功,之后執行WebServiceClientTest。 主要參考資料: 梁愛虎著《SOA 思想,技術與系統集成應用詳解》
XPath的概念:
XPath是一種定位XML文檔的各個部分的語言,它還提供處理字符串,數字和布爾值的常用函數。XPath可以定位XML文檔的元素和屬性等部件。XPath提供的一星兒函數能夠定位返回字符串或檢測出字符串的長度,XPath還包括字符數據轉換成數字類型和布爾類型的函數。 XPath從它的路徑符號給出名稱,它使用的語法銅URL類似,通常稱XPath語句為XPath表達式。 XPath不是一種獨立技術,它用于其它的技術如XSLT中,用XPath可以檢索XML的元素和屬性,可以XPath看做是XML的SQL。 XPath語法: 1.定位路徑:他是一種選擇節點集合的XPath表達式類型,通常使用“/”隔開的定位階來構造定位路徑。如/games/game,上述定位路徑有兩個定位階。定位路徑有兩種類型:相對的和絕對的。相對的定位路徑是由“/”分開的一個或多個定位階的序列。絕對定位路徑由“/”和后面的相對定位路徑組成。 2.定位階:他是定位路徑的組成部分,由三部分組成: 1)一個軸:它規定了上下文節點和定位階所選節點之間的關系。 2)一個節點測試:它規定了定位階選擇的階段類型。 3)零或多個謂詞,它們使用了表達式進一步改進節點的選擇。 如XPath表達式"/games/game[@genre='rpg']",該表達式選擇了所有genre屬性值為rpg的位于games元素下的game元素。 3.節點測試:它通知XPath表達式運算的節點類型,節點測試通常都是源樹種元素名稱,如/games/game,該表達式有兩個節點測試:games和game。 4.軸說明符:它用于明確指出要處理的節點測試的關系,如果要選games元素的所有子元素,則代碼如下:child::games. 下面歸納了部分軸說明符 軸說明符 說明 child 節點的所有子節點 descendant 節點的所有后代節點 parent 節點的父節點 following-sibling 節點后面的兄弟節點 preceding-sibling 節點前面的兄弟節點 5.謂詞:它是另一種進一步縮小節點選擇范圍的方法。XPath使用方括號[]來指定謂詞,謂詞使用XPath提供的函數提供測試,它可以訪問屬性(使用@符號)。 如前面出現過的XPath表達式"/games/game[@genre='rpg']",方括號[]中的“@genre='rpg'”就是謂詞,該表達式用于測試位于games元素下的game元素的genre屬性值是否為rpg。 XPath函數: XPath函數使得用戶能夠處理字符串,數字和節點集合,下面列出了三個節點集合函數: number last() 返回最后位置 如count(games/game)返回games元素下的game元素數目 number position() 返回當前位置 如games/game[last()]返回games元素下的最后一個game元素 number count(node-set) 返回結合參數中節點數目 如/games/game[position()=1]將返回games元素下的首個game元素。 下面使用開源包dom4j對XML文檔進行XPath操作的例子: 將要處理的XML文檔如下: <?xml version="1.0" encoding="GBK"?>
<employees> <employee lable="tech"> <id>0001</id> <name>李白</name> <title>程序員</title> <skill>Java</skill> <age>18</age> </employee> <employee lable="tech"> <id>0002</id> <name>杜甫</name> <title>高級程序員</title> <skill>C#</skill> <age>28</age> </employee> <employee lable="tech"> <id>0003</id> <name>Andy</name> <title>項目經理</title> <skill>PPT</skill> <age>38</age> </employee> <employee lable="admin"> <id>0004</id> <name>Bill</name> <title>部門經理</title> <skill>Java</skill> <age>48</age> </employee> <employee lable="admin"> <id>0005</id> <name>Cindy</name> <title>總經理</title> <skill>C#</skill> <age>58</age> </employee> </employees> XPath示例程序: package com.heyang;
import java.util.Iterator; import java.util.List; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.XPath; import org.dom4j.io.SAXReader; /** * dom4j的XPath解析示例 * @author: 何楊(heyang78@gmail.com) * @date: 2009-9-27-上午09:59:52 */ public class XPathSample{ public static void main(String[] args) throws Exception{ SAXReader reader = new SAXReader(); Document doc = reader.read(XPathSample.class.getResource("/Employee.xml").getPath()); // 1.XPath選擇器,選擇根節點employees下所有屬性lable為tech的employee子節點 System.out.println("1.選擇根節點employees下所有屬性lable為tech的employee子節點"); XPath xpathSelector = DocumentHelper.createXPath("/employees/employee[@lable='tech']"); List results = xpathSelector.selectNodes(doc); for ( Iterator iter = results.iterator(); iter.hasNext(); ) { Element element = (Element) iter.next(); // 找到employee子節點的name子節點的文字打印 System.out.println(element.selectSingleNode("./name").getText()); } // 2.打印名稱為李白的employee節點的title System.out.println("2.打印名稱為李白的employee節點的title"); String title = doc.valueOf( "//employee[name='李白']/title" ); System.out.println(title); // 3.XPath選擇器,選擇所有屬性lable為admin的employee子節點的name子節點 System.out.println("3.選擇所有屬性lable為admin的employee子節點的name子節點"); XPath xpathSelector2 = DocumentHelper.createXPath("//employee[@lable='admin']/name"); List results2 = xpathSelector2.selectNodes(doc); for ( Iterator iter = results2.iterator(); iter.hasNext(); ) { Element element = (Element) iter.next(); System.out.println(element.getText()); } // 4.XPath選擇器,選擇所有年齡節點大于20的employee子節點的name子節點 System.out.println("4.選擇所有年齡節點大于20的employee子節點的name子節點"); XPath xpathSelector3 = DocumentHelper.createXPath("//employee[age>20]/name"); List results3 = xpathSelector3.selectNodes(doc); for ( Iterator iter = results3.iterator(); iter.hasNext(); ) { Element element = (Element) iter.next(); System.out.println(element.getText()); } } } 控制臺輸出: 1.選擇根節點employees下所有屬性lable為tech的employee子節點
李白 杜甫 Andy 2.打印名稱為李白的employee節點的title 程序員 3.選擇所有屬性lable為admin的employee子節點的name子節點 Bill Cindy 4.選擇所有年齡節點大于20的employee子節點的name子節點 杜甫 Andy Bill Cindy XPath相關參照文檔(網絡上搜索得到,不保證內容的完全正確性): 表達式 描述
節點名 選擇所有該名稱的節點集 / 選擇根節點 // 選擇當前節點下的所有節點 . 選擇當前節點 .. 選擇父節點 @ 選擇屬性 示例 表達式 描述 bookstore 選擇所有bookstore子節點 /bookstore 選擇根節點bookstore bookstore/book 在bookstore的子節點中選擇所有名為book的節點 //book 選擇xml文檔中所有名為book的節點 bookstore//book 選擇節點bookstore下的所有名為book為節點 //@lang 選擇所有名為lang的屬性 方括號[],用來更進一步定位選擇的元素 表達式 描述 /bookstore/book[1] 選擇根元素bookstore的book子元素中的第一個(注意: IE5以上瀏覽器中第一個元素是0) /bookstore/book[last()] 選擇根元素bookstore的book子元素中的最后一個 /bookstore/book[last()-1] 選擇根元素bookstore的book子元素中的最后第二個 /bookstore/book[position()<3] 選擇根元素bookstore的book子元素中的前兩個 //title[@lang] 選擇所有擁有屬性lang的titile元素 //title[@lang='eng'] 選擇所有屬性值lang為eng的title元素 /bookstore/book[price>35.00] 選擇根元素bookstore的book子元素中那些擁有price子元素且值大于35的 /bookstore/book[price>35.00]/title 選擇根元素bookstore的book子元素中那些擁有price子元素且值大于35的title子元素 選擇位置的節點 通配符 描述 * 匹配所有元素 @* 匹配所有屬性節點 node() 匹配任何類型的節點 示例 表達式 描述 /bookstore/* 選擇根元素bookstore的下的所有子元素 //* 選擇文檔中所有元素 //title[@*] 選擇所有擁有屬性的title元素 使用操作符“|”組合選擇符合多個path的表達式
在Spring的Bean裝配文件中配置組件是合適的,但類似于數據庫細節,隊列主題細節等應該分離出來,這時我們可以使用Spring的PropertyPlaceholderConfigurer從外部屬性文件中裝載一些配置信息,使用起來很簡單,請參考下例:
http://www.aygfsteel.com/Files/heyang/SpringProperties20090926133054.rar 注意加入必要的包:commons-logging-1.0.4.jar,log4j-1.2.14.jar,spring.jar 兩個配置的內容: person1.properties person1.id=001
person1.name=Andy person1.password=123456 person2.properties person2.id=002
person2.name=Bill person2.password=111111 Bean裝配文件的內容: <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>person1.properties</value> <value>person2.properties</value> </list> </property> </bean> <bean id="person1" class="com.heyang.Person" > <property name="id"> <value>${person1.id}</value> </property> <property name="name"> <value>${person1.name}</value> </property> <property name="password"> <value>${person1.password}</value> </property> </bean> <bean id="person2" class="com.heyang.Person" > <property name="id"> <value>${person2.id}</value> </property> <property name="name"> <value>${person2.name}</value> </property> <property name="password"> <value>${person2.password}</value> </property> </bean> </beans> Person類: public class Person{
private String name; private String id; private String password; public String toString(){ return "Person id="+id+" name="+name+" password="+password; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public static void main(String[] args){ ApplicationContext appContext = new ClassPathXmlApplicationContext("bean.xml"); Person person1=(Person)appContext.getBean("person1"); System.out.println(person1); Person person2=(Person)appContext.getBean("person2"); System.out.println(person2); } } 控制臺輸出: Person id=001 name=Andy password=123456
Person id=002 name=Bill password=111111
命名服務概述
命名服務(Naming Service)是可以將復雜數據對象或其引用關聯到已知名稱的機制.然后可以發布這些名稱,客戶可以使用這些名稱查詢與它們相關聯的數據對象.名稱與對象之間的關聯稱為綁定.命名服務通常與其它服務(如文件系統,目錄和數據庫等)集成以提供這種綁定.大家可以從現代圖書館的卡片目錄系統來理解命名服務. JNDI介紹(Java Naming and Directory Inteface,Java命名與目錄接口) JNDI是Java命名與目錄接口(Java Naming and Directory Inteface)的縮寫,有時也簡稱Java名錄服務,J2EE組件通過調用JNDI提供的查找(lookup)方法以定位對象。JNDI是專門為Java設計的,一個Java應用程序可以用JNDI檢索Java對象.JNDI還可以執行標準目錄操作,如關聯屬性和對象,并用對象的屬性搜索它們. JNDI名字是對象的友好名字,這些名字通過J2EE服務器提供的命名目錄服務綁定到各自的對象上。由于J2EE組件是通過JNDI編程接口訪問服務的,所以通常情況下把對象的友好名字稱之為JNDI名字。比如,mydatabase數據庫的JNDI名字為jdbc/mydatabase,一旦J2EE服務器啟動,系統自動從配置文件讀取相關信息,并將jdbc/mydatabase的JNDI數據庫名字添加到名字空間。 Sun公司對JNDI的定義為”一種對Java平臺的標準擴展,它為Java技術編寫的應用程序提供了對企業中多種命名和目錄服務的統一接口.作為Java Enterprise API集的一部分,JNDI使與異構企業命名和目錄服務的無縫連接提供了可能”. 連接工廠(Connection Factory) 連接工廠(Connection Factory)是用于產生鏈接對象,使得J2EE組件可以訪問資源的一種對象。比如,用于數據庫的連接工廠是javax.sql.Database對象,它產生java.sql.Connection對象。 JNDI和Weblogic Server Weblogic提供了在JNDI規范中規定的實現.這使Java客戶可以用標準JNDI調用連接到Weblogic Server.客戶可以在Weblogic命名空間中訪問Weblogic命名服務并使對象可用,還可以檢索它們. 如果希望訪問已經加載到Weblogic Server的JNDI樹中的對象的Java客戶一般要執行以下任務: 1.與服務器建立一個上下文 2.對JNDI樹進行查詢或者更新 取得上下文例程 這是在命名空間中訪問綁定對象的第一步.應用程序將獲得引導上下文稱為InitialContext.它是從InitialContext工廠獲得的.這個工廠使用幾個屬性標識上下文需要指向的Weblogic Server. Hashtable ht=new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); ht.put(Context.PROVIDER_URL, "t3://127.0.0.1:7001"); Context ctx=null; try { ctx=new InitialContext(ht); } catch (NamingException e) { e.printStackTrace(); System.out.println("不能得到上下文"); } 創建一個綁定例程 要在WeblogicJNDI樹中創建一個新的綁定,可使用Context.bind方法.這個方法以新綁定的名稱以及綁定到這個名稱號的對象為參數.注意這個對象必須是可序列化的,也就是說它必須實現java.io.Serializablejie接口(實現這個接口無需實現任何方法,它只是告訴JVM這個對象可以序列化). Hashtable ht=new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); ht.put(Context.PROVIDER_URL, "t3://127.0.0.1:7001"); Context ctx=null; try { ctx=new InitialContext(ht); String text="菩提本非樹,明鏡亦非臺,本來無一物,何處染塵埃."; ctx.bind("TEST", text); } catch (NamingException e) { e.printStackTrace(); System.out.println("不能得到上下文"); } 下面是在Weblogic中查看剛才創建的綁定內容圖示 一.點擊環境,服務器,點擊“查看JNDI樹鏈接” ![]() 二.可以看到已經綁定到JNDI樹中的對象 ![]() 刪除現有的綁定例程 使用Context.unbind方法可以從JNDI樹中刪除綁定,不能再從樹中訪問這些對象了. Hashtable ht=new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); ht.put(Context.PROVIDER_URL, "t3://127.0.0.1:7001"); Context ctx=null; try { ctx=new InitialContext(ht); ctx.unbind("TEST"); } catch (NamingException e) { e.printStackTrace(); System.out.println("不能得到上下文"); } 總結 JNDI為應用程序提供了標準統一的方式,連接和使用企業中存在的多個對象目錄的能力.WeblogiocServer提供了JNDI的實現,客戶機可以和它無縫連接.同一客戶機還可以用JNDI API連接到另一命名服務上.Weblogic Server大量利用JNDI樹完成其常規功能.如果應用程序使用EJB,那么這個EJB就發布在JNDI樹中,類似的其它對象如DataSource對象,事務對象都發布在JNDI樹上. 參考例程: WeblogicJNDI(注意:weblogic.jar請自行加入lib目錄) ![]() 消息服務 消息服務是以可靠的,異步的,松耦合的,語言無關的,平臺無關的以及通常是可配置的方式在分布式應用程序之間傳遞消息的軟件。消息服務通過封裝在發送者和接受者之間傳遞的消息,并提供位于分布式消息客戶之間的軟件層完成這一任務。消息服務還提供了讓消息客戶使用的接口,它隔離了底層消息服務實現。這樣的基礎結構還可以看成是事件通知類型的服務,其中消息是事件,在消息客戶間發送這些消息是一種事件通知機制。 理解消息傳遞 消息傳遞是一種在軟件組件或者應用之間進行通信的方法。一個消息傳遞系統是一個點對點的設施:一個消息傳遞客戶端可以發送消息到任何其它客戶端,或者接收來自它們的消息。每個客戶端連接到一個消息傳遞代理,消息傳遞代理提供了創建,發送,接受和閱讀消息的工具。 消息傳遞支持松耦合的分布式通信。組件發送消息到某個目的地,接收方可以從目的地獲取消息。但是,發送方和接收方在通信時并不需要同時可用。實際上,發送 方不需要了解任何有關接收方的內容,而接收方也不需要了解任何有關發送方的內容。發送方和接收方只需知道要使用什么樣的消息格式以及什么目的地。 消息傳遞和電子郵件不同,它是一種在人們之間或軟件和人們之間建立通信的方法,而消息傳遞則用于軟件應用或軟件組件之間的通信。 使用消息服務中間件的消息服務實現: 一些中間件軟件(Weblogic,Sonic MQ等)實現了異步接收由消息生產者生成的消息,并將它們路由給消息消費者的消息服務功能。消息客戶通過消息客戶接口,以透明的方式利用中央消息服務的服務。 通過致力于使用中間件中的中央管理的持久和冗余的機制,消息中間件提高了消息服務的可靠性和可用性。消息中間件允許消息客戶與消息服務中間件服務器之間的連接,還減輕了客戶管理到多個消息服務端點位置的連接的需要。 (補圖) Message-Oriented Middleware,MOM 面向消息的中間件和消息服務幾乎是同義詞,MOM就是消息服務的一種實現,盡管采取的是特定類型的MOM系統的標準方式。MOM API定義了分布式應用程如何使用底層MOM消息信道或者隊列彼此通信。 消息在應用程序之間通過MOM傳遞,不會使消息的發送者阻塞,也就是說,發送者可以發送一個消息并讓MOM保證它到達預定的接受者,而不用等待接受者的響應。 Java Message Service(JMS) 從J2EE1.3版本開始,JMS API開始成為該平臺的一部分,應用開發者可以通過JavaEE組件來使用消息傳遞。 當JMS API在1998年引入時,它最重要的用途是允許Java應用訪問現有的面向消息中間件系統,如來自IBM的MQSeries,后來JMS產品可以為企業應用提供一個完整的消息傳遞功能。 Java Message Service(JMS)是一個定義了消息客戶如何用標準方式與底層消息服務提供者通信的Java API。 JMS還提供了一個接口,底層消息服務提供者實現了這個接口,向客戶提供JMS服務。 JMS提供了點到點和發布-訂閱模型。在JMS規范中,這種消息模型也稱為消息域。點到點消息是通過實現消息隊列完成的,生產者在隊列中寫入消費者接收的消息;發布-訂閱模型是通過實現主題節點的層次結構完成的,其中生產者發布消息,消費者可以訂閱這些消息。 JMS提供了核心抽象消息API,點到點消息隊列模型API和發布-訂閱模型API都擴展這個API。 什么是JMS API Java消息服務API允許應用創建發送,接收和閱讀消息。它的特點有: 1.異步:JMS供應者在接受到消息時可以將它們傳遞到某個客戶端,客戶端不必為了接收這些消息而主動發出請求。 2.可靠:JMS API可以確保某個消息傳遞而且只傳遞一次。對于那些能夠忍受消息丟失或接受到重復消息的應用,也可以使用較低級別的可靠性。 何時使用JMS API 在以下情況下,企業應用供應者更可能選擇消息傳遞API而不是某種緊耦合API: 1.供應者希望組件不依賴有關其他組件接口的信息,這樣可以很容易替換組件。 2.供應者希望應用在不管所有組件是否已經就緒并且同時運行的情況下都能夠運行。 3.應用業務模型允許組件發送信息到其它組件并且在不必接收到立即響應的情況下繼續工作。 基本的JMS API概念 編寫基本的JMS客戶端應用必須了解以下概念: JMS API 體系結構 消息傳遞域 消息使用 JMS API體系結構 JMS應用由以下部分所組成: 1.JMS供應者(JMS Provider):它是一個實現了JMS接口并提供管理和控制功能的消息傳遞系統。 2.JMS客戶端(JMS Client):是以Java編程語言編寫,負責生成和是喲個消息的程序或組件。任何JavaEE應用組件都可以作為JMS客戶端。 3.消息(Message):是在JMS客戶端之間進行信息溝通的對象。 4.管理對象(Administrered Object)是預先配置好的JMS對象,它是由管理員針對客戶端的使用而創建的。JMS管理對象的兩種類型有目的地(Destination)和連接工廠(ConnectionFactory)。 ![]() 兩種消息傳遞域(模式) 點對點消息傳遞域:這種消息傳遞是建立在消息隊列,發送方和接收方的概念上的。每個消息都被尋址到某個特定的隊列,并且負責接收的客戶端從保存消息的隊列中提取這些消息。在消息被使用或過期之前,隊列會移植保存所有發送給它們的消息。它的特點有: 1.每個消息只有一個使用者(消費者),即一條消息只會被一個使用者(消費者)使用。 2.消息的發送方和接收方并沒有時間同步的依賴性。當發送方發送消息時,不管接受方是否在運行,后者都可以接收到所發送的消息。 3.接收方在成功處理完消息后會發出確認。 4.這種模式適合多個發送者對一個消費者的情況。 發布/訂閱消息傳遞域:這種消息傳遞方式中客戶端按照某個主題來處理消息,發送者和訂閱者通常是匿名的,而且可以動態的發布或訂閱內容層次。系統負責將來自某個主題的多個發布者的消息分發到該主題的多個訂閱者。主題只要負責將消息分發到當前訂閱者,便同時負責保存這些消息。它的特點有: 1.每個消息可以有多個使用者。 2.發布者和訂閱者具有時間同步依賴性。訂閱了某個主題的客戶端只有在客戶端創建了訂閱之后才能使用所發布的消息,而且訂閱者必須繼續保持活動狀態才能使用消息。 3.這種模式適合一個發送者對多個消費者的情況。 圖:兩種JMS類層次結構 ![]() 消息使用 消息傳遞產品本質上是異步的:在消息的生成和使用之間并沒有基本的時間同步依賴性,JMS規定消息可以按照以下方式之一來使用: 同步:訂閱者或接收方通過調用receive方法明確獲取來自目的地的消息。Receive方法在消息到來之前可以進入阻塞狀態,或者在消息未在指定時間范圍內到達的情況下進入超時狀態。 異步:客戶端可以向使用者注冊一個消息監聽器(message listener)。消息監聽器類似于事件監聽器,當消息到達目的地時,JMS供應者通過調用監聽器的onMessage方法來傳遞消息。 JMS API的重要接口 ConnectionFactory :連接工廠,用于創建到特定JMS服務提供者(如Weblogic,IBM MQ或是Sonic MQ)的消息服務的連接。可以用JNDI查詢到一個JMS服務提供者管理的初始ConnectionFactory對象的句柄。它提供了返回特定Connection對象實例的方法,可以使用ConnectionFactory.createConnection()得到Connection對象的句柄。 Connection :它封裝了一個從JMS 客戶端到JMS服務提供者(JMS Provider)實例的連接 。 Destination :消息的目的地 Session:JMS會話,它與Connection相關聯,便是創建消息的上下文。會話可以用于定義一個事務,在該事務的邊界中可以存在一組在十五中發送和接收的消息,因此,所有這些消息可包含在一個原子事務中。Session接口封裝了一個上下文,JMS消息是在這個上下文中創建和接收的。它還擴展了java.lang.Runnable接口,表明每一個會話運行在單個線程的上下文中,可以用Connection.createSession()方法創建Session對象的句柄,傳遞給方法的Boolean參數表明會話是否是事務性的。附加在會話上的監聽器也可用于得到更多異步回調行為。MessageListener實現了一個onmessage()方法,對于所有由這個Session對象收到的消息,它取Message作為參數,Session對象的setMessageListener和getMessageListenner分別設置和取得MessageListener。 MessageProducer: 由Session 對象創建的用來發送消息的對象 MessageConsumer: 由Session 對象創建的用來接收消息的對象 開發JMS客戶端的幾個步驟: 廣義上說,一個JMS應用是幾個JMS 客戶端交換消息,開發JMS客戶端應用由以下幾步構成: 1) 用JNDI 得到ConnectionFactory對象; 2) 用JNDI 得到目標隊列或主題對象,即Destination對象; 3) 用ConnectionFactory創建Connection 對象; 4) 用Connection對象創建一個或多個JMS Session; 5) 用Session 和Destination 創建MessageProducer和MessageConsumer; 6) 通知Connection 開始傳遞消息。】 JMS消息模型 JMS 消息由以下幾部分組成:消息頭,屬性,消息體。 1.消息頭(header):JMS消息頭包含了許多字段,它們是消息發送后由JMS提供者或消息發送者產生,用來表示消息、設置優先權和失效時間等等,并且為消息確定路由。 2.屬性(property):由消息發送者產生,用來添加刪除消息頭以外的附加信息。 3.消息體(body):由消息發送者產生,JMS中定義了5種消息體:ByteMessage、MapMessage、ObjectMessage、StreamMessage和TextMessage。 JMS的五種專用消息類型 JMS API中定義了五種類型的消息,它們擴展了Message接口并對應于五種類型的消息正文數據。它們是: 1.TextMessage:正文為基礎java.lang.String對象的消息,如xml文件內容。 2.MapMessage:正文為底層鍵值對集合的消息,鍵是String對象,值類型可以是Java任何基本類型。 3.BytesMessage:正文為字節集合。 4.StreamMessage:正文為Java中的輸入輸出流。 5.ObjectMessage:正文為Java中的可序列化對象(實現Serializable接口的對象)。 當前比較流行的JMS商業軟件和開源產品: 目前許多廠商采用并實現了JMS API,現在,JMS產品能夠為企業提供一套完整的消息傳遞功能,下面是一些比較流行的JMS商業軟件和開源產品。 1.IBM MQSeries IBM MQ系列產品提供的服務使得應用程序可以使用消息隊列進行相互交流,通過一系列基于Java的API,提供了MQSeries在Java中應用開發的方法。它支持點到點和發布/訂閱兩種消息模式,在基本消息服務的基礎上增加了結構化消息類,通過工作單元提供數據整合等內容。 2.WebLogic WebLogic是BEA公司實現的基于工業標準的J2EE應用服務器,支持大多數企業級JavaAPI,它完全兼容JMS規范,支持點到點和發布/訂閱消息模式,它具有以下一些特點: 1) 通過使用管理控制臺設置JMS配置信息; 2) 支持消息的多點廣播; 3) 支持持久消息存儲的文件和數據庫; 4) 支持XML消息,動態創建持久隊列和主題。 3.SonicMQ SonicMQ是Progress公司實現的JMS產品。除了提供基本的消息驅動服務之外,SonicMQ也提供了很多額外的企業級應用開發工具包,它具有以下一些基本特征: 1) 提供JMS規范的完全實現,支持點到點消息模式和發布/訂閱消息模式; 2) 支持層次安全管理; 3) 確保消息在Internet上的持久發送; 4) 動態路由構架(DRA)使企業能夠通過單個消息服務器動態的交換消息; 5) 支持消息服務器的集群。 4.Active MQ Active MQ是一個基于Apcache 2.0 licenced發布,開放源碼的JMS產品。其特點為: 1) 提供點到點消息模式和發布/訂閱消息模式; 2) 支持JBoss、Geronimo等開源應用服務器,支持Spring框架的消息驅動; 3) 新增了一個P2P傳輸層,可以用于創建可靠的P2P JMS網絡連接; 4) 擁有消息持久化、事務、集群支持等JMS基礎設施服務。 5.OpenJMS OpenJMS是一個開源的JMS規范的實現,它包含以下幾個特征: 1) 它支持點到點模型和發布/訂閱模型; 2) 支持同步與異步消息發送; 3) 可視化管理界面,支持Applet; 4) 能夠與Jakarta Tomcat這樣的Servlet容器結合; 5) 支持RMI、TCP、HTTP與SSL協議。 Weblogic9.2中JMS的相關配置: http://www.aygfsteel.com/heyang/archive/2009/09/24/296244.html 參考程序: http://www.aygfsteel.com/Files/heyang/WeblogicStandardJMS_Queue20090924101108.rar http://www.aygfsteel.com/Files/heyang/WeblogicStandardJMS_Topic20090924101059.rar http://www.aygfsteel.com/Files/heyang/SpringJMS_Queue20090924103214.rar http://www.aygfsteel.com/Files/heyang/SpringJMS_Topic20090924103204.rar
摘要: 使用Spring的MDP,我們可以與JMS服務提供者通信,向隊列或是主題發送或消費JMS消息,下面是兩個示例程序,分別是與隊列和主題通信,兩個程序大同小異,主要是配置上有些許區別,其中Weblogic中的JMS相關配置部分請見“Weblogic9.2中JMS的相關配置”。
為了減少體積,這兩個程序去除了庫,這些庫是commons-logging-1.0.4.jar,d... 閱讀全文
一.創建JMS服務器
JMS服務器是Weblogic之下的一個JMS消息處理器,我們需要首先配置好一個JMS服務器.在后面的隊列或是主題的配置中,需要用到它. 一.JMS服務器的配置 1.1 首先,點擊"域結構"中的"服務"->"消息傳遞"->"JMS服務器",準備開始建立JMS服務器.當然,在創建之前,需要點擊"更改中心"中的"鎖定并編輯按鈕". ![]() 1.2 輸入JMS服務器的名稱,暫時還不需要配置持久性存儲. ![]() 1.3 選擇要部署此 JMS 服務器的服務器實例或可遷移目標. ![]() 1.4 JMS服務器創建成功了,點擊"更改中心"中的"激活更改"按鈕,激活剛才進行的修改. ![]() 1.5 激活完畢后的畫面. ![]() 二.創建JMS模塊. JMS模塊是JMS連接工廠ConnectionFactory,隊列Queue和主題Topic的載體,因此我們在建立它們之前需要建立一個JMS模塊. 2.1 點擊域結構下的"服務"->"消息傳遞"->"JMS模塊"鏈接,在點擊"更改中心"中的"鎖定并編輯"按鈕,之后點擊右側的"新建"按鈕,準備開始建立一個JMS模塊. ![]() 2.2 輸入JMS模塊的名稱. ![]() 2.3 選擇要部署此 JMS 系統模塊的服務器或群集 ![]() 2.4 創建完成. ![]() 2.5 激活所進行的修改.至此,JMS模塊創建完成. ![]() 三.創建JMS隊列(Queue) JMS模型有兩種,一種是隊列Queue,它的最主要特征是一條JMS消息只會被一個JMS消息消費者接收;另一種是主題Topic,它的最主要特征是一條JMS消息會被訂閱了此主題的所有訂閱者接收.我們先來看看隊列的配置. 3.1 點擊剛才創建好的JMS模塊"MyJMSModule"鏈接,準備在它底下建立一個隊列. ![]() 3.2 點下"鎖定并編輯"按鈕,點擊右邊的"新建"按鈕,準備新建一種資源. ![]() 3.3 這個界面中列出了多種資源類型,我們現在要建立的隊列,當然點擊"隊列"單選框了. ![]() 3.4 輸入隊列的JNDI名,這個很重要,隊列的發送者和消費者要找到隊列就要靠它.而名稱可以隨便寫,采取默認也行.寫完后點擊"下一步"按鈕. ![]() 3.5 點擊此畫面中的"新建子部署"按鈕,準備新建一個子部署. ![]() 3.6 點擊確定按鈕,子部署名可以采用默認值. ![]() 3.7 為隊列指定子部署和JMS服務器,這兩個就是我們剛才和一開頭配置的。之后點擊“完成”按鈕。 ![]() 3.8 至此,JMS隊列創建成功,在"更改中心"中,點擊"激活更改"按鈕,激活所進行的更改. ![]() 四.建立JMS主題. 4.1 JMS主題和JMS隊列,JMS連接工廠都是JMS模塊下的一種資源,同樣,建立一個JMS主題前,仍然是點擊JMS模塊鏈接,然后也選擇新建一種資源,進入如下頁面后,點擊"主題"單選框. ![]() 4.2 輸入JMS主題的JNDI名,這很重要,主題的發布者和訂閱者找到它就靠這個名,而名稱則可以隨意.之后點擊"下一步"按鈕. ![]() 4.3 進入此頁面,為主題選擇子部署和JMS服務器,之前,我們在建立JMS隊列的時候也進行同樣的配置。 ![]() 4.4 JMS主題創建成功,激活所進行的修改。 ![]() 五.建立連接工廠ConnectionFactory. 我們單有隊列或是主題是不夠的,還需要建立一個連接工廠,連接工廠用于創建到特定JMS服務提供者的消息服務的連接,它常與隊列或是主題配合使用. 5.1 連接工廠也是JMS模塊下的一種資源,我們同樣是和前面一樣,點擊JMS模塊鏈接,再選擇新建按鈕,進去下面的頁面后,選擇"連接工廠"單選框. ![]() 5.2 輸入連接工廠的JNDI名,而名稱則可以隨意. ![]() 5.3 進入此頁面后,點擊完成按鈕,至此連接工廠配置完成.之后選擇激活所進行的更改即可. ![]() 最后,提供兩個程序給大家測試一下剛才配置的正確性. http://www.aygfsteel.com/Files/heyang/WeblogicStandardJMS_Topic20090924101059.rar http://www.aygfsteel.com/Files/heyang/WeblogicStandardJMS_Queue20090924101108.rar 注意為了減少體積,weblogic.jar已經刪除,大家請自行添加進去,另這兩個程序中的隊列或主題和連接工廠和上面配置的可能有大小寫的不同,請大家注意修改.
使用Ajax從后臺取得反饋信息后總要在前臺顯示給客戶看,顯示方式從簡單到復雜一般有三種:1.使用alert顯示文字,這種方式簡便易用,但容易中斷用戶操作,效果也太死板;2.改變頁面某一區域的文字或是圖片,這種使用起來也比較方便,實現也不復雜;3.使用新窗口顯示,這種效果最好,但實現復雜些。本例中采用第二種方式。
Ajax提示信息出現的位置在于次級菜單欄的下方,邊欄和內容欄的上方,由兩部分組成:圖標和文字。 ![]() 消息欄的HTML代碼如下: <div id="msgDiv">
<span id="iconSpan"> <img src="web/img/icon/ok.gif" width="24px" height="24px"/> </span> <span id="msgSpan"> fdsfdsfsdfsdfsdfsdfsdfsdfsdfsd </span> </div> 定義它們的CSS如下: #msgDiv{
display:none; } #iconSpan{ vertical-align:middle; } #msgSpan{ height:100%; font-size:16px; color:#404040; } 在沒有Ajax消息前,它們整體是不表現的,通過display:none進行限制;有消息發生后,再使用JavaScript改變msgDiv,iconSpan,msgSpan的狀態和內容即可,基本原理很簡單,但我們不希望把代碼弄亂,因此需要用類整合一下。 其中iconSpan中將顯示的圖標共有四種:加載圖標,用于在從服務器取回響應前;完成圖標,用于從服務器取得正確信息后;警告圖標,用于從服務器取得錯誤信息后;錯誤圖標,用于無法取得來自服務器的響應時。這樣,用戶在仔細查看文字前,就能大致了解發生的情況,為了使用上的方便,我特地把Ajax消息顯示器組合成了一個Msger類。如下所示: /*************************
* * Class:Msger * 2009-9-9 **************************/ //-- Contructor function Msger(){ this.msgDiv=$("msgDiv"); this.iconSpan=$("iconSpan"); this.msgSpan=$("msgSpan"); // 這里是四種圖標 this.icons=new Array; this.icons[0]="<img src='web/img/icon/error.gif' width='24px' height='24px'/>"; this.icons[1]="<img src='web/img/icon/loading.gif' width='24px' height='24px'/>"; this.icons[2]="<img src='web/img/icon/ok.gif' width='24px' height='24px'/>"; this.icons[3]="<img src='web/img/icon/warning.gif' width='24px' height='24px'/>"; this.timer=new Object; } // 顯示錯誤信息,出現后不消失 Msger.prototype.showErrorMsg=function(msg){ this.msgDiv.style.display="block"; this.iconSpan.innerHTML=this.icons[0]; this.msgSpan.innerHTML=msg; this.msgSpan.style.color="#ff0000"; } // 顯示載入信息,出現后不消失,因為很快會被其他信息替代 Msger.prototype.showLoadingMsg=function(msg){ this.msgDiv.style.display="block"; this.iconSpan.innerHTML=this.icons[1]; this.msgSpan.innerHTML=msg; this.msgSpan.style.color="#404040"; } // 顯示正確消息,使用后漸漸消失 Msger.prototype.showOkMsg=function(msg){ this.msgDiv.style.display="block"; this.iconSpan.innerHTML=this.icons[2]; this.msgSpan.innerHTML=msg; this.msgSpan.style.color="#404040"; this.timer=setTimeout("msger.fadeout()",2000); } // 顯示警告消息,出現一段時間后消失 Msger.prototype.showWarningMsg=function(msg){ this.msgDiv.style.display="block"; this.iconSpan.innerHTML=this.icons[3]; this.msgSpan.innerHTML=msg; this.msgSpan.style.color="#f5692e"; this.timer=setTimeout("msger.hide()",5000); } // 隱藏 Msger.prototype.hide=function(){ this.msgDiv.style.display="none"; clearTimeout(this.timer); } // 漸漸消失 Msger.prototype.fadeout=function(){ var colorRGB=this.msgSpan.style.color; var color=parseInt(colorRGB.slice(1,3),16)+3; if(color<256){ var v1=(Math.floor(color/16)).toString(16); var v2=(Math.floor(color%16)).toString(16); var colorStr="#"+v1+""+v2+v1+""+v2+v1+""+v2; this.msgSpan.style.color=colorStr; this.timer=setTimeout("msger.fadeout()",120); } else{ this.hide(); } } 上面這些函數都好理解,fadeout函數還需要贅述一下,讓文字漸漸消失是通過修改文字的顏色實現的,使它不斷向純白色靠攏就行,另外使用setTimeout調用自身的寫法“msger.fadeout()”要值得注意。以上函數大家務必要理解。 使用上就比以前簡化了,以下是用戶列表頁面的例子: <script language="javascript">
<!-- //-- 消息顯示器 var msger; /***************************************************** * 窗口載入時調用的啟動函數 *****************************************************/ window.onload=function(){ ![]() // 初始化消息顯示器 msger=new Msger; ![]() } /***************************************************** * 選擇成員加入Session *****************************************************/ function selectMember(id){ msger.showLoadingMsg("將選擇的用戶id'"+id+"'加入項目備選用戶名單中 ![]() new Ajax.Request(prjName+'SelectUsersIntoSession.do?id='+id, { method:'get', onSuccess: function(ajaxObj){ var status=ajaxObj.responseXML.getElementsByTagName("status")[0].firstChild.data; // alert(ajaxObj.responseText); if(status=="ok"){ var text=ajaxObj.responseXML.getElementsByTagName("text")[0].firstChild.data; msger.showOkMsg(text); } else{ // 返回錯誤信息 var text=ajaxObj.responseXML.getElementsByTagName("text")[0].firstChild.data; msger.showWarningMsg(text); } }, onFailure: function(){ msger.showErrorMsg("無法取得服務器的響應"); } } ); } //--> </script> 其中,類實例msger與前面的“this.timer=setTimeout("msger.fadeout()",120);”中的msger是呼應的,需要注意。 好了,就到這里,全體代碼請到“ ProjectManager框架下載(更新時間2009年9月10日14:59:48)下載。” --全文完--
在ProjectManager中,由于用戶需要進行的操作較多,于是采用了主菜單和次級菜單的形式來擴大菜單容量。但新問題是用戶不容易記住當前所處的位置,對于新手尤其是這樣,因此,在主菜單和次級菜單上把當前位置標出是對用戶有所幫助的,具體形式如下圖所示:
![]() 具體怎么做到這一點呢?使用JS分別把主菜單和次級菜單的當前項更換一個類別就可以,首先我們看看兩個菜單css定義: #menubar{
width: 950px; height: 30px; margin-left:auto; margin-right:auto; } #menubar ul{ margin:0px; padding:0px; list-style-type:none; } #menubar li{ float:left; display:block; height:30px; line-height:30px; } #menubar li.leftBlank{ width:235px; text-align:left; font-size:20px; color:#000000; } #menubar li.rightBlank{ width:235px; text-align:right; } #menubar li a{ width:96px; font-size:16px; color:#404040; text-decoration:none; text-align:center; background:#ffffff url(../img/manubar.gif) 0px 0px; } #menubar li a.current{ width:96px; color:#ffffff; font-weight:bold; background:#ffffff url(../img/manubar.gif) 0px -49px; border:0px; } #submenubar{ width: 950px; height: 31px; margin-left:auto; margin-right:auto; background:#000000 url(../img/submanubar.gif) 0px -1px repeat-x; border-left:1px #ff7101 solid; border-right:1px #ff7101 solid; } #submenubar ul{ margin:0px; padding:0px; list-style-type:none; } #submenubar li{ float:left; height:31px; line-height:31px; } #submenubar li a{ padding-left:20px; font-size:12px; color:#ffffff; text-decoration:none; text-align:center; } #submenubar li a.current{ color:#c20002; font-weight:bold; } #submenubar li a:hover{ text-decoration:underline; } 上面加粗的部分,就是我們要用JS賦給當前菜單項的,這就比較簡單了,找出來修改className即可,代碼如下: /*****************************************************
* 設置頂級菜單中當前頁所處的位置 *****************************************************/ function setCurrentMenu(menuNumber){ // 設置主菜單 var manubar=$("menubar"); var menu=manubar.childNodes[2].childNodes[menuNumber].firstChild; menu.className="current"; } /***************************************************** * 設置次級菜單中當前頁所處的位置 *****************************************************/ function setCurrentSubMenu(menuNumber){ // 設置次級菜單 var manubar=$("submenubar"); var menu=manubar.childNodes[2].childNodes[menuNumber].firstChild; menu.className="current"; } 其后,在頁面的窗體載入事件中調用這兩個函數即可,指定menuNumber即可指定當前菜單項。userMenuIntroBody.jsp中示例調用如下: <script language="javascript">
這樣,就做到了是第一個主菜單,第五個次級菜單變成當前項,分別用黃色條紋圖片背景和紅色加粗字體標識出來。<!-- /***************************************************** * 窗口載入時調用的啟動函數 *****************************************************/ window.onload=function(){ // 設置當前頁在主菜單和次級菜單中的位置 setCurrentMenu(1); setCurrentSubMenu(5); // 隱藏邊欄,加寬內容欄,使得內容如同全屏一樣 makeConceptFullScreen(); } //--> </script> 有兩點需要贅述一下,一是主菜單的第一項是第二個li節點,第一個是左空白,而次級菜單的第一項是第一個li節點;二是原始的菜單項都是沒有指定current類別的,都是在具體頁面中用JS指定。具體大家多看看代碼。 --全文完-- |