当前位置: 首页 > 工具软件 > OpenEJB > 使用案例 >

OpenEJB (1)

卜和悌
2023-12-01

本文部分内容节选自Enterprise JavaBeans 3.0 by Bill Burke & Richard Monson-Haefel

 

1 Overview
    Apache OpenEJB 是可嵌入、轻量级的EJB3.0实现,它即可作为Standalone Server,也可以作为Embedded Server嵌入到Tomcat, JUnit, Eclipse, Intellij, Maven, Ant 等等。OpenEJB缺省使用Apache OpenJPA作为JPA实现,Apache ActiveMQ作为JMS实现。OpenEJB 被应用于Apache Geronimo应用服务器、IBM WebSphere Application Server CE和Apple's WebObjects。
    OpenEJB的创建者是 David Blevins 和 Richard Monson-Haefel。David Blevins目前还活跃在OpenEJB User Froum。而Richard Monson-Haefel则是Enterprise JavaBeans前几版的作者。目前Enterprise JavaBeans由JBoss公司首席架构师Bill Burke执笔,出版了第五版。
    OpenEJB 实际上包括两个部分:服务器和容器,这两者是分离的。当创建InitialContext的时候,如果使用LocalInitialContextFactory并配置了openejb.embedded.remotable=true属性,那么会启动ServiceManager。ServiceManager会在类路径上查找ServerServices。如果类路径上有openejb-ejbd jar(依赖openejb-server, openejb-client, openejb-core),那么这些services会被启动并允许远程的客户端访问。如果你希望添加更多的ServerServices以支持其它协议,例如HTTP,那么只要把openejb-httpejbd jar加入到类路径上即可。

 

2 EJBs
2.1 Session Bean
    Session bean可以分为两种类型:stateless和stateful。Stateless session bean不维护两次方法调用间的状态。Stateful session bean维护着与客户端的会话状态。Session bean拥有一个或多个业务接口,业务接口可以是远程接口,也可以是本地接口,但是不能两者兼备。远程接口抛出的异常也会被传播到客户端,因此异常中的成员变量也必须能够被序列化。调用本地接口的方法不会引起参数和返回值的拷贝。如果远程接口和本地接口拥有相同的方法,EJB规范允许它们继承自相同的接口。需要注意的是,只要配置openejb.remotable.businessLocals=true,那么OpenEJB允许客户端远程调用本地接口。在这种情况下,本地接口的参数和返回值仍然需要可以被序列化。
    以下是个stateless session bean的例子:

public interface Calculator {
	
	int sum(int add1, int add2);
	
	int multiply(int mul1, int mul2);
}
import javax.ejb.Local;

@Local
public interface CalculatorLocal extends Calculator{
}
import javax.ejb.Remote;

@Remote
public interface CalculatorRemote extends Calculator{
}
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

@Stateless
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public class CalculatorImpl implements CalculatorLocal, CalculatorRemote {

	public int sum(int add1, int add2) {
		return add1 + add2;
	}

	public int multiply(int mul1, int mul2) {
		return mul1 * mul2;
	}
	
	@PostConstruct
	private void init() {
		System.out.println("#in CalculatorImpl.init()");
	}
	
	@PreDestroy
	private void dispose() {
		System.out.println("#in CalculatorImpl.dispose()");
	}
	

	@AroundInvoke
	private Object intercept(InvocationContext ctx) throws Exception {
		try {
			System.out.println("#pre-invoke method: " + ctx.getMethod().getName());
			return ctx.proceed();
		} finally {
			System.out.println("#post-invoke method: " + ctx.getMethod().getName());
		}
	}
}
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

public class StatelessTest {
	
	public static void main(String args[]) throws Exception {
		//
		Properties properties = new Properties();
		properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
		InitialContext ctx = new InitialContext(properties);
		
		//
		{
			CalculatorLocal cl1 = (CalculatorLocal) ctx.lookup("CalculatorImplLocal");
			System.out.println("cl1.sum(12, 7): " + cl1.sum(12, 7));
			System.out.println("cl1.multiply(12, 7): " + cl1.multiply(12, 7));
		}
		
		//
		{
			Object ref1 = ctx.lookup("CalculatorImplRemote");
			CalculatorRemote cr1 = (CalculatorRemote) PortableRemoteObject.narrow(ref1, CalculatorRemote.class);
			System.out.println("cr1.sum(12, 7): " + cr1.sum(12, 7));
			System.out.println("cr1.multiply(12, 7): " + cr1.multiply(12, 7));
		}
	}
}

     以上例子中,CalculatorImpl 类的init() 和dispose()方法是EJB的生命周期回调方法。intercept()方法是拦截器方法,此外也可以定义拦截器类。需要注意的是,拦截器与所拦截的EJB具有相同的生命周期。

    以上例子采用的是local client(embedded container)模式。META-INF/ejb-jar.xml中的内容只是包含了ejb-jar根元素,如下:

<ejb-jar/>

    OpenEJB会在扫描并部署类路径上的EJB。另外一种部署的方式是编辑conf/openejb.xml文件,例如:

<openejb>
    <Deployments dir="./bin/yourpackage" />
    <Deployments jar="./lib/another.jar" />
</openejb>

    OpenEJB提供缺省的JNDI Name格式是{deploymentId}{interfaceType.annotationName},它可以通过openejb.jndiname.format定制。{deploymentId}缺省是{ejbName},而{ejbName}缺省是EJB的非限定类名。{interfaceType.annotationName}有如下取值:

  • RemoteHome (EJB 2 EJBHome)
  • LocalHome (EJB 2 EJBLocalHome)
  • Remote (EJB 3 Business Remote)
  • Local (EJB 3 Business Local)
  • Endpoint (EJB webservice endpoint)

    因此查找CalculatorLocal接口的JNDI Name是CalculatorImplLocal。关于更多JNDI Name Formatting的介绍,请参考OpenEJB官方文档。

    如果在程序的当前路径下存在conf目录,那么OpenEJB在第一次启动的时候会创建以下文件。如果需要,也可以对这些文件进行编辑以修改相关的配置。

  • openejb.xml (main config file)
  • logging.properties (log levels and files)
  • login.config (jaas config file)
  • users.properties (users that can log in)
  • groups.properties (groups in which users belong)
  • admin.properties (network socket for administration)
  • ejbd.properties (network socket for ejb invocations)
  • hsql.properties (network socket for hsql client access)
  • httpejbd.properties (network socket for ejb invocations over http)
  • telnet.properties (network socket for telnet "server")

 

2.2 Entity Bean
    EJB3.0规范中,Entity bean是被@Entity标注的POJO。如果Entity bean可以被序列化,那么它可以脱离应用服务器而被传递到表示层。可以为Entity bean定义Entity监听器以便指定Entity bean的生命周期回调方法。关于Entity bean的更多介绍,请参考JPA相关文档。

 

2.3 Message Driven Bean
    Message Driven Bean是无状态的,事务感知的服务器端组件。事务上下文并非从JMS的发送方传播过来,而是由容器发起(有意义的事务属性是NotSupported和Required),或者是由bean明确使用UserTransaction被发起的。如果事务是由容器管理的,JMS确认行为是在事务上下文中执行的。EJB2.1后,EJB规范支持一个可扩展的,也更为开放的MDB定义,使之可以对任何开发商的任何消息系统提供服务,而不仅仅是JMS消息系统。以下是个MDB的简单例子:

import javax.ejb.Remote;

@Remote
public interface SchedulerRemote {
	
	void schedule(long initialExpration, long interval, String subject);
	
	void cancel(String subject);
}
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

@Stateless
public class SchedulerImpl implements SchedulerRemote {
	@Resource
	private TimerService timerService;
	
	@Resource(name="JmsConnectionFactory1")
	private ConnectionFactory connectionFactory;

	public void schedule(long initialExpration, long interval, String subject) {
		timerService.createTimer(initialExpration, interval, subject);
	}
	
	public void cancel(String subject) {
		for(Object obj : timerService.getTimers()) {
			Timer timer = (Timer)obj;
			if(timer.getInfo().equals(subject)) {
				timer.cancel();
			}
		}
	}

	@Timeout
	public void sendMessage(Timer timer) {
		try {
			//
			Connection connection = connectionFactory.createConnection();
			connection.start();
			
			//
			Session session = connection.createSession(true, 0);
			Destination destination = session.createQueue((String)timer.getInfo());
            MessageProducer producer = session.createProducer(destination);
            producer.setDeliveryMode(DeliveryMode.PERSISTENT);
			
            //
            TextMessage message = session.createTextMessage("this is a simple message, time: " + System.currentTimeMillis());
            producer.send(message);
            connection.close();
		} catch (Exception e) {
			System.err.println("failed to send message, detail: " + e.getMessage());
		}
	}
}
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activationConfig={
	@ActivationConfigProperty(propertyName="destinationType",propertyValue="javax.jms.Queue"),
	@ActivationConfigProperty(propertyName="destination",propertyValue="SimpleQueue")
})
public class SimpleConsumer implements MessageListener {

	public void onMessage(Message msg) {
		try {
			TextMessage tm = (TextMessage)msg;
			System.out.println("on message: " + tm.getText());
		}  catch(Exception e) {
			e.printStackTrace();
		}
	}
}
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;

public class MdbTestServer {
	
	public static void main(String args[]) throws Exception {
		//
		Properties properties = new Properties();
		properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
		properties.setProperty("openejb.embedded.remotable", "true");
		
		//
		@SuppressWarnings("unused")
		InitialContext ctx = new InitialContext(properties);
		
		//
		while(true) {
			try {
				Thread.sleep(1000);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

public class MdbTestClient {
	
	public static void main(String args[]) throws Exception {
		//
		Properties properties = new Properties();
		properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.RemoteInitialContextFactory");
	    properties.setProperty(Context.PROVIDER_URL, "ejbd://localhost:4201");
	    properties.setProperty(Context.SECURITY_PRINCIPAL, "Kevin");
	    properties.setProperty(Context.SECURITY_CREDENTIALS, "password");
		InitialContext ctx = new InitialContext(properties);
		
		//
		Object ref = ctx.lookup("SchedulerImplRemote");
		SchedulerRemote sr = (SchedulerRemote) PortableRemoteObject.narrow(ref, SchedulerRemote.class);
		sr.schedule(0L, 1000, "SimpleQueue");
		
		//
		Thread.sleep(5000);
		
		//
		sr.cancel("SimpleQueue");
	}
}

    conf/ users.properties文件的内容如下:

    Kevin= password

    conf/openejb.xml文件中的相关内容如下:

<Container id="My MDB Container " type="MESSAGE">
  # The resource adapter delivers messages to the container
  ResourceAdapter JmsResourceAdapter1

  # Specifies the message listener interface handled by this container
  MessageListenerInterface javax.jms.MessageListener

  # Specifies the activation spec class
  ActivationSpecClass org.apache.activemq.ra.ActiveMQActivationSpec

  # Specifies the maximum number of bean instances that are
  # allowed to exist for each MDB deployment.
  InstanceLimit 10
</Container>

<Resource id="mysqlDataSource" type="DataSource">
  JdbcDriver com.mysql.jdbc.Driver
  JdbcUrl jdbc:mysql://localhost:3306/ejb
  UserName root
  Password password
  JtaManaged true
</Resource>

<Resource id="mysqlDataSourceUnmanaged" type="DataSource">
  JdbcDriver com.mysql.jdbc.Driver
  JdbcUrl jdbc:mysql://localhost:3306/ejb
  UserName root
  Password password
  JtaManaged false
</Resource>

<Resource id="JmsResourceAdapter1" type="ActiveMQResourceAdapter">
  # Broker configuration URI as defined by ActiveMQ
  # see http://activemq.apache.org/broker-configuration-uri.html
  BrokerXmlConfig broker:(tcp://localhost:61616)?useJmx=false

  # Broker address
  ServerUrl vm://localhost?async=true

  # DataSource for persistence messages
  DataSource mysqlDataSourceUnmanaged
</Resource>

<Connector id="JmsConnectionFactory1" type="javax.jms.ConnectionFactory">
  ResourceAdapter JmsResourceAdapter1

  # Specifies if the connection is enrolled in global transaction
  # allowed values: xa, local or none
  TransactionSupport xa

  # Maximum number of physical connection to the ActiveMQ broker
  PoolMaxSize 10

  # Minimum number of physical connection to the ActiveMQ broker
  PoolMinSize 0

  # Maximum amount of time to wait for a connection
  ConnectionMaxWaitMilliseconds 5000

  # Maximum amount of time a connection can be idle before being reclaimed
  ConnectionMaxIdleMinutes 15
</Connector>

    以上例子中,SimpleConsumer订阅SimpleQueue;客户端调用SchedulerRemote接口的schedule()和cancel()方法;SchedulerImpl通过使用TimerService定期向SimpleQueue中发送TextMessage。

    跟2.1中的例子不同,本例中采用的是remote client(embedded container)模式:MdbTestServer中配置了properties.setProperty("openejb.embedded.remotable", "true");

 类似资料: