(一)概述
JBOSS是一種組件化的微內核結構,其組成包括一個服務器內核和各種擴展組件,並通過JMX(Java Management Extension)來連接管理各種擴展組件,其實現的擴展組件包括JTS/JTA組件、安全管理(JAAS)組、數據源組件、遠程管理件等等,所有組件以Bean服務的方式連接加載到服務器內核中。我們可以通過定製特定的服務器實例,使用所需要的組件,以符合我們的開發應用要求。在以下的介紹中,我們使用的是JBOSS4.0.4GA版本,如果不做特別說明,$JBOSS_HOME指的就是JBOSS的安裝路徑。
JBoss的一般有如下幾個目錄bin、lib、client、server,我們將分別介紹。
1.bin目錄主要是一些在各種操作系統中啓動服務和停止服務的腳本和啓動停止包,以windows爲例,是run.bat和shutdonw.bat腳本,可以通過在命令臺下運行腳本來啓動服務和停止服務:
1)run 啓動default服務實例
2)run -c all 啓動all服務實例
3)shutdown -S 關閉服務器
在開發中如果我們想讓其支持調試(注意,最好只在開發時使用該參數),可以將run.bat腳本的第80行rem set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y %JAVA_OPTS%中前面的rem去掉,如果在開發中需要支持代碼改變之後立即應用而不需要重啓服務器,可以將其修改爲set JAVA_OPTS=-Xdebug -noagent -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y %JAVA_OPTS%
2.lib
一些服務器和J2EE開發的核心包,譬如j2ee.jar可以從該包獲得,在進行J2EE開發時,將該目錄下的文件拷貝到你的工作區或者將你的編譯、運行路徑連接到該目錄下
3.client
一些J2EE開發客戶端需要的依賴包,同樣將該目錄下的文件引入到你的工作區
4.server
服務器實例,服務器默認提供了三個服務器實例,分別是all、default和minimal,其中all提供了JBOSS的所有擴展組件(包括嵌入一個TOMCAT Web容器),default提供了JBOSS大部分的組件組件,而minimal則不提供任何擴展組件。如果需要定製所需的服務器實例,可以在該目錄下創建一個目錄,將all下的所有文件拷貝到該目錄下,在做相應的增減。
接下來將介紹各個擴展組件的使用配置,採用服務器的all服務器實例,所以在開始之前,別忘了將其啓動起來run -c all
(二)日誌(Log4j)
Log4j是一種日誌記錄工具包,起有如下幾個比較重要的概念
1.日誌級別分爲:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF,譬如如果調用org.apache.log4j.Logger.debug(msg),那麼只有當系統的日誌級別小於等於DEBUG(即ALL或者DEBUG)時,該信息纔會輸出
2.日誌追加器(Appender):日誌追加器定義了日誌信息以什麼樣的格式寫到哪裏,主要有ConsoleAppender和FileAppender,前者以System.out的方式輸出,後者則以文件的格式輸出,自定義的Appender必須實現rg.apache.log4j.Appender接口
3.日誌分類(Category):日誌可以對不同的分類配置不同的日誌級別和日誌追加器,一般以做日誌記錄的類的包名爲日誌分類,日誌分類存在父子關係,譬如對於一個類ayufox.jboss.eclipse.Bootstrap進行日誌,則日誌分類ayufox和ayufox.jboss都對其起作用,其中ayfuox.jboss覆蓋ayufox的配置,就如同ayufox.jboss繼承了ayufox,ayufox.jboss的行爲覆蓋ayufox一樣
Log4j的使用比較簡單,如下
Logger logger = LogManager.getLogger(myClass);
logger.debug(...)
logger.warn(...)
JBoss使用Log4j完成日誌記錄的任務,見$JBOSS_HOME/server/all/config/jboss-service.xml(以all服務實例爲例,下面不再強調)
以下內容爲程序代碼:
<mbean code="org.jboss.logging.Log4jService" name="jboss.system:type=Log4jService,service=Logging" xmbean-dd="resource:xmdesc/Log4jService-xmbean.xml"> <attribute name="ConfigurationURL">resource:log4j.xml</attribute> <attribute name="Log4jQuietMode">true</attribute> <attribute name="RefreshPeriod">60</attribute> </mbean> |
|
從ConfigurationURL,可以知道log4j的配置文件爲$JBOSS_HOME/server/all/config/log4j.xml,詳細配置的理解可以參考該文件和log4j的文檔,在這裏舉例說明,假設我們對jms服務器端的行爲做進一步瞭解,我們可以讓其打印出更多的調試信息,則在log4j.xml中配置如下
以下內容爲程序代碼:
<category name="org.jboss.mx"> <priority value="DEBUG" /> <appender-ref ref="CONSOLE"/> </category>
|
|
(三):命名服務(NS)
命名服務提供了讓用戶可以通過一個名字映射到一個對象的服務,以讓用戶可以使用一個可識別的名字來訪問一個對象,譬如internet的DNS就是一個例子,其提供了根據域名獲得IP的服務。在J2EE中使用JNDI來提供這樣的一種功能。
JNDI的API包位於javax.naming下最主要的類是Context和InitContext,其提供了向命名服務提供者註冊、取消註冊和獲得註冊對象的功能,譬如EJB的擴展組件可能就會在啓動時將加載EJB包並解析Bean和向命名服務提供註冊,而客戶端根據註冊名向命名服務請求獲得EJB的HOME對象。
JBoss的命名服務包括三種,遠程命名服務、本地命名服務和本地ENC,其名字分別以任意、java:和java:comp開頭:遠程命名服務和本地命名服務的名字必須在整個服務器實例中唯一,遠程目錄可以通過遠程訪問,即客戶端和服務提供端位於不同的JVM,譬如一個遠程HOME接口;而本地命名服務僅可以通過本地訪問,即客戶端和服務位於同一個服務器實例,譬如JBOSS提供的數據源;而ENC意思是Enterprise Naming Context,其名字在同一個環境中唯一,譬如不同的應用(兩個不同的Web應用)其名字可以是一樣的,譬如在EJB配置中(如下),可以通過java:com/env/ejb/hello來獲得該EJB,其也僅可以在同一個應用實例中訪問
以下內容爲程序代碼:
<ejb-ref> <ejb-ref-name>ejb/hello</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>ayufox.ejb.test.HelloHome</home> <remote>ayufox.ejb.test.HelloObject</remote> <ejb-link>hello</ejb-link> </ejb-ref> |
|
1.服務器端配置1
命名服務JBoss提供的配置如下$JBOSS_HOME/server/all/config/jboss-service.xml
以下內容爲程序代碼:
<mbean code="org.jboss.naming.NamingService" name="jboss:service=Naming" xmbeandd="resource:xmdesc/NamingService-xmbean.xml"> <attribute name="CallByValue">false</attribute> <attribute name="Port">1099</attribute> <attribute name="BindAddress">${jboss.bind.address}</attribute> <attribute name="RmiPort">1098</attribute> <attribute name="RmiBindAddress">${jboss.bind.address}</attribute> <depends optional-attribute-name="LookupPool" proxy-type="attribute">jboss.system:service=ThreadPool</depends> </mbean> |
|
各個參數意義自明,不再解釋,其中BindAddress是因爲有些服務器有多個網卡具有多個IP,可以通過指定某一個IP則僅向該IP訪問纔有效,可以通過配置該bean來改變JNDI訪問端口等。
2.服務器端配置2
$JBOSS_HOME/server/all/config/jdni.properties(一般不需要重新配置)
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
3.遠程客戶端配置:
在運行環境路徑下配置jdni.properties
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=localhost:1099
4.測試代碼
1)獲得所有遠程命名對象
以下內容爲程序代碼:
InitialContext ctx = new InitialContext(); NamingEnumeration<NameClassPair> ne = ctx.list("java:"); while (ne.hasMoreElements()) { NameClassPair cp = ne.next(); System.out.println(cp.getName()); } |
|
2)獲得遠程HOME接口
以下內容爲程序代碼:
Context ctx = new InitialContext(); Object ref = ctx.lookup("java:/ejb/hello");
|
|
(四):事務(TM)
多個操作組成一個不可分割的整體,可以稱爲一個事務,下面將介紹事務的一些關鍵特性。
1)事務的四要素簡稱ACID,分別是
Atomicity 原子性:事務的多個操作組成一個單元,所有操作要麼一起成功,要麼一起失敗
Consistency 一直性:事務操作的前後,數據庫必須保持數據的一致性和完整性
Isolation 隔離性:多個事務之間彼此不會相互影響
Durability 持久性:事務成功之後,事務結果應該持久化,也就是說事務的成功是持久的
2)加鎖:鎖分爲悲觀鎖和樂觀鎖,當使用悲觀鎖時,分爲五個隔離級別,分別是SERIALIZABLE、REPEATABLE_READ (default)、READ_COMMITTED、READ_UNCOMMITTED和NONE,隔離級別逐步減弱。樂觀鎖也叫版本鎖,其對數據進行操作時,將其複製到臨時區,操作之後將版本與原有數據比較,如果一致則將遞增版本並寫回,如果不一致則回滾,由於樂觀鎖僅在複製出數據和提交數據時對數據加鎖,所以並行度更高,但如果寫操作比較頻繁地話則容易出現衝突導致回滾。
3)兩階段提交(Two-Phase Commit)
如果對多種可恢復的資源進行操作,在事務提交時,可能會導致部分事務成功而部分事務失敗,使數據處於不一致的狀態,這時候可使用分佈式事務,分佈式事務採用兩階段提交協議,在事務提交之前,先詢問各種資源是否爲提交做好準備(第一階段),然後再提交(第二階段),如果失敗,則全部回滾
JBoss事務管理器配置在$JBOSS_HOME/server/all/config/jboss-service.xml中,如下
以下內容爲程序代碼:
<mbean code="org.jboss.tm.TransactionManagerService" name="jboss:service=TransactionManager" xmbean-dd="resource:xmdesc/TransactionManagerService-xmbean.xml"> <attribute name="TransactionTimeout">300</attribute> <attribute name="GlobalIdsEnabled">true</attribute> <depends optional-attribute-name="XidFactory">jboss:service=XidFactory</depends> </mbean> |
|
可以通過JNDI名java:/UserTransaction訪問事務
以下內容爲程序代碼: UserTransaction ut = (UserTransaction) ctx.lookup("java:/UserTransaction"); ut.begin(); ut.commit();
|
|
(五):EJB開發
說明:測試代碼目錄結構
src
ayufox/ejb/test (該目錄放置SessionBean類)
ayufox/ejb/test/entity (該目錄放置EntityBean類)
META-INF (該目錄放置配置文件)
1.SessionBean開發配置:
我們將一步一步講解一個EJB的SessionBean開發過程,並重點說明JBOSS的EJB開發的一些配置(沒有特別說明的話,所有配置都在META-INF目錄下
1)創建一個例子程序
遠程業務接口:ayufox.ejb.test.IHello
以下內容爲程序代碼:
package ayufox.ejb.test; import java.rmi.RemoteException; public interface IHello { String sayHello(String name) throws RemoteException; } |
|
Bean對象:ayufox.ejb.test.HelloObject
以下內容爲程序代碼:
package ayufox.ejb.test; import javax.ejb.EJBObject; public interface HelloObject extends EJBObject, IHello { } |
|
遠程SessionBean:ayufox.ejb.test.HelloSession
以下內容爲程序代碼:
package ayufox.ejb.test; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBException; import javax.ejb.SessionBean; import javax.ejb.SessionContext;
public class HelloSession implements SessionBean, IHello { public void setSessionContext(SessionContext context) throws EJBException, RemoteException { System.out.println("set session context"); } public void ejbCreate() throws RemoteException, CreateException { System.out.println("ejb create"); }
public void ejbRemove() throws EJBException, RemoteException { System.out.println("ejb remove"); }
public void ejbActivate() throws EJBException, RemoteException { System.out.println("ejb activate"); }
public void ejbPassivate() throws EJBException, RemoteException { System.out.println("ejb passivate"); }
public String sayHello(String name) throws RemoteException { System.out.println("say hello to " + name); return "Hello, " + name + "!Welcome to EJB world!"; }
} |
|
Home接口:ayufox.ejb.test.HelloHome
以下內容爲程序代碼:
package ayufox.ejb.test; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome;
public interface HelloHome extends EJBHome { HelloObject create() throws RemoteException, CreateException; } |
|
ejb配置:META-INF/ejb-jar.xml
通過如上的代碼和配置之後,我們就可以訪問並使用該EJB了
以下內容爲程序代碼:
InitialContext ctx = new InitialContext(); Object ref = ctx.lookup("java:hello"); HelloHome home = (HelloHome) PortableRemoteObject.narrow(ref, HelloHome.class); HelloObject hello = home.create(); System.out.println(hello.sayHello("ayufox")); hello.remove(); |
|
默認的情況下,EJB的JNDI名爲EJB名,JNDI的配置請參見命名服務部分
2)指定EJB的JNDI名
配置META-INF/jboss.xml
JNDI名就改爲helloEJB,通過java:helloEJB或者helloEJB訪問該HOME接口
關於jboss.xml的配置規範,參見$JBOSS_HOME/docs/dtd/jboss.dtd
2.EntityBean開發配置
首先在數據庫中建表如下
以下內容爲程序代碼:
CREATE TESTUSER ( USERNAME VARCHAR(50) PRIMARY KEY, PASSWORD VARCHAR(50) NOT NULL ) |
|
1)代碼(這裏爲了測試方便,將其設計爲遠程對象)
遠程對象
以下內容爲程序代碼:
package ayufox.ejb.test.entity; import java.rmi.RemoteException; import javax.ejb.EJBObject; public interface UserObject extends EJBObject { void setUsername(String username) throws RemoteException; void setPassword(String password) throws RemoteException; String getUsername() throws RemoteException; String getPassword() throws RemoteException; } |
|
EntityBean(CMP):
以下內容爲程序代碼:
package ayufox.ejb.test.entity; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBException; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.RemoveException; public abstract class UserCMPEntity implements EntityBean { public void setEntityContext(EntityContext context) throws EJBException, RemoteException { System.out.println("set entity context"); } public void unsetEntityContext() throws EJBException, RemoteException { System.out.println("unset entity context"); } public String ejbCreate(String username, String password) throws CreateException, RemoteException { setUsername(username); setPassword(password); return username; } public void ejbPostCreate(String username, String password) throws RemoteException { System.out.println("ejb post create"); } public void ejbRemove() throws RemoveException, EJBException, RemoteException { System.out.println("ejb remove"); } public void ejbActivate() throws EJBException, RemoteException { System.out.println("ejb activate"); } public void ejbPassivate() throws EJBException, RemoteException { System.out.println("ejb passivate"); } public void ejbLoad() throws EJBException, RemoteException { System.out.println("ejb load"); } public void ejbStore() throws EJBException, RemoteException { System.out.println("ejb store"); } public abstract String getPassword() throws RemoteException; public abstract void setPassword(String password) throws RemoteException; public abstract String getUsername() throws RemoteException; public abstract void setUsername(String username) throws RemoteException; } |
|
HOME接口:
以下內容爲程序代碼:
package ayufox.ejb.test.entity; import java.rmi.RemoteException; import java.util.Collection; import javax.ejb.CreateException; import javax.ejb.EJBHome; import javax.ejb.FinderException; public interface UserHome extends EJBHome { UserObject create(String username, String password) throws CreateException, RemoteException; UserObject findByPrimaryKey(String key) throws FinderException, RemoteException; Collection findByUsername(String username) throws FinderException, RemoteException; } |
|
META-INF/ejb-jar.xml
2)配置數據源
從$JBOSS_HOME/docs/examples/jca下拷貝你所使用的數據庫到$JBOSS_HOME/server/all/deploy下,譬如如果你使用MySql數據庫,則拷貝mysql-ds.xml,根據你的數據庫情況修改該拷貝文件,並拷貝數據庫驅動包到$JBOSS_HOME/server/all/lib下,譬如對於我的配置爲如下
以下內容爲程序代碼:
<datasources> <local-tx-datasource> <jndi-name>MySqlDS</jndi-name> <connection-url>jdbc:mysql://localhost:3306/test</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>root</user-name> <password>11</password> <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name> <metadata> <type-mapping>mySQL</type-mapping> </metadata> </local-tx-datasource> </datasources> |
|
這樣,我們就定義了一個數據源,其可以通過本地JDNI訪問(不可以通過遠程訪問),可以通過java:/MySqlDS訪問該數據源
3)指定數據源
JBOSS系統使用系統默認的數據源作爲EJB的CMP BEAN的數據源,見$JBOSS_HOME/server/all/config/standardjaws.xml,如下
以下內容爲程序代碼:
<jaws> <datasource>java:/DefaultDS</datasource> <type-mapping>Hypersonic SQL</type-mapping> <debug>false</debug> .... </jaws> |
|
系統指定了一個HSQL數據庫實現作爲EJB的CMP BEAN默認數據源,我們可以通過兩種方式來修改我們的測試實體CMP BEAN所使用的數據源
A.修改默認配置,即修改$JBOSS_HOME/server/all/config/standardjaws.xml,將上面的那部分代碼修改如下,(該配置對於所有EJB的CMP BEAN有效果,除非其特別指定自己的數據源)
以下內容爲程序代碼:
<jaws> <datasource>java:/MySqlDS</datasource> <type-mapping>mySQL</type-mapping> <!-- 該配置由上面數據源定義指定--> <debug>false</debug> .... </jaws> |
|
B.特定指定本應用的CMP BEAN數據源,僅對同一應用下的EJB實體類有效,在META-INF下創建文件jaws.xml如下
以下內容爲程序代碼:
<jaws> <datasource>java:/MySqlDS</datasource> <type-mapping>mySQL</type-mapping> </jaws> |
|
4)指定CMP影射
默認情況下,JBOSS的CMP映射假設表名爲EJB名,所有get/set屬性爲表字段名,在這裏,由於表名與EJB名不一直,所以需要修改,在META-INF下創建文件jbosscmp-jdbc.xml,見如下
該文件指定了一些CMP到JDBC映射的細節,詳細可配置情況請參考$JBOSS_HOME/docs/dtd/jbosscmp-jdbc_4_0.dtd,同樣,我們可以指定全局的CMP到JDBC映射細節,可以在$JBOSS_HOME/server/all/config/standardjbosscmp-jdbc.xml中配置
5)測試代碼
與SessionBean類似,不再列舉
(六):消息服務(JMS)
Java消息服務(JMS,Java Message Service)是爲建立企業級的消息異步傳遞服務而產生的。客戶將消息發送到消息服務目的地(Destination),消息服務提供商再根據消息情況轉發給消息消費者。JSM消息服務目的地分爲兩種:隊列方式的點對點(Point-to-Point)和主題方式的分發/訂閱(Pub/Sub)。放在隊列的消息只可以由一個消費者消費,而放在主題的消息,消費者可以通過訂閱獲得其感興趣的消息。下面以主題爲例說明一下JBoss的JMS的配置。
測試代碼:
1)訂閱者程序
以下內容爲程序代碼:
Context ctx = new InitialContext(); TopicConnectionFactory factory = (TopicConnectionFactory) ctx .lookup("java:TopicConnectionFactory"); TopicConnection conn = factory.createTopicConnection(); Topic topic = (Topic) ctx.lookup("java:topic/testTopic"); TopicSession session = conn.createTopicSession(false, TopicSession.AUTO_ACKNOWLEDGE); conn.start(); TopicSubscriber subscriber = session.createSubscriber(topic); subscriber.setMessageListener(new MessageListener() { public void onMessage(Message msg) { if (msg instanceof TextMessage) { try { System.out.println(((TextMessage) msg).getText()); } catch (JMSException e) { } } } }); |
|
2)分發者程序
以下內容爲程序代碼:
Context ctx = new InitialContext(); TopicConnectionFactory factory = (TopicConnectionFactory) ctx.lookup("java:TopicConnectionFactory"); TopicConnection conn = factory.createTopicConnection(); TopicSession session = conn.createTopicSession(false, TopicSession.AUTO_ACKNOWLEDGE); conn.start(); Topic topic = (Topic) ctx.lookup("java:topic/testTopic"); TopicPublisher publisher = session.createPublisher(topic); TextMessage msg = session.createTextMessage("hello, ayufox"); publisher.send(msg); |
|
由於點對點程序類似,此處不再舉例。JBOSS啓動加載JMS功能擴展時,自動註冊了兩個JNDI對象,分別是TopicConnectionFactory和QueueConnectionFactory實現,可分別通過JNDI名java:TopicConnectionFactory和java:QueueConnectionFactory獲取,並同時註冊了java:queue和java:topic兩個環境(Context),所有的目的地(Destination)如上面的java:topic/testTopic都位於這兩個之中的一個環境下,可以通過在$JBOSS_HOME/server/all/deploy-hasingleton/jms/jbossmq-destinations-service.xml配置註冊新的消息目的地,在該配置文件下見testTopic的配置
以下內容爲程序代碼:
<mbean code="org.jboss.mq.server.jmx.Topic" name="jboss.mq.destination:service=Topic,name=testTopic"> <depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends> <depends optional-attribute-name="SecurityManager">jboss.mq:service=SecurityManager</depends> <attribute name="SecurityConf"> <security> <role name="guest" read="true" write="true"/> <role name="publisher" read="true" write="true" create="false"/> <role name="durpublisher" read="true" write="true" create="true"/> </security> </attribute> </mbean>
|
|