今天返璞归真,在JBoss上做了一个EJB2的简单例子,我是怎么做的呢?
首先我们看下面的RMI架构图,它说明了本地对象如何与远程对象进行通信:
我们采用WTP创建EJB工程的向导,还是利用那个经典的例子:
利用XDoclet自动生成的工程包含的文件有:
这里我们忽略Local接口。
在ConverterBean的foo()方法中编写我们的业务逻辑。
默认生成一个ejb-jar.xml配置文件,它是EJB规范中要求的描述文件,可能像这样:
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar id="ejb-jar_1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd" version="2.1"> <description><![CDATA[TestEJB2 generated by eclipse wtp xdoclet extension.]]></description> <display-name>TestEJB2</display-name> <enterprise-beans> <!-- Session Beans --> <session id="Session_Converter"> <description><![CDATA[An EJB named Converter]]></description> <display-name>Converter</display-name> <ejb-name>Converter</ejb-name> <home>cn.com.samueli.ejb2.ConverterHome</home> <remote>cn.com.samueli.ejb2.Converter</remote> <ejb-class>cn.com.samueli.ejb2.ConverterBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> <!-- To add session beans that you have deployment descriptor info for, add a file to your XDoclet merge directory called session-beans.xml that contains the <session></session> markup for those beans. --> <!-- Entity Beans --> <!-- To add entity beans that you have deployment descriptor info for, add a file to your XDoclet merge directory called entity-beans.xml that contains the <entity></entity> markup for those beans. --> <!-- Message Driven Beans --> <!-- To add message driven beans that you have deployment descriptor info for, add a file to your XDoclet merge directory called message-driven-beans.xml that contains the <message-driven></message-driven> markup for those beans. --> </enterprise-beans> </ejb-jar>
我们这里部署在JBoss4上,所以还需要特定于容器的部署描述文件,可能像这样:(jboss.xml)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd"> <jboss> <enterprise-beans> <session> <ejb-name>Converter</ejb-name> <jndi-name>MyConverter</jndi-name> </session> </enterprise-beans> </jboss>
万事俱备,我们部署我们的EJB应用到<JBoss-install>/server/default/deploy下面,并启动服务。
客户端我们采用两种方式来调用远程对象,一种利用Lookup查找接口,一种利用Spring的支持:
首先来看第一种:
public static void main(String[] args) { try { Context initial = new InitialContext(); Object objref = initial.lookup("MyConverter"); ConverterHome home = (ConverterHome) PortableRemoteObject.narrow( objref, ConverterHome.class); Converter c = home.create(); c.foo(); System.exit(0); } catch (Exception ex) { System.err.println("Caught an unexpected exception!"); ex.printStackTrace(); } }
由于采用JNDI查找,我们还需要这样的配置文件:(jndi.properties)
# jndi.properties -- # java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.provider.url=localhost:1099 java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
第二种方法,对于spring支持,我们首先需要有这样的配置文件:(applicationContext.xml)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate"> <property name="environment"> <props> <!-- <prop key="java.naming.provider.url"> t3://127.0.0.1:7001 </prop> <prop key="java.naming.factory.initial"> weblogic.jndi.WLInitialContextFactory </prop> <prop key="java.naming.provider.url"> iiop://127.0.0.1:2809 </prop> <prop key="java.naming.factory.initial"> com.ibm.websphere.naming.WsnInitialContextFactory </prop> --> <prop key="java.naming.provider.url"> localhost:1099 </prop> <prop key="java.naming.factory.initial"> org.jnp.interfaces.NamingContextFactory </prop> </props> </property> </bean> <bean id="ConvertorEJB" class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean" lazy-init="true"> <property name="jndiTemplate"> <ref local="jndiTemplate" /> </property> <property name="jndiName"> <value>MyConverter</value> </property> <property name="businessInterface"> <value>cn.com.samueli.ejb2.Converter</value> </property> </bean> </beans>
我们的代码中就很简单了,可能是这样:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Converter c = (Converter) context.getBean("ConvertorEJB"); try { c.foo(); } catch (RemoteException e) { e.printStackTrace(); }
OK,我们的两种客户端访问方式都可以调用远程对象的方法,并输出了理想中的结果。
当然在上面的过程也出现了一些问题,比如在客户端运行时出现了一些错误:
客户端代码片段:
try { Context initial = new InitialContext(); Object objref = initial.lookup("java:comp/env/ejb/Converter/MyConverter"); ConverterHome home = (ConverterHome) PortableRemoteObject.narrow( objref, ConverterHome.class); Converter c = home.create(); c.foo(); System.exit(0); } catch (Exception ex) { System.err.println("Caught an unexpected exception!"); ex.printStackTrace(); }
报错:
javax.naming.NoInitialContextException: Cannot instantiate class: org.jnp.interfaces.NamingContextFactory [Root exception is java.lang.ClassNotFoundException: org.jnp.interfaces.NamingContextFactory] at javax.naming.spi.NamingManager.getInitialContext(Unknown Source) at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source) at javax.naming.InitialContext.init(Unknown Source) at javax.naming.InitialContext.<init>(Unknown Source) at ConverterClient.main(ConverterClient.java:12)
原来是缺少客户端依赖文件,把<JBoss-install>/client目录下的:jbossall-client.jar和jnp-client.jar添加到Build path中,再运行仍然报错:
javax.naming.NameNotFoundException: comp not bound
Google了一下,发现原来问题是这样:
于是把我的客户端程序的:
Object objref = initial.lookup("java:comp/env/ejb/Converter/MyConverter");
修改为:
Object objref = initial.lookup("MyConverter");
运行OK了。
参考:
http://blog.csdn.net/baobao8505/archive/2007/05/21/1619368.aspx
http://www.blogjava.net/kawaii/archive/2007/02/06/98395.html