1.开发无状态会话Bean的步骤。
a.开发接口类,并提供业务方法。
b.开发实现类,并实现业务方法。
c.使用@Stateless注解,声明实现类是一个无状态会话Bean。
4.使用@Remote(接口类.class),声明接口为一个远程借口。(默认为本地接口)。//此步骤可省略。
接口HelloWorld.java:
packagecom.qcd.ejb3;
publicinterfaceHelloWorld{
publicStringsayHello(Stringname);
}
实现类HelloWorldBean.java:
packagecom.qcd.ejb3.impl;
importjavax.ejb.Remote;
importjavax.ejb.Stateless;
importcom.qcd.ejb3.HelloWorld;
@Stateless//声明HelloWorldBean为一个无状态会话Bean。
@Remote(HelloWorld.class)//声明HelloWorld为一个远程借口,默认是本地借口
publicclassHelloWorldBeanimplementsHelloWorld{
@Override
publicStringsayHello(Stringname){
returnname+"says:helloworld!";
}
}
2.发布EJB
a.讲EJB打成Jar(可以使用Eclipse的export命令,导出Jar包。也可以用Ant生成Jar包。)
b.复制JAR到发布目录(JBoss默认启动模式是default,那么复制Jar到%JBoss_home%/server/default/deploy)
c.启动JBoss,JBoss会自动发现并部署EJB。(如果已经启动了JBoss,则JBoss可以热部署)。
3.调用EJB
Propertiesprops=newProperties();
props.put("java.naming.factory.initial","org.jnp.interfaces.NamingContextFactory");
props.put("java.naming.provider.url","localhost:1099");
try{
InitialContextic=newInitialContext(props);
HelloWorldhelloworld=(HelloWorld)ic.lookup("HelloWorldBean/remote");
System.out.println(helloworld.sayHello("张三"));
}catch(NamingExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
java.naming.factory.initial:即Context.INITIAL_CONTEXT_FACTORY,制定JNDI连接工厂。
java.naming.provider.url:即Context.PROVIDER_URL,制定JNDI连接字符串。
调用服务器不同,连接工厂和连接字符串也不同。
Jboss的:
java.naming.factory.initial:org.jnp.interfaces.NamingContextFactory
java.naming.provider.url:localhost:1099
WebLogic的:
java.naming.factory.initial:org.weblogic.jndi.WLInitialContextFactory
java.naming.provider.url:t3://localhost:7001
客户端程序可以部署到另外的计算机上,只需要更改连接的服务器IP地址和启动JBoss绑定相应IP地址即可。
这正是EJB的远程调用特性。
上面代码中返回的HelloWorld对象并不是我们在服务器端编写的HelloWorldBean,而是一个实现了HelloWorld借口的代理对象。
这个代理对象最终调用我们的实现类HelloWorldBean.
helloworld.getClass().getName()可以得到此代理类的类名。
HelloWorldBean/remote:Jndi名称。
讲EJB应用打成Jar包后,默认的全局JNDI名称是:
本地接口:ejb-class-name/local;
远程接口:ejb-class-name/remote;
例如:讲HelloWorld应用打包成Jar,那么它的远程接口的JNDI名称是:HelloWorldBean/remote.
如果讲EJB打包成EAR,默认的全局JNDI名称为:
本地接口:ear-file-base-name/ejb-class-name/local;
远程接口:ear-file-base-name/ejb-class-name/remote;
如讲HelloWorld应用作为模块打包成EARHelloWorld.ear,它的远程接口为:HelloWorld/HelloWorldBean/remote。
除了硬编码制定Context环境外,还可以在类路径新建jndi.properties,讲配置信息写入jndi.properties。
在构造Context时,会自动在类路径查找jndi.properties,并将配置信息加载。
/src/jndi.properties:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=localhost:1099
try{
InitialContextic=newInitialContext();
HelloWorldhelloworld=(HelloWorld)ic.lookup("HelloWorldBean/remote");
System.out.println(helloworld.sayHello("lisi"));
System.out.println(helloworld.getClass().getName());
}catch(NamingExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
4.使用Ant提高工作效率
<?xmlversion="1.0"encoding="utf-8"?>
<projectbasedir="."name="HelloWorldEjb3">
<propertyname="src.dir"value="${basedir}/src">
</property>
<propertyenvironment="env">
</property>
<propertyname="jboss.home"value="${env.JBOSS_HOME}">
</property>
<propertyname="jboss.server.config"value="default">
</property>
<propertyname="build.dir"value="${basedir}/build">
</property>
<pathid="build.classpath">
<filesetdir="${jboss.home}/client">
<includename="*.jar"/>
</fileset>
<!--将build作为classpath的一部分。-->
<pathelementlocation="${build.dir}"/>
</path>
<targetname="prepare">
<deletedir="${build.dir}">
</delete>
<mkdirdir="${build.dir}"/>
</target>
<!--在编译Java文件之前,先创建文件夹build-->
<targetname="compile"depends="prepare">
<!--将编译后的class文件放到build目录-->
<javacsrcdir="${src.dir}"destdir="${build.dir}"includeantruntime="on">
<classpathrefid="build.classpath">
</classpath>
</javac>
</target>
<targetname="ejbjar"depends="compile"description="创建EJB发布包">
<jardestfile="${basedir}/${ant.project.name}.jar">
<filesetdir="${build.dir}">
<includename="**/*.class"/>
</fileset>
</jar>
</target>
<targetname="deploy"depends="ejbjar"description="发布EJB">
<copyfile="${basedir}/${ant.project.name}.jar"todir="${jboss.home}/server/${jboss.server.config}/deploy">
</copy>
</target>
<targetname="undeploy"description="卸载EJB">
<deletefile="${jboss.home}/server/${jboss.server.config}/deploy/${ant.project.name}.jar">
</delete>
</target>
</project>
在执行Ant任务时遇到2个问题:
1.warning:'includeantruntime'wasnotset,defaultingtobuild.sysclasspath=last;settofalseforrepeatablebuilds
解决办法:将
<javacsrcdir="${src.dir}"destdir="${build.dir}">
改为:
<javacsrcdir="${src.dir}"destdir="${build.dir}"includeantruntime="on">
2.Unabletofindajavaccompiler;
com.sun.tools.javac.Mainisnotontheclasspath.
PerhapsJAVA_HOMEdoesnotpointtotheJDK.
Itiscurrentlysetto"C:\ProgramFiles\Java\jre6"。
解决办法:将C:\ProgramFiles\Java\jdk1.6.0_22\lib\tools.jar复制到C:\ProgramFiles\Java\jre6\lib。
5.远程接口调用Ejb的过程
a.客户端与Ejb建立Socket通信
b.客户端与Ejb在通信管道上发送IIOP协议消息。
那么在此过程中,必然产生网络通信开销、协议解析开销、对象序列化开销等。
但是如果客户端与Ejb在同一机器的同一JVM内,那么它们完全可以通过内存交互,这样就避免了上面因网络通信产生的各种开销。
此时,就可以使用本地接口。
6.本地接口的开发
跟远程接口差不多,只需要将@remote改为@local即可。
调用的时候也不需要指明上下文环境信息。
HelloWorldBean.java:
packagecom.qcd.ejb3.impl;
importjavax.ejb.Local;
importjavax.ejb.Remote;
importjavax.ejb.Stateless;
importcom.qcd.ejb3.HelloWorld;
@Stateless//声明HelloWorldBean为一个无状态会话Bean。
//@Remote(HelloWorld.class)//声明HelloWorld为一个远程借口,默认是本地借口
@Local(HelloWorld.class)//指明为本地接口。
publicclassHelloWorldBeanimplementsHelloWorld{
@Override
publicStringsayHello(Stringname){
returnname+"says:helloworld!";
}
}
新建Web工程,进行测试
Test.jsp:
<import="com.qcd.ejb3.HelloWorld"%>
<import="javax.naming.NamingException"%>
<import="javax.naming.InitialContext"%>
<import="java.util.Properties"%>
<%@pagelanguage="java"contentType="text/html;charset=UTF-8"
pageEncoding="ISO-8859-1"%>
<!DOCTYPEhtmlPUBLIC"-//W3C//DTDHTML4.01Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=UTF-8">
<title>TestEjbLocalInterface</title>
</head>
<body>
<%
try{
InitialContextic=newInitialContext();
HelloWorldhelloworld=(HelloWorld)ic.lookup("HelloWorldBean/local");
out.println(helloworld.sayHello("lisi"));
System.out.println(helloworld.getClass().getName());
}catch(NamingExceptione){
//TODOAuto-generatedcatchblock
out.println(e.getLocalizedMessage());
}
%>
</body>
</html>
测试:
不需要将接口类放入Web的classpath,将ejb应用引入到web工程即可。
在实际开发中,会充分考虑到本地调用和远程调用的情况。
因此,一般会有一个远程接口,一个本地接口,本地接口继续远程接口。
实现类同时实现本地接口和远程接口的所有业务方法。
将本地接口类指明为本地接口,远程接口类指明为远程接口。
远程接口HelloWorld.java:
packagecom.qcd.ejb3;
publicinterfaceHelloWorld{
publicStringsayHello(Stringname);
}
本地接口HelloWorldLocal.java:
packagecom.qcd.ejb3;
publicinterfaceHelloWorldLocalextendsHelloWorld{
}
实现类HelloWorldBean.java:
packagecom.qcd.ejb3.impl;
importjavax.ejb.Local;
importjavax.ejb.Remote;
importjavax.ejb.Stateless;
importcom.qcd.ejb3.HelloWorld;
importcom.qcd.ejb3.HelloWorldLocal;
@Stateless//声明HelloWorldBean为一个无状态会话Bean。
@Remote(HelloWorld.class)//指明HelloWorld为远程接口
@Local(HelloWorldLocal.class)//指明HelloWorldLocal为本地接口。
publicclassHelloWorldBeanimplementsHelloWorld,HelloWorldLocal{
@Override
publicStringsayHello(Stringname){
returnname+"says:helloworld!";
}
}
这样的话,不管是本地调用还是远程调用都是OK的。
将JBossServer集成到Eclipse中,启动JBoss报如下错误:
原因是AttachmentStore初始化错误。
打开jboss-5.1.0.GA/server/default/conf/bootstrap下的profile.xml文件。
关于AttachmentStore的配置
<!--Theattachmentstore-->
<beanname="AttachmentStore"class="org.jboss.system.server.profileservice.repository.AbstractAttachmentStore">
<constructor><parameter><injectbean="BootstrapProfileFactory"property="attachmentStoreRoot"/></parameter></constructor>
改成
<!--Theattachmentstore-->
<beanname="AttachmentStore"class="org.jboss.system.server.profileservice.repository.AbstractAttachmentStore">
<constructor><parameterclass="java.io.File"><injectbean="BootstrapProfileFactory"property="attachmentStoreRoot"/></parameter></constructor>
启动JBoss时报如下错误:
[Naming]Couldnotstartonport1099
解决办法:
修改\server\default\conf\bindingservice.beans\META-INF\bindings-jboss-beans.xml
<!--NamingService-->
<beanclass="org.jboss.services.binding.ServiceBindingMetadata">
<propertyname="serviceName">jboss:service=Naming</property>
<propertyname="bindingName">Port</property>
<propertyname="port">1099</property>
<propertyname="description">ThelisteningsocketfortheNamingservice</property>
</bean>
j将端口1099修改为其他即可。
7.Ejb调用其他Ejb
我们再创建一个具有本地接口的无状态会话Bean。
在HelloWorldBean中调用这个Bean。
本地接口DateUtil.java:
packagecom.qcd.ejb3;
publicinterfaceDateUtil{
publicStringgetSystemTime();
}
本地接口实现类DateUtilBean.java:
packagecom.qcd.ejb3.impl;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjavax.ejb.Stateless;
importcom.qcd.ejb3.DateUtil;
@Stateless
publicclassDateUtilBeanimplementsDateUtil{
/**
*获取当前系统时间。
*/
publicStringgetSystemTime(){
//TODOAuto-generatedmethodstub
SimpleDateFormatsdf=newSimpleDateFormat("yyyy-mm-ddHH:MM:ss");
Datedate=newDate();
returnsdf.format(date);
}
}
HelloWorldBean.java:
packagecom.qcd.ejb3.impl;
importjavax.ejb.Local;
importjavax.ejb.Remote;
importjavax.ejb.Stateful;
importjavax.ejb.Stateless;
importjavax.naming.InitialContext;
importjavax.naming.NamingException;
importcom.qcd.ejb3.DateUtil;
importcom.qcd.ejb3.HelloWorld;
importcom.qcd.ejb3.HelloWorldLocal;
//在HelloWorldBean中调用DateUtilBean
//第一种方式,是使用jndi查找DateUtilBean.
//第二种房事,是使用依赖注入。
//@Stateless//声明HelloWorldBean为一个无状态会话Bean。
@Stateful
//@Remote(HelloWorld.class)//声明HelloWorld为一个远程借口,默认是本地借口
@Remote(HelloWorld.class)//指明HelloWorld为远程接口
@Local(HelloWorldLocal.class)//指明HelloWorldLocal为本地接口。
publicclassHelloWorldBeanimplementsHelloWorld,HelloWorldLocal{
@Override
publicStringsayHello(Stringname){
try{
InitialContextic=newInitialContext();
DateUtildu=(DateUtil)ic.lookup("/DateUtilBean/local");
Stringtime=du.getSystemTime();
returnname+"说:现在时间是:"+time;
}catch(NamingExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
returnnull;
}
}
用的是JNDI查找的方式。
第二种方式是使用依赖注入。
//使用依赖注入获取Bean
@EJB
privateDateUtildateUtil;
@Override
publicStringsayHello(Stringname){
Stringtime=dateUtil.getSystemTime();
returnname+"说:现在时间是:"+time;
}
使用@EJB,如果EJB发现有多个实现类时,EJB会报一个异常。
因此,在使用@EJB注解时,通过beanName指定EJB的名称。
如:
@EJB(beanName="DateUtilBean")
privateDateUtildu;
对于定时服务,数据源等,要使用@Resource注解。