Ejb3开发

来源:互联网 发布:桥接模式java类图 编辑:程序博客网 时间:2024/05/19 01:14

1.开发无状态会话Bean的步骤。
a.开发接口类,并提供业务方法。
b.开发实现类,并实现业务方法。
c.使用@Stateless注解,声明实现类是一个无状态会话Bean。
4.使用@Remote(接口类.class),声明接口为一个远程借口。(默认为本地接口)。//此步骤可省略。

 

接口HelloWorld.java:

package com.qcd.ejb3;

public interface HelloWorld {
 public String sayHello(String name);
}

 

实现类HelloWorldBean.java:

package com.qcd.ejb3.impl;

import javax.ejb.Remote;
import javax.ejb.Stateless;

import com.qcd.ejb3.HelloWorld;

@Stateless//声明HelloWorldBean为一个无状态会话Bean。
@Remote(HelloWorld.class)// 声明HelloWorld为一个远程借口,默认是本地借口
public class HelloWorldBean implements HelloWorld {

 @Override
 public String sayHello(String name) {
  return name + " says:hello world!";
 }

}

 

 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
Properties props = new Properties();
props.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
props.put("java.naming.provider.url", "localhost:1099");

try {
 InitialContext ic = new InitialContext(props);
 HelloWorld helloworld = (HelloWorld)ic.lookup("HelloWorldBean/remote");
 System.out.println(helloworld.sayHello("张三"));
} catch (NamingException e) {
 // TODO Auto-generated catch block
 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应用作为模块打包成EAR HelloWorld.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 {
 InitialContext ic = new InitialContext();
 HelloWorld helloworld = (HelloWorld)ic.lookup("HelloWorldBean/remote");
 System.out.println(helloworld.sayHello("lisi"));
 System.out.println(helloworld.getClass().getName());
} catch (NamingException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}

4.使用Ant提高工作效率

 <?xml version="1.0" encoding="utf-8"?>
<project basedir="." name="HelloWorldEjb3">
 <property name="src.dir" value="${basedir}/src">
 </property>
 <property environment="env">
 </property>
 <property name="jboss.home" value="${env.JBOSS_HOME}">
 </property>
 <property name="jboss.server.config" value="default">
 </property>
 <property name="build.dir" value="${basedir}/build">
 </property>

 <path id="build.classpath">
  <fileset dir="${jboss.home}/client">
   <include name="*.jar" />
  </fileset>
  <!--将build作为classpath的一部分。-->
  <pathelement location="${build.dir}" />
 </path>

 <target name="prepare">
  <delete dir="${build.dir}">
  </delete>
  <mkdir dir="${build.dir}" />
 </target>

 <!--在编译Java文件之前,先创建文件夹build-->
 <target name="compile" depends="prepare">
  <!--将编译后的class文件放到build目录-->
  <javac srcdir="${src.dir}" destdir="${build.dir}" includeantruntime="on">
   <classpath refid="build.classpath">
   </classpath>
  </javac>
 </target>

 <target name="ejbjar" depends="compile" description="创建EJB发布包">
  <jar destfile="${basedir}/${ant.project.name}.jar">
   <fileset dir="${build.dir}">
    <include name="**/*.class" />
   </fileset>
  </jar>
 </target>

 <target name="deploy" depends="ejbjar" description="发布EJB">
  <copy file="${basedir}/${ant.project.name}.jar" todir="${jboss.home}/server/${jboss.server.config}/deploy">
  </copy>
 </target>

 <target name="undeploy" description="卸载EJB">
  <delete file="${jboss.home}/server/${jboss.server.config}/deploy/${ant.project.name}.jar">
  </delete>
 </target>
</project>

 

在执行Ant任务时遇到2个问题:

1.warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds

解决办法:将
<javac srcdir="${src.dir}" destdir="${build.dir}">

改为:

<javac srcdir="${src.dir}" destdir="${build.dir}" includeantruntime="on">

 

2.Unable to find a javac compiler;
com.sun.tools.javac.Main is not on the classpath.
Perhaps JAVA_HOME does not point to the JDK.
It is currently set to "C:\Program Files\Java\jre6"。

解决办法:将C:\Program Files\Java\jdk1.6.0_22\lib\tools.jar复制到C:\Program Files\Java\jre6\lib。

 

5.远程接口调用Ejb的过程

 

 

 

a.客户端与Ejb建立Socket通信

b.客户端与Ejb在通信管道上发送IIOP协议消息。

那么在此过程中,必然产生网络通信开销、协议解析开销、对象序列化开销等。

但是如果客户端与Ejb在同一机器的同一JVM内,那么它们完全可以通过内存交互,这样就避免了上面因网络通信产生的各种开销。

此时,就可以使用本地接口。

 

6.本地接口的开发

跟远程接口差不多,只需要将@remote改为@local即可。

调用的时候也不需要指明上下文环境信息。

HelloWorldBean.java:

package com.qcd.ejb3.impl;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;

import com.qcd.ejb3.HelloWorld;

@Stateless//声明HelloWorldBean为一个无状态会话Bean。
//@Remote(HelloWorld.class)//声明HelloWorld为一个远程借口,默认是本地借口
@Local(HelloWorld.class) // 指明为本地接口。
public class HelloWorldBean implements HelloWorld {

 @Override
 public String sayHello(String name) {
  return name + " says:hello world!";
 }

}

 

新建Web工程,进行测试

Test.jsp:

<import="com.qcd.ejb3.HelloWorld"%>
<import="javax.naming.NamingException"%>
<import="javax.naming.InitialContext"%>
<import="java.util.Properties"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test Ejb Local Interface</title>
</head>
<body>
 <%
 try {
  InitialContext ic = new InitialContext();
  HelloWorld helloworld = (HelloWorld)ic.lookup("HelloWorldBean/local");
  out.println(helloworld.sayHello("lisi"));
  System.out.println(helloworld.getClass().getName());
 } catch (NamingException e) {
  // TODO Auto-generated catch block
  out.println(e.getLocalizedMessage());
 }
 %>
</body>
</html>

 

测试:

 

 

 

不需要将接口类放入Web的classpath,将ejb应用引入到web工程即可。

 

在实际开发中,会充分考虑到本地调用和远程调用的情况。

因此,一般会有一个远程接口,一个本地接口,本地接口继续远程接口。

实现类同时实现本地接口和远程接口的所有业务方法。

将本地接口类指明为本地接口,远程接口类指明为远程接口。

远程接口HelloWorld.java:

package com.qcd.ejb3;

public interface HelloWorld {
 public String sayHello(String name);
}

 

本地接口HelloWorldLocal.java:

package com.qcd.ejb3;

public interface HelloWorldLocal extends HelloWorld{
}

 

实现类HelloWorldBean.java:

package com.qcd.ejb3.impl;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;

import com.qcd.ejb3.HelloWorld;
import com.qcd.ejb3.HelloWorldLocal;

@Stateless//声明HelloWorldBean为一个无状态会话Bean。
@Remote(HelloWorld.class) // 指明HelloWorld为远程接口
@Local(HelloWorldLocal.class) // 指明HelloWorldLocal为本地接口。
public class HelloWorldBean implements HelloWorld,HelloWorldLocal {

 @Override
 public String sayHello(String name) {
  return name + " says:hello world!";
 }

}

 

这样的话,不管是本地调用还是远程调用都是OK的。

 

将JBoss Server集成到Eclipse中,启动JBoss报如下错误:

java.lang.IllegalArgumentException: Wrong arguments. new for target java.lang.reflect.Constructor expected=[java.net.URI] actual 

原因是AttachmentStore初始化错误。
打开jboss-5.1.0.GA/server/default/conf/bootstrap下的profile.xml文件。

关于AttachmentStore的配置
<!-- The attachment store -->
<bean name="AttachmentStore" class="org.jboss.system.server.profileservice.repository.AbstractAttachmentStore">
<constructor><parameter><inject bean="BootstrapProfileFactory" property="attachmentStoreRoot" /></parameter></constructor>

改成
<!-- The attachment store -->
<bean name="AttachmentStore" class="org.jboss.system.server.profileservice.repository.AbstractAttachmentStore">
<constructor><parameter class="java.io.File"><inject bean="BootstrapProfileFactory" property="attachmentStoreRoot" /></parameter></constructor>

 

 

 启动JBoss时报如下错误:

[Naming] Could not start on port 1099

 

解决办法:

修改\server\default\conf\bindingservice.beans\META-INF\bindings-jboss-beans.xml

<!-- Naming Service -->
            <bean class="org.jboss.services.binding.ServiceBindingMetadata">
               <property name="serviceName">jboss:service=Naming</property>
               <property name="bindingName">Port</property>
               <property name="port">1099</property>
               <property name="description">The listening socket for the Naming service</property>
            </bean>

j将端口1099修改为其他即可。

 

 

7.Ejb调用其他Ejb

我们再创建一个具有本地接口的无状态会话Bean。

在HelloWorldBean中调用这个Bean。

 

本地接口DateUtil.java:

package com.qcd.ejb3;

public interface DateUtil {
 public String getSystemTime() ;
}

 

本地接口实现类DateUtilBean.java:

package com.qcd.ejb3.impl;

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.ejb.Stateless;

import com.qcd.ejb3.DateUtil;
@Stateless
public class DateUtilBean implements DateUtil {

 /**
  * 获取当前系统时间。
  */
 public String getSystemTime() {
  // TODO Auto-generated method stub
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd HH:MM:ss");
  Date date = new Date();
  return sdf.format(date);
 }

}

 

HelloWorldBean.java:

package com.qcd.ejb3.impl;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateful;
import javax.ejb.Stateless;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import com.qcd.ejb3.DateUtil;
import com.qcd.ejb3.HelloWorld;
import com.qcd.ejb3.HelloWorldLocal;

// 在HelloWorldBean中调用DateUtilBean
// 第一种方式,是使用jndi查找DateUtilBean.
// 第二种房事,是使用依赖注入。

//@Stateless//声明HelloWorldBean为一个无状态会话Bean。
@Stateful
//@Remote(HelloWorld.class)//声明HelloWorld为一个远程借口,默认是本地借口
@Remote(HelloWorld.class) // 指明HelloWorld为远程接口
@Local(HelloWorldLocal.class) // 指明HelloWorldLocal为本地接口。
public class HelloWorldBean implements HelloWorld,HelloWorldLocal {

 @Override
 public String sayHello(String name) {
  try {
   InitialContext ic = new InitialContext();
   DateUtil du = (DateUtil) ic.lookup("/DateUtilBean/local");
   String time = du.getSystemTime();
   return name + "说:现在时间是:" + time;
  } catch (NamingException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  return null;
 }

}

 

用的是JNDI查找的方式。

 

第二种方式是使用依赖注入。

// 使用依赖注入获取Bean
 @EJB
 private DateUtil dateUtil;
 
 @Override
 public String sayHello(String name) {  
  String time = dateUtil.getSystemTime();
  return name + "说:现在时间是:" + time;
 } 

 

 

使用@EJB,如果EJB发现有多个实现类时,EJB会报一个异常。
因此,在使用@EJB注解时,通过beanName指定EJB的名称。
如:
@EJB(beanName="DateUtilBean")
private DateUtil du;

对于定时服务,数据源等,要使用@Resource注解。