项目中一直都在使用EJB(无状态SessionBean),但由于EJB在项目中主要起到分布式作用,因此,都是非常简单的应用,对EJB的环境配置、事务等功能都未曾细研,在此转自http://infocenter.apusic.com/help/index.jsp?topic=/aas/v6/ejb_env.html 上的文章,以备查阅!
摘要
本节提供对Apusic应用服务器中的EJB环境的介绍,包括作为EJB环境的JNDI命名上下文(Context)的介绍、如何使用EJB的环境项(environment entry)、如何在EJB环境中设置对资源连接创建器的引用、如何在EJB环境中设置对应用服务器中被管理对象的引用,最后是存在于应用服务器命名环境中、可访问的javax.transaction.UserTransaction接口的介绍。
EJB组件编程模型提供了一个将企业应用业务逻辑的开发和部署分开的灵活模型。通常在企业应用开发、部署和运行过程中,随着业务需求的不断变化,应用的业务逻辑也在随之发生变化,另外,为提高EJB的可重用特性,需要一种标准的方式,将组件逻辑与实际生产环境中需要访问的其他资源的具体信息分离,因此,EJB编程模型中,引入了EJB环境。
EJB环境实际上是伴随EJB的一个运行时由容器负责管理的JNDI命名上下文,其内容来自于开发者在部署描述中设置和声明的信息。通常,对于业务逻辑中易发生变化的内容,以及组件中需要访问的实际生产环境中的资源(如数据库、邮件服务等)信息,开发者可以将其从组件代码中分离出来,放置到EJB环境有关的部署描述中,这样,在运行时,可只以通过修改部署描述来修改应用的业务逻辑并使组件适应新的部署环境。
EJB环境中提供对如下内容的设置和定制:
使用环境项(environment entry)进行一般的业务逻辑定制 ;
定制对其它EJB组件的引用;
通过资源管理器的连接创建器引用(resouce manager connection factory reference)指定资源管理器的连接创建器;
使用资源环境引用定义伴随某资源的被管理对象接口;
当组件被部署到应用服务器,容器将对部署描述中有关EJB环境设置的内容进行管理,并将其绑定到一个容器管理的命名上下文(naming context)上,组件代码中通过JNDI API访问EJB环境中定义的内容。
需要注意的是,对于不同的EJB组件,其部署描述中定义的环境项(environment entry)集合是不能共享的;只有同一个EJB组件的所有实例,才能共享相同的环境项集合。另外,在EJB规范中规定,在运行时,实例只能对环境项的进行只读操作,不能试图修改环境项。
如果同一个EJB组件被多次部署到容器中,则每一次部署都会被视为不同的组件而生成不同的Home实例,并对不同的Home实例伴随不同的环境项集合,只有属于同一个Home实例的EJB实例,才共享相同的环境项集合。
EJB组件模型中的环境项,提供了在装配和部署时,通过修改部署描述,改变组件业务逻辑的方法。
当组件被部署到应用服务器时,容器将把组件部署描述中设置和声明的环境项保存到EJB环境中的一个名为“java:comp/env的”JNDI命名上下文中,包括此命名上下文直接或非直接的子上下文。在运行时组件可通过JNDI API对这些环境项进行访问。环境项的类型则由开发者在部署描述中声明。下面是在EJB组件业务方法中访问环境项的范例:
public class EmployeeServiceEJB implements SessionBean { ... public void setTaxInfo(int numberOfExemptions, ...) throws InvalidNumberOfExemptionsException { ... // Obtain the enterprise bean’s environment naming context. Context initCtx = new InitialContext(); Context myEnv = (Context)initCtx.lookup("java:comp/env"); // Obtain the maximum number of tax exemptions // configured by the Deployer. Integer max = (Integer)myEnv.lookup(“maxExemptions”); // Obtain the minimum number of tax exemptions // configured by the Deployer. Integer min = (Integer)myEnv.lookup(“minExemptions”); // Use the environment entries to customize business logic. if (numberOfExeptions > Integer.intValue(max) || numberOfExemptions < Integer.intValue(min)) throw new InvalidNumberOfExemptionsException(); // Get some more environment entries. These environment // entries are stored in subcontexts. String val1 = (String)myEnv.lookup(“foo/name1”); Boolean val2 = (Boolean)myEnv.lookup(“foo/bar/name2”); // The enterprise bean can also lookup using full pathnames. Integer val3 = (Integer) initCtx.lookup("java:comp/env/name3"); Integer val4 = (Integer) initCtx.lookup("java:comp/env/foo/name4"); ... } }
对EJB代码中访问的环境项,需要在部署描述文件中使用env-entry元素进行声明,每个env-entry元素代表EJB环境中的一个环境项。env-entry元素的结构如下图:
env-entry元素结构
一个env-entry元素中包含一个可选的描述环境项的子元素description;一个声明相对于java:comp/env命名上下文的环境项名字的元素env-entry-name;声明环境项值的Java类型(即通过JNDI的lookup方法返回的对象类型)的元素env-entry-type;一个表示环境项值的可选元素env-entry-value。
由于在运行时,每个EJB组件的环境项的作用域仅存在于本组件内部,因此,不同EJB组件是访问不到其它组件中定义的环境项的,因此不同的EJB组件采用相同的环境项名字,不会发生冲突。
环境项的值只能是以下几种类型:
java.lang.String;;
java.lang.Character;
java.lang.Integer;
java.lang.Boolean;
java.lang.Double;
java.lang.Byte;
java.lang.Short;
java.lang.Long;
java.lang.Float。
除了java.lang.Character类型的环境项值必须是一个字符之外,每一种类型的环境项值,必须符合对应的Java类型中定义的只接受一个字符串参数的构建器方法,所能接受的字符串参数的格式。
对应上一节中的例子,访问多个环境项的EmployeeServiceEJB,有如下的部署描述文件设置:
<enterprise-beans> <session> ... <ejb-name>EmployeeService</ejb-name> <ejb-class>EmployeeServiceEJB</ejb-class> ... <env-entry> <description> The maximum number of tax exemptions allowed to be set. </description> <env-entry-name>maxExemptions</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> <env-entry-value>15</env-entry-value> </env-entry> <env-entry> <description> The minimum number of tax exemptions allowed to be set. </description> <env-entry-name>minExemptions</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> <env-entry-value>1</env-entry-value> </env-entry> <env-entry> <env-entry-name>foo/name1</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>value1</env-entry-value> </env-entry> <env-entry> <env-entry-name>foo/bar/name2</env-entry-name> <env-entry-type>java.lang.Boolean</env-entry-type> <env-entry-value>true</env-entry-value> </env-entry> <env-entry> <description>Some description.</description> <env-entry-name>name3</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> </env-entry> <env-entry> <env-entry-name>foo/name4</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> <env-entry-value>10</env-entry-value> </env-entry> ... </session> </enterprise-beans> ...
企业应用中,组件之间的相互引用可能会限制单个组件的可移植和可重用特性,在EJB组件的编程模型中,引入了EJB引用。即当某个EJB组件中需要访问应用中的其他EJB时,可以在部署描述中声明对其他EJB的引用,当应用被部署到服务器,由容器解析此引用,并将引用保存在EJB环境中,不需要在组件代码中包含特定的EJB的JNDI名称,这样,当组件被部署到不同的生产环境或被引用的组件发生变化,不需要修改组件代码即可改变EJB的引用目标。
在使用EJB引用时,开发者需要在部署描述中使用ejb-ref元素指定一个到其他EJB的引用项。此引用项包含在EJB环境中,并包含在java:comp/env命名上下文中,根据EJB规范,此引用的命名应在EJB环境中的ejb子上下文中,如:java:comp/env/ejb/RefName,组件代码中即可在EJB环境中使用JNDI的API查找被引用EJB的Home接口。
下面是访问EJB引用的代码范例:
public class EmployeeServiceEJB implements SessionBean { public void changePhoneNumber(...) { ... // Obtain the default initial JNDI context. Context initCtx = new InitialContext(); // Look up the home interface of the EmployeeRecord // enterprise bean in the environment. Object result = initCtx.lookup( "java:comp/env/ejb/EmplRecord"); // Convert the result to the proper type. EmployeeRecordHome emplRecordHome = (EmployeeRecordHome) javax.rmi.PortableRemoteObject.narrow(result, EmployeeRecordHome.class); ... } }
上面的例子中,在EmployeeServiceEJB的EJB环境中指定了一个名为ejb/EmplRecord的EJB引用,通过此引用可访问被引用的EJB的Home接口。
尽管EJB引用属于EJB环境中的一个项目,但不可在部署描述中使用env-entry元素对引用进行声明,必须使用ejb-ref或ejb-local-ref元素进行声明,每个ejb-ref或ejb-local-ref代表一个EJB引用。ejb-ref和ejb-local-ref元素的结构如下图:
ejb-ref和ejb-local-ref元素结构
ejb-ref和ejb-local-ref元素都描述了对其他EJB的引用,所不同的是,ejb-ref声明的是通过远程Home接口和使用远程接口访问被引用的EJB,而ejb-local-ref声明的是通过本地Home接口和使用本地接口访问被引用的EJB。
ejb-ref和ejb-local-ref都包含一个可选的description元素,用于对此引用提供描述信息;ejb-ref-name元素指定此EJB引用的引用名;ejb-ref-type用于指定引用的EJB组件的类型,可以是“Session”或“Entity”;home、local-home、remote和local元素分别用于指定被引用的EJB组件的远程Home接口、本地Home接口、远程接口和本地接口。
由于在运行时,每个EJB组件的EJB引用的作用域仅存在于本组件内部,因此,不同EJB组件是访问不到其它组件中定义的EJB引用的,因此不同的EJB组件采用相同的EJB引用名字,不会发生冲突。
下面是声明EJB引用的部署描述范例:
... <enterprise-beans> <session> ... <ejb-name>EmployeeService</ejb-name> <ejb-class>com.wombat.empl.EmployeeServiceBean</ejb-class> ... <ejb-ref> <description> This is a reference to the entity bean that encapsulates access to employee records. </description> <ejb-ref-name>ejb/EmplRecord</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>EmployeeRecordHome</home> <remote>EmployeeRecord</remote> </ejb-ref> <ejb-ref> <ejb-ref-name>ejb/Payroll</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>PayrollHome</home> <remote>com.aardvark.payroll.Payroll</remote> </ejb-ref> <ejb-ref> <ejb-ref-name>ejb/PensionPlan</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>PensionPlanHome</home> <remote>PensionPlan</remote> </ejb-ref> ... </session> ... </enterprise-beans> ...
另外,开发者还需要在apusic-application.xml文件中,对应的EJB组件的设置中,使用ejb-ref或ejb-local-ref元素将引用名与被引用的EJB组件的JNDI名字进行映射。
apusic-application.xml中的ejb-ref和ejb-local-ref元素的结构如下图:
ejb-ref和ejb-local-ref元素结构
则上例中的EJB引用的配置在apusic-application中的配置如下:
<apusic-application> ... <module> ... <ejb> ... <session ejb-name="EmployeeService"> ... <ejb-ref> <ejb-ref-name>ejb/EmplRecord</ejb-ref-name> <jndi-name>ejb/EmplRecord</jndi-name> </ejb-ref> <ejb-ref> <ejb-ref-name>ejb/Payroll</ejb-ref-name> <jndi-name>ejb/Payroll</jndi-name> </ejb-ref> <ejb-ref> <ejb-ref-name>ejb/PensionPlan</ejb-ref-name> <jndi-name>ejb/PensionPlan</jndi-name> </ejb-ref> ... </session> ... </ejb> ... </module> ... </apusic-application>
开发者也可在ejb-jar部署描述中的ejb-ref或ejb-local-ref元素里使用可选的ejb-link元素来将ejb引用连接到具体的EJB组件。ejb-link元素的值必须是被引用的EJB组件的名字,即被引用的EJB组件的部署描述元素ejb-name的值。被引用的EJB组件可以是同一个应用中,与声明引用的EJB组件相同或不同的EJB模块中的EJB组件。
为了避免整个应用中在发生引用时所产生的EJB名称冲突,ejb-link元素的值必须遵循如下规则,即当被引用的EJB组件位于应用中的其他EJB模块,则在被引用的EJB组件的名字前必须加上被引用组件位于的EJB模块的模块路径,在被引用模块路径和被引用组件名之间使用“#”字符进行分隔。被引用模块路径是相对于当前的引用模块的路径。
如下例:
<enterprise-beans> <session> ... <ejb-name>EmployeeService</ejb-name> <ejb-class>com.wombat.empl.EmployeeServiceEJB</ejb-class> ... <ejb-ref> <ejb-ref-name>ejb/EmplRecord</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.wombat.empl.EmployeeRecordHome</home> <remote>com.wombat.empl.EmployeeRecord</remote> <ejb-link>EmployeeRecord</ejb-link> </ejb-ref> ... </session> ... <entity> <ejb-name>EmployeeRecord</ejb-name> <home>com.wombat.empl.EmployeeRecordHome</home> <remote>com.wombat.empl.EmployeeRecord</remote> ... </entity> ... </enterprise-beans> ...
下例是引用同一个应用中不同模块的EJB组件时,ejb-link的用法:
<entity> ... <ejb-name>OrderEJB</ejb-name> <ejb-class>com.wombat.orders.OrderEJB</ejb-class> ... <ejb-ref> <ejb-ref-name>ejb/Product</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.acme.orders.ProductHome</home> <remote>com.acme.orders.Product</remote> <ejb-link>../products/product.jar#ProductEJB</ejb-link> </ejb-ref> ... </entity>
注意 |
当在ejb-jar模块部署描述中的ejb-ref或ejb-local-ref元素里使用了ejb-link元素将EJB引用连接到了具体的EJB组件,则在apusic-application部署描述可以不使用ejb-ref或ejb-local-ref元素将引用名与被引用EJB组件的JNDI名进行映射。 |
Java EE平台中定义了三种可管理资源,JMS消息系统、可通过JDBC连接的数据库系统以及符合Java Connector Architecture规范的后端系统,应用可通过以上资源的连接创建器(Connection Factory)创建对以上资源的连接。应用服务器则对以上资源提供资源管理器,对应用中创建的资源连接进行管理,如对资源连接提供连接池、在事务中自动对连接进行征用(enlist)等。
Apusic应用服务器中提供对以上内容的实现,例如当应用通过javax.sql.DataSource接口从连接池中获取java.sql.Connection连接时,应用服务器中的资源管理器即对连接进行管理,不过,这些工作对于应用而言则是透明的,应用只根据接口提供的标准API对连接进行操作即可。
EJB组件中,不可避免将会使用某些资源,但是,实际生产环境中的资源情况是不同的,尤其是当EJB组件被装配到不同的生产环境的时候,因此,EJB组件模型在EJB环境中提供了资源管理器的连接创建器引用的机制,即应用中的代码通过资源连接创建器的逻辑名称,取得资源连接创建器对象,然后通过此对象创建与资源的连接。这样,当应用被部署到不同的生产环境,通过将逻辑名称映射到不同的资源,即可提高应用或组件的可重用特性,不用修改代码即可适应新的生产环境。
连接创建器的引用必须在EJB组件的部署描述中进行声明,引用是作为EJB环境中的一个特殊项而被声明的,根据EJB规范,推荐开发者所声明的引用位于不同的EJB环境中的子上下文(subcontext)中,如有关数据库连接的创建器引用名应位于java:comp/env/jdbc的子上下文中、有关JMS消息系统的连接创建器引用名应位于java:comp/env/jms的子上下文中、有关JavaMail的连接创建器引用名应位于java:comp/env/mail的子上下文中、有关URL连接创建器引用名应位于java:comp/env/url的子上下文中。
在部署描述中声明了连接创建器引用后,必须在部署描述中将引用映射到实际的资源,这个工作需要在apusic-application.xml中使用相应的部署描述元素将引用名与资源的JNDI名进行映射。
在完成了引用名与实际资源的映射后,组件在运行时即可通过JNDI的API对引用进行查找,然后通过连接创建器创建与特定资源的连接。
虽然连接创建器引用作为EJB环境的一部分,但是连接创建器的引用在ejb-jar中使用resource-ref元素声明每一个引用,而不是使用env-entry元素。
resource-ref元素的结构如下图:
resource-ref元素结构
EJB组件部署描述中的每个resource-ref元素声明一个连接创建器的引用,包含一个可选的用于对此引用提供文档信息的description元素;包含一个必需的声明引用名的res-ref-name元素、一个声明引用类型的res-ref元素、一个声明引用身份鉴定方式的res-auth元素和一个可选的引用共享范围的res-sharing-scope元素。
res-ref-name元素的值是EJB代码中使用的引用名称,相对于java:comp/env上下文,如值为jdbc/EmployeeAppDB的引用,实际上被视为java:comp/env/jdbc/EmployeeAppDB上下文。
res-type元素的值是引用的资源连接创建器的Java类型,如javax.sql.DataSource。
res-auth-type元素的值用于指定是否在EJB代码中包含登录资源的身份鉴定信息或是按照部署描述中的安全信息通过容器登录资源,如通过代码则值必须是Application,如通过容器则值必须是Container。
res-sharing-scope元素的值用于指定通过连接创建器引用创建的连接是否可以共享。其值可以是Shareable或是Unshareable,如未指定res-sharing-scope元素,则容器按照值为Shareble的情况进行处理。
由于在运行时,每个EJB组件的连接创建器引用的作用域仅存在于本组件内部,因此,不同EJB组件是访问不到其它组件中定义的连接创建器引用的,因此不同的EJB组件采用相同的连接创建器引用名字,不会发生冲突。
下面是EJB组件中声明连接创建器的部署描述文件的例子:
... <enterprise-beans> <session> ... <ejb-name>EmployeeService</ejb-name> <ejb-class>EmployeeServiceEJB</ejb-class> ... <resource-ref> <description> A data source for the database in which the EmployeeService enterprise bean will record a log of all transactions. </description> <res-ref-name>jdbc/EmployeeAppDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Shareable</res-sharing-scope> </resource-ref> ... </session> </enterprise-beans> ...
下面是声明一个JMS消息系统连接创建器引用的例子:
... <enterprise-beans> <session> ... <resource-ref> <description> A queue connection factory used by the MySession enterprise bean to send notifications. </description> <res-ref-name>jms/qConnFactory</res-ref-name> <res-type>javax.jms.QueueConnectionFactory</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Unshareable</res-sharing-scope> </resource-ref> ... </session> </enterprise-beans> ...
对连接创建器引用的映射需要在apusic-application部署描述中使用resource-ref元素进行定义。
apusic-application部署描述中resource-ref元素的结构如下:
resource-ref元素结构
开发者需要将EJB组件中使用res-ref-name元素定义的引用名与实际的资源连接创建器的JNDI名进行映射。如上节中的例子,对应的apusic-application.xml部署描述文件有如下内容:
<apusic-application> ... <module> ... <ejb> ... <session ejb-name="EmployeeService"> ... <resource-ref> <res-ref-name>jdbc/EmployeeAppDB</res-ref-name> <jndi-name>jdbc/ConcreteDataSource</jndi-name> </resource-ref> ... </session> ... </ejb> ... </module> ... </apusic-application>
下面的例子演示如何在EJB组件代码中使用上面两节中定义的数据库连接的连接创建器创建JDBC连接:
public class EmployeeServiceBean implements SessionBean { EJBContext ejbContext; public void changePhoneNumber(...) { ... // obtain the initial JNDI context Context initCtx = new InitialContext(); // perform JNDI lookup to obtain resource manager // connection factory javax.sql.DataSource ds = (javax.sql.DataSource) initCtx.lookup("java:comp/env/jdbc/EmployeeAppDB"); // Invoke factory to obtain a connection. The security // principal is not given, and therefore // it will be defined in related deployment descriptor. java.sql.Connection con = ds.getConnection(); ... } }
可在连接创建器引用声明中出现的连接创建器引用包括如下类型:
JDBC
javax.sql.DataSource
JMS消息系统
javax.jms.QueueConnectionFactory
javax.jms.TopicConnectionFactory
Java Mail
javax.mail.Session
URL
java.net.URL
在EJB编程模型中,组件可通过某资源的被管理对象(如JMS消息服务中的队列或主题)的逻辑名字,进而取得对此被管理对象的引用。这一类逻辑名字,称为资源环境引用,属于EJB环境的一个组成部分。资源环境引用在EJB组件的部署描述中声明,通过这类引用,EJB组件即可适应不同的实际运行环境。
资源环境引用属于EJB环境项中的一个特殊组成部分,不通过env-entry元素进行声明,必须在ejb-jar中组件相关的部署描述的resource-env-ref元素进行声明;在部署到实际生产环境之前,还必须在apusic-application中组件相关的部署描述中使用resource-env-ref元素将引用与实际的被管理对象进行映射。
下图是ejb-jar中resource-env-ref元素的结构:
resource-env-ref元素结构
在ejb-jar中的resource-env-ref元素,包含一个可选的为此引用提供文档信息的description元素,一个声明引用名称的resource-env-ref-name元素和声明引用对象Java类型的resource-env-ref-type元素。
resource-env-ref-name是相对于java:comp/env上下文的一个JNDI名字。因此,如果在代码中使用了一个名为java:comp/env/jms/StockQueue的JMS队列的引用,则在resource-env-ref-name中的值应为jms/StockQueue,而不是java:comp/env/jms/StockQueue。
由于在运行时,每个EJB组件的资源环境引用的作用域仅存在于本组件内部,因此,不同EJB组件是访问不到其它组件中定义的资源环境引用的,因此不同的EJB组件采用相同的资源环境引用名字,不会发生冲突。
下面是在ejb-jar中声明资源环境引用的例子:
... <resource-env-ref> <description> This is a reference to a JMS queue used in the processing of Stock info </description> <resource-env-ref-name>jms/StockInfo</resource-env-ref-name> <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type> </resource-env-ref> ...
在完成对资源环境引用的声明之后,在部署到实际运行环境之前,开发者还需要将已声明的引用与实际的被管理对象进行映射。映射在整个应用的部署描述文件apusic-application.xml中进行,使用resource-env-ref元素。
apusic-applicatoin中的resource-env-ref元素结构如下图:
resource-env-ref元素结构
resource-env-ref元素中,必须包含一个resource-env-ref-name,其值为ejb-jar中对应的resource-env-ref元素中resource-env-ref-name元素的值;还必须包含一个jndi-name元素,此元素的值为被管理对象的JNDI名字。
上面例子对应的apusic-application中的resource-env-ref元素声明如下:
... <resource-env-ref> <resource-env-ref-name>jms/StockInfo</resource-env-ref-name> <jndi-name>jms/StockInfoQueue</jndi-name> </resource-env-ref> ...
本部署描述片断,将引用jms/StockInfo映射到一个名为jms/StockInfoQueue的JMS消息队列。
在EJB组件代码中,则可以使用JNDI的API,根据资源环境引用名查找并获取对实际运行环境中被管理对象的引用,如下例:
public class StockServiceBean implements SessionBean { public void processStockInfo(...) { ... // Obtain the default initial JNDI context. Context initCtx = new InitialContext(); // Look up the JMS StockQueue in the environment. Object result = initCtx.lookup( "java:comp/env/jms/StockQueue"); // Convert the result to the proper type. javax.jms.Queue queue = (javax.jms.Queue)result; } }
在EJB规范中,Bean管理事务的Session Bean和Message-driven Bean可以通过JNDI API查找“java:comp/UserTransaction”名字,取得对javax.transaction.UserTransaction对象的引用。
如下例:
public MySessionEJB implements SessionBean { ... public someMethod(){ Context initCtx = new InitialContext(); UserTransaction utx = (UserTransaction)initCtx.lookup( “java:comp/UserTransaction”); utx.begin(); ... utx.commit(); } ... }
public MySessionEJB implements SessionBean { SessionContext ctx; ... public someMethod(){ UserTransaction utx = ctx.getUserTransaction(); utx.begin(); ... utx.commit(); } ... }