(一)概述
JBOSS是一种组件化的微内核结构,其组成包括一个服务器内核和各种扩展组件,并通过JMX(JavaManagementExtension)来连接管理各种扩展组件,其实现的扩展组件包括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-call启动all服务实例
3)shutdown-S关闭服务器
在开发中如果我们想让其支持调试(注意,最好只在开发时使用该参数),可以将run.bat脚本的第80行remsetJAVA_OPTS=-Xdebug-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y%JAVA_OPTS%中前面的rem去掉,如果在开发中需要支持代码改变之后立即应用而不需要重启服务器,可以将其修改为setJAVA_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的所有扩展组件(包括嵌入一个TOMCATWeb容器),default提供了JBOSS大部分的组件组件,而minimal则不提供任何扩展组件。如果需要定制所需的服务器实例,可以在该目录下创建一个目录,将all下的所有文件拷贝到该目录下,在做相应的增减。
接下来将介绍各个扩展组件的使用配置,采用服务器的all服务器实例,所以在开始之前,别忘了将其启动起来run-call
(二)日志(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的使用比较简单,如下
Loggerlogger=LogManager.getLogger(myClass);
logger.debug(...)
logger.warn(...)
JBoss使用Log4j完成日志记录的任务,见$JBOSS_HOME/server/all/config/jboss-service.xml(以all服务实例为例,下面不再强调)
以下内容为程序代码:
<mbeancode="org.jboss.logging.Log4jService"name="jboss.system:type=Log4jService,service=Logging"xmbean-dd="resource:xmdesc/Log4jService-xmbean.xml"> <attributename="ConfigurationURL">resource:log4j.xml</attribute> <attributename="Log4jQuietMode">true</attribute> <attributename="RefreshPeriod">60</attribute> </mbean> |
|
从ConfigurationURL,可以知道log4j的配置文件为$JBOSS_HOME/server/all/config/log4j.xml,详细配置的理解可以参考该文件和log4j的文档,在这里举例说明,假设我们对jms服务器端的行为做进一步了解,我们可以让其打印出更多的调试信息,则在log4j.xml中配置如下
以下内容为程序代码:
<categoryname="org.jboss.mx"> <priorityvalue="DEBUG"/> <appender-refref="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意思是EnterpriseNamingContext,其名字在同一个环境中唯一,譬如不同的应用(两个不同的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
以下内容为程序代码:
<mbeancode="org.jboss.naming.NamingService"name="jboss:service=Naming"xmbeandd="resource:xmdesc/NamingService-xmbean.xml"> <attributename="CallByValue">false</attribute> <attributename="Port">1099</attribute> <attributename="BindAddress">${jboss.bind.address}</attribute> <attributename="RmiPort">1098</attribute> <attributename="RmiBindAddress">${jboss.bind.address}</attribute> <dependsoptional-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)获得所有远程命名对象
以下内容为程序代码:
InitialContextctx=newInitialContext(); NamingEnumeration<NameClassPair>ne=ctx.list("java:"); while(ne.hasMoreElements()) { NameClassPaircp=ne.next(); System.out.println(cp.getName()); } |
|
2)获得远程HOME接口
以下内容为程序代码:
Contextctx=newInitialContext(); Objectref=ctx.lookup("java:/ejb/hello"); |
|
(四):事务(TM)
多个操作组成一个不可分割的整体,可以称为一个事务,下面将介绍事务的一些关键特性。
1)事务的四要素简称ACID,分别是
Atomicity原子性:事务的多个操作组成一个单元,所有操作要么一起成功,要么一起失败
Consistency一直性:事务操作的前后,数据库必须保持数据的一致性和完整性
Isolation隔离性:多个事务之间彼此不会相互影响
Durability持久性:事务成功之后,事务结果应该持久化,也就是说事务的成功是持久的
2)加锁:锁分为悲观锁和乐观锁,当使用悲观锁时,分为五个隔离级别,分别是SERIALIZABLE、REPEATABLE_READ(default)、READ_COMMITTED、READ_UNCOMMITTED和NONE,隔离级别逐步减弱。乐观锁也叫版本锁,其对数据进行操作时,将其复制到临时区,操作之后将版本与原有数据比较,如果一致则将递增版本并写回,如果不一致则回滚,由于乐观锁仅在复制出数据和提交数据时对数据加锁,所以并行度更高,但如果写操作比较频繁地话则容易出现冲突导致回滚。
3)两阶段提交(Two-PhaseCommit)
如果对多种可恢复的资源进行操作,在事务提交时,可能会导致部分事务成功而部分事务失败,使数据处于不一致的状态,这时候可使用分布式事务,分布式事务采用两阶段提交协议,在事务提交之前,先询问各种资源是否为提交做好准备(第一阶段),然后再提交(第二阶段),如果失败,则全部回滚
JBoss事务管理器配置在$JBOSS_HOME/server/all/config/jboss-service.xml中,如下
以下内容为程序代码:
<mbeancode="org.jboss.tm.TransactionManagerService"name="jboss:service=TransactionManager"xmbean-dd="resource:xmdesc/TransactionManagerService-xmbean.xml"> <attributename="TransactionTimeout">300</attribute> <attributename="GlobalIdsEnabled">true</attribute> <dependsoptional-attribute-name="XidFactory">jboss:service=XidFactory</depends> </mbean> |
|
可以通过JNDI名java:/UserTransaction访问事务
以下内容为程序代码:
UserTransactionut=(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
以下内容为程序代码:
packageayufox.ejb.test; importjava.rmi.RemoteException; publicinterfaceIHello { StringsayHello(Stringname)throwsRemoteException; } |
|
Bean对象:ayufox.ejb.test.HelloObject
以下内容为程序代码:
packageayufox.ejb.test; importjavax.ejb.EJBObject; publicinterfaceHelloObjectextendsEJBObject,IHello { } |
|
远程SessionBean:ayufox.ejb.test.HelloSession
以下内容为程序代码:
packageayufox.ejb.test; importjava.rmi.RemoteException; importjavax.ejb.CreateException; importjavax.ejb.EJBException; importjavax.ejb.SessionBean; importjavax.ejb.SessionContext;
publicclassHelloSessionimplementsSessionBean,IHello { publicvoidsetSessionContext(SessionContextcontext)throwsEJBException, RemoteException { System.out.println("setsessioncontext"); } publicvoidejbCreate()throwsRemoteException,CreateException { System.out.println("ejbcreate"); }
publicvoidejbRemove()throwsEJBException,RemoteException { System.out.println("ejbremove"); }
publicvoidejbActivate()throwsEJBException,RemoteException { System.out.println("ejbactivate"); }
publicvoidejbPassivate()throwsEJBException,RemoteException { System.out.println("ejbpassivate"); }
publicStringsayHello(Stringname)throwsRemoteException { System.out.println("sayhelloto"+name); return"Hello,"+name+"!WelcometoEJBworld!"; }
} |
|
Home接口:ayufox.ejb.test.HelloHome
以下内容为程序代码:
packageayufox.ejb.test; importjava.rmi.RemoteException; importjavax.ejb.CreateException; importjavax.ejb.EJBHome;
publicinterfaceHelloHomeextendsEJBHome { HelloObjectcreate()throwsRemoteException,CreateException; } |
|
ejb配置:META-INF/ejb-jar.xml
通过如上的代码和配置之后,我们就可以访问并使用该EJB了
以下内容为程序代码:
InitialContextctx=newInitialContext(); Objectref=ctx.lookup("java:hello"); HelloHomehome=(HelloHome)PortableRemoteObject.narrow(ref,HelloHome.class); HelloObjecthello=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开发配置
首先在数据库中建表如下
以下内容为程序代码:
CREATETESTUSER ( USERNAMEVARCHAR(50)PRIMARYKEY, PASSWORDVARCHAR(50)NOTNULL ) |
|
1)代码(这里为了测试方便,将其设计为远程对象)
远程对象
以下内容为程序代码:
packageayufox.ejb.test.entity; importjava.rmi.RemoteException; importjavax.ejb.EJBObject; publicinterfaceUserObjectextendsEJBObject { voidsetUsername(Stringusername)throwsRemoteException; voidsetPassword(Stringpassword)throwsRemoteException; StringgetUsername()throwsRemoteException; StringgetPassword()throwsRemoteException; } |
|
EntityBean(CMP):
以下内容为程序代码:
packageayufox.ejb.test.entity; importjava.rmi.RemoteException; importjavax.ejb.CreateException; importjavax.ejb.EJBException; importjavax.ejb.EntityBean; importjavax.ejb.EntityContext; importjavax.ejb.RemoveException; publicabstractclassUserCMPEntityimplementsEntityBean { publicvoidsetEntityContext(EntityContextcontext)throwsEJBException, RemoteException { System.out.println("setentitycontext"); } publicvoidunsetEntityContext()throwsEJBException,RemoteException { System.out.println("unsetentitycontext"); } publicStringejbCreate(Stringusername,Stringpassword) throwsCreateException,RemoteException { setUsername(username); setPassword(password); returnusername; } publicvoidejbPostCreate(Stringusername,Stringpassword) throwsRemoteException { System.out.println("ejbpostcreate"); } publicvoidejbRemove()throwsRemoveException,EJBException, RemoteException { System.out.println("ejbremove"); } publicvoidejbActivate()throwsEJBException,RemoteException { System.out.println("ejbactivate"); } publicvoidejbPassivate()throwsEJBException,RemoteException { System.out.println("ejbpassivate"); } publicvoidejbLoad()throwsEJBException,RemoteException { System.out.println("ejbload"); } publicvoidejbStore()throwsEJBException,RemoteException { System.out.println("ejbstore"); } publicabstractStringgetPassword()throwsRemoteException; publicabstractvoidsetPassword(Stringpassword)throwsRemoteException; publicabstractStringgetUsername()throwsRemoteException; publicabstractvoidsetUsername(Stringusername)throwsRemoteException; } |
|
HOME接口:
以下内容为程序代码:
packageayufox.ejb.test.entity; importjava.rmi.RemoteException; importjava.util.Collection; importjavax.ejb.CreateException; importjavax.ejb.EJBHome; importjavax.ejb.FinderException; publicinterfaceUserHomeextendsEJBHome { UserObjectcreate(Stringusername,Stringpassword)throwsCreateException,RemoteException; UserObjectfindByPrimaryKey(Stringkey)throwsFinderException,RemoteException; CollectionfindByUsername(Stringusername)throwsFinderException,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的CMPBEAN的数据源,见$JBOSS_HOME/server/all/config/standardjaws.xml,如下
以下内容为程序代码:
<jaws> <datasource>java:/DefaultDS</datasource> <type-mapping>HypersonicSQL</type-mapping> <debug>false</debug> .... </jaws> |
|
系统指定了一个HSQL数据库实现作为EJB的CMPBEAN默认数据源,我们可以通过两种方式来修改我们的测试实体CMPBEAN所使用的数据源
A.修改默认配置,即修改$JBOSS_HOME/server/all/config/standardjaws.xml,将上面的那部分代码修改如下,(该配置对于所有EJB的CMPBEAN有效果,除非其特别指定自己的数据源)
以下内容为程序代码:
<jaws> <datasource>java:/MySqlDS</datasource> <type-mapping>mySQL</type-mapping><!--该配置由上面数据源定义指定--> <debug>false</debug> .... </jaws> |
|
B.特定指定本应用的CMPBEAN数据源,仅对同一应用下的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,JavaMessageService)是为建立企业级的消息异步传递服务而产生的。客户将消息发送到消息服务目的地(Destination),消息服务提供商再根据消息情况转发给消息消费者。JSM消息服务目的地分为两种:队列方式的点对点(Point-to-Point)和主题方式的分发/订阅(Pub/Sub)。放在队列的消息只可以由一个消费者消费,而放在主题的消息,消费者可以通过订阅获得其感兴趣的消息。下面以主题为例说明一下JBoss的JMS的配置。
测试代码:
1)订阅者程序
以下内容为程序代码:
Contextctx=newInitialContext(); TopicConnectionFactoryfactory=(TopicConnectionFactory)ctx .lookup("java:TopicConnectionFactory"); TopicConnectionconn=factory.createTopicConnection(); Topictopic=(Topic)ctx.lookup("java:topic/testTopic"); TopicSessionsession=conn.createTopicSession(false, TopicSession.AUTO_ACKNOWLEDGE); conn.start(); TopicSubscribersubscriber=session.createSubscriber(topic); subscriber.setMessageListener(newMessageListener() { publicvoidonMessage(Messagemsg) { if(msginstanceofTextMessage) { try { System.out.println(((TextMessage)msg).getText()); } catch(JMSExceptione) { } } } }); |
|
2)分发者程序
以下内容为程序代码:
Contextctx=newInitialContext(); TopicConnectionFactoryfactory=(TopicConnectionFactory)ctx.lookup("java:TopicConnectionFactory"); TopicConnectionconn=factory.createTopicConnection(); TopicSessionsession=conn.createTopicSession(false,TopicSession.AUTO_ACKNOWLEDGE); conn.start(); Topictopic=(Topic)ctx.lookup("java:topic/testTopic"); TopicPublisherpublisher=session.createPublisher(topic); TextMessagemsg=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的配置
以下内容为程序代码:
<mbeancode="org.jboss.mq.server.jmx.Topic" name="jboss.mq.destination:service=Topic,name=testTopic"> <dependsoptional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends> <dependsoptional-attribute-name="SecurityManager">jboss.mq:service=SecurityManager</depends> <attributename="SecurityConf"> <security> <rolename="guest"read="true"write="true"/> <rolename="publisher"read="true"write="true"create="false"/> <rolename="durpublisher"read="true"write="true"create="true"/> </security> </attribute> </mbean> |
|