ENTERPRISE JAVABEANS 3.0规范简介

摘要

  EJB ( Enterprise JavaBeans )技术是一种J2EE技术,用于开发和部署基于组件的业务应用程序。使用EJB架构编写的应用程序是可伸缩的、事务性的,并且是多用户安全的。

   虽然EJB架构具有丰富的功能,但其复杂性阻碍了它的广泛应用。在EJB领域,竞争性技术纷至沓来。例如,O/R映射技术(如Toplink和开源的 Hibernate框架)已经超越EJB,成为开发持久性解决方案的优先选项。介绍EJB 3.0规范是吸引开发人员回到EJB的重大举措。规范的目标分为两部分:

  • 使开发人员的EJB开发更为轻松。
  • 标准化持久性框架。

  EJB 3.0能让我们更加接近将EJB按常规JavaBean对待的梦想。它减少了开发人员要提供的编程工件数,消除了需要实现的回调方法或将其减至最少,并降 低了实体bean编码模型和O/R映射模型的复杂性。借助于EJB 3.0,J2EE可以得到更广泛的应用。

  在本文中,首先我们将简要 讨论一下EJB 2.1的局限性。接下来,通过逐个介绍提出的重大更改来描述EJB 3.0如何解决这些难题,更改包括EJB类型、O/R映射模型、实例关系模型和EJB QL(EJB查询语言)等方面。最后,以基于EJB 3.0的EJB代码示例来结束本文。

EJB 2.1的局限性

  使用EJB 2.1开发EJB不是一件容易的事。原因如下:

  • 要创建单个EJB,需要创建大量XML部署描述符。
  • 必须创建一个包含三个源文件的集合。
  • 必须实现多个回调方法,这些方法通常永远都用不到。
  • 必须抛出和获取多个不必要的异常类型。
  • 由于容器管理实体bean之类的组件属于抽象类,所以离开了容器的上下文,就完全不能对EJB进行测试。
  • 最后,目前的EJB-QL形式在功能上有所限制,而且难于使用。这些限制迫使开发人员使用straight JDBC和SQL,或者使用其他持久性框架,如Toplink和Hibernate。

  冗长的API是件让人头疼的事,EJB 3.0在这方面进行了重要尝试,解决了大多数问题。本文涵盖了此规范的主要内容。

部署描述符方法的终结

  XML部署描述符的配置是在简化EJB开发时的主要瓶颈。因此,EJB 3.0规范的主要目标之一就是使开发人员免于处理XML文件。我们通过将添加到JDK 5.0中的元数据注释用作JSR 175 JCP规范的一部分而实现了这一点。注释是一种面向属性的编程,类似于Xdoclet。但与需要预编译的Xdoclet不同的是,注释是通过Java编译 器在编译时编译到类中的。从开发人员的观点出发,注释是与public/private类似的限定符,可以在类、字段或方法中使用:

import javax.ejb.*; 
@Stateless
public class MyAccountBean implements MyAccount
{
@Tx(TxType.REQUIRED)
@MethodPermission({"customer"})
public void deposit(double money) {...}

  注释一般是自解释的。@Stateless注释指示bean无状态。@Tx属性指定方法的事务性声明,@MethodPermission属性 指定被允许访问方法的用户。因此,这意味着不再需要编写XML部署描述符来描述这些属性。不过,这并没有排除XML的使用;只不过使其成为可选方案。该规 范允许使用XML部署描述符重写这些注释。

POJO编程模型

  需要注意的关键一点是,上述无状态会话bean示例是自行结束的。抛开注释,此文件就是一个JavaBean,也称为简单传统Java对象 (Plain Old Java Object,POJO)。对于实体bean来说,接口是可选的,而对于会话bean和消息驱动bean来说,接口则是必需的。然而,这并不意味着必须为 会话bean或消息驱动bean定义接口。如果不实现接口,则会自动生成bean接口。根据在bean类中使用的注释,生成的接口类型可能是本地的或远程 的。bean的所有公共方法都将作为自动生成的业务接口的一部分被包括在内:

public interface ShoppingCart
{
public void purchase(Product product, int quantity);
public void emptyCart();
}

  如果希望选择应该向客户端公开的接口的方法,或者为接口指定一个不同于自动生成名称的名称,则建议显式生成接口。

  此接口类是一个简单传统Java接口(Plain Old Java Interface,POJI)。接口和bean类都不必抛出不必要的异常,如RemoteException。

回调方法

  在EJB 2.1规范中,开发人员必须实现bean类中的多种回调方法,如ejbActivate()、ejbPassivate()、ejbLoad()和 ejbStore(),其中大多数方法永远不会使用。在3.0中,已经没有实现这些方法的强制规定了。在EJB 3.0中,bean开发人员不必实现不必要的回调方法,而可以将任意方法指定为回调方法来接收生命周期事件的通知。任何回调方法都必须使用预先定义的生命 周期事件回调注释进行注释。生命周期事件回调方法注释的示例包括PostConstruct、PreDestroy、PostActivate或 PrePassivate。有些事件回调方法对所有类型的EJB通用,而有些bean是特定于bean类型的(如用于实体bean的 PostPersist)。

  回调方法可以在bean类中定义,也可以在bean监听器类中定义。Bean监听器类是使用与其关联的 bean类上的CallbackListener注释表示的。在两种情况下,对回调方法使用的注释都是相同的,只有方法签名不相同。在监听器类中定义的回 调方法必须将对象用作参数;而当回调在bean自身中时则不需要。此对象参数可用于将bean实例传递到监听器类中的方法。下面是一个将回调方法置于实体 bean中的示例:

@Entity
public class AccountBean{
@PostPersist insertAccountDetails(AccountDetails accountDetails)
public void createAccount(){}
}

  现在来看一个创建监听器类并将其添加到bean类的示例。以下代码定义回调监听器AccountListener:

/* Adds callback listener to bean class */
@CallbackListener AccountListener
public class AccountBean{
public void createAccount(){}
}

  以下代码将回调监听器添加到Account Bean中:

/* Callback method defined inside a Listener class*/
public class AccountListener{
@PostPersist insertAccountDetails(
AccountDetails accountDetails){}
}

  由于@PostPersist用于注册对刚刚被注入数据库的对象调用的方法,在这种情况下,每次使用AccountBean中的createAccount()方法时,都将调用insertAccountDetails()方法。

按异常进行配置

  “按异常进行配置”的方法是EJB 3.0中各个方面的指导方法,用于简化开发工作。它旨在通过使开发人员只在默认值不适用的地方编码,从而简化其工作。

   例如,在很多情况下,可以使用默认值而不使用显式元数据注释元素。在这些情况下,开发人员无需指定元数据注释即可获得与完全指定注释相同的结果。例如, 默认情况下,实体bean(由@Entity进行注释)具有一个默认的实体类型CMP,指示它具有容器管理的持久性。这些默认值可以简化对EJB的注释。 默认值始终表示最常见的要求。例如,如果未指定注释,则对EJB使用容器管理的事务划分(其中的容器是相对于bean而言的,用于管理数据库工作单元的提 交或回滚)。类似地,为会话和消息驱动bean生成默认业务接口将公开接口中bean的所有公共方法,因为这是最常见的用例。

对象关系映射

  O/R映射或持久性模型已发生了重大变化,从基于抽象持久性模式的方法转变为时下流行的各种与POJO相关的方法所促生出的一种方法。O/R映射使用注释指定。O/R映射元数据表达应用程序将应用域的实体和关系映射到数据库的需要和期望。

   在EJB 2.1中,开发人员使用他们自己的机制处理某些特定于数据库的操作,如主键生成。而EJB 3.0提供了针对多个特定于数据库的操作的支持。O/R映射模型具有对本机SQL的内部支持。在本文中,我们虽然在讨论实体bean中的更改时会简述持久 性框架,但我们不提供其详细信息。有关详细信息,请查看EJB 3.0 API规范并下载EJB 3.0持久性文档。

使用注释封装JNDI查找

  通过使用注释、依赖注入机制和简单查找机制,EJB 3.0解决了环境依赖性和的JNDI访问的封装问题。

  EJB的上下文由其容器上下文及其资源和环境上下文组成。bean可以使用以下两种方式访问其资源引用及其上下文中的其他环境条目:

  • 让容器向其提供使用注入之类的引用;例如,@EJB public AddressHome addressHome;自动使用JNDI名称AddressHome查找EJB。
  • 使用添加到javax.ejb.EJBContext接口的Object lookup(String name)方法。此方法可用于查找绑定在bean的JNDI环境命名上下文中的资源及其他环境条目。

依赖注入

  Bean通过依赖性注释来声明资源或其环境上下文中其他条目上的依赖性。依赖性注释指定bean所依赖的对象或资源的类型、特征以及用于接受访问的名称。

   以下是依赖性注释的示例:

  @EJB(name="mySessionBean", beanInterface=MySessionIF.class)

   @Resource(name="myDB", type="javax.sql.DataSource.class")

  依赖性注释可以被附加到bean类或其实例变量或方法。需要为依赖性注释指定的信息量取决于它对上下文的使用以及可以从该上下文推断出的信息量。

使用@Resource注入随机资源

  @EJB注释仅注入EJB存根。更加通用的依赖注入注释为@Resource。使用@Resource注释,可以使用对象的JNDI名称从 JNDI注入任何服务对象。全局(java:/)和本地(java:comp/env) JNDI树都将被搜索到。以下示例注入消息连接工厂和消息队列:

  @Resource (name="ConnectionFactory") QueueConnectionFactory factory;

  @Resource (name="queue/A") Queue queue;

  对于“知名”对象,如TimerService和SessionContext,JNDI名称是标准名称;因此@Resource注释无需显式指定name属性即可注入这些对象:

  @Resource TimerService tms;

  @Resource SessionContext ctx;

  与@EJB annotation类似,@Resource注释可应用于setter方法,@Resources注释可应用于数组。@EJB和@Resource注释都特定于它们注入的资源。它们可以简化开发人员的工作。

代码示例

  在以下示例中,将为变量customerDB分配一个JNDI名称为myDB的DataSource对象。需要指定name属性,因为我们选择 的变量的名称customerDB不同于JNDI名称myDB。type属性不需要指定,因为它可以从变量的类型(例如,DataSource)派生。

@Stateless public class MySessionBean implements MySession {

//type is inferred from variable
@Resource(name="myDB") public DataSource customerDB;

public void myMethod1(String myString){
try {
Connection conn = customerDB.getConnection();
...
catch (Exception ex)
}
}

对EJB四种类型的更改

  我们知道,EJB有四种,EJB 3.0对每种EJB类型都进行了更改。在本部分中,我们将查看对每种EJB类型提出的更改。EJB 3.0中的一个主要优点是,所有托管的服务对象都是POJO(如会话bean)或非常轻量级的组件(如消息驱动bean)。不难发现,EJB 3.0已经使EJB的开发变得更加容易和简单。

无状态会话bean

  EJB 3.0会话bean是由EJB容器管理的POJO。

  会话bean的功能由其服务接口(又称为业务接口,是一种简单传 统Java接口)定义。会话bean使用该接口类名称从服务器的JNDI检索该bean的存根对象。存根对象实现bean的服务接口。然后,客户端可以针 对存根对象调用bean接口方法。存根对象仅将调用传递给容器中的实际bean实例对象,该对象只有这些方法的实现,并不进行实际工作。存根对象由EJB 容器自动生成,它知道如何将bean方法调用转到容器——开发人员无需提供存根对象的实现。在无状态会话bean中,客户端存根对象可以将方法调用转到容 器管理对象池中可用的任何bean实例。因此,不必在bean类中使用任何用于保存bean状态的字段。

业务接口

  业务接口为无状态会话bean所需。但这种接口并不总是需要定义。如果不定义,它们会自动生成。根据在bean类中使用的注释,生成的接口类型可能是本地的或远程的;如果没有注释,则为本地接口。bean的所有公共方法都将作为自动生成的业务接口的一部分被包括在内。

主接口

  无状态会话bean不需要主接口。客户端可以通过变量的注入或注释的方法获取无状态会话bean的引用。

bean类

  无状态会话bean必须通过无状态注释进行注释,或者在部署描述符中表示为无状态会话bean。bean类无需实现javax.ejb.SessionBean接口。@Stateless 注释指示bean为无状态会话bean:

@Stateless
public class TraderBean implements Trader {
public void buy (String symbol, int quantity){
System.out.println("Buying "+quantity+ " of "+ symbol);
}
public void sell (String symbol, int quantity);{
System.out.println("Selling "+quantity+ " of "+ symbol);
}
}
会话bean客户端

  将会话bean部署到EJB 3.0容器中后,存根对象将被创建,并在服务器的JNDI注册库中进行注册。客户端代码使用JNDI中接口的类名称获取bean的存根。下面的存根实例展 示了如何为此JSP页面检索TraderBean。可以针对存根对象调用方法,该调用将被透明地委托给EJB 3.0容器中的bean实例:

private Trader tr = null;
public void initialize () {
try {
InitialContext ctx = new InitialContext();
tr = (Trader) ctx.lookup(
Trader.class.getName());
}catch (Exception e) {
e.printStackTrace ();
}
}
// ... ...
public void service (Request req, Response rep) {
// ... ...
double res = tr.buy("SNPS",1000);
}
无状态会话bean的回调

  以下无状态会话bean的生命周期事件回调是受支持的:

  • PostConstruct
  • PreDestroy

  在容器执行任何依赖注入之后、调用bean上的第一个业务方法之前,将回调PostConstruct。PostConstruct方法在未指定的事务上下文和安全上下文中调用。

  在销毁bean实例时,将回调PreDestroy。PreDestroy方法在未指定的事务和安全上下文中执行。

远程和本地接口

  会话bean还可以实现多个接口,每个接口针对一个不同类型的客户端。默认情况下,该接口用于与EJB 3.0容器运行于同一JVM中的“本地”客户端。通过Java引用进行的方法调用既快又有效。另一类型的会话bean接口为远程接口,这种接口用于远程客 户端。当客户端通过远程接口查找会话bean存根时,容器返回实现远程接口的序列化存根对象。即使是在集群环境中,远程存根也知道如何将远程过程调用 (RPC)传递给服务器。远程接口也是简单传统Java接口。

  注意,使用远程接口涉及对存根的序列化和反序列化,而且所有对bean实例的调用都需要通过网络进行。与使用本地接口相比,此方法相当低效。因此,应该避免从本地客户端查找远程接口。

  在会话bean实现中,应该使用@Local and @Remote注释为此bean指定本地和远程接口。下面是一个实现本地和远程接口的示例bean:

@Stateless
@Local ({Trader.class})
@Remote ({RemoteTrader.class})
public class TraderBean implements Trader, RemoteTrader {

public void buy (String symbol, int quantity){
System.out.println("Buying "+quantity+ " of "+ symbol);
}

public void sell (String symbol, int quantity);{
System.out.println("Selling "+quantity+ " of "+ symbol);
}
}

  @Local和@Remote注释还可用于代替bean实现类来标记会话bean接口。例如,以下代码片断指定RemoteTrader为远程接口。有了这一代码片断,就不再需要TraderBean上的@Remote标记了。

有状态会话bean

  有状态会话bean是一种保持其内部状态的会话bean。如果客户端针对同一个bean存根调用多个方法调用,则这些调用将始终连接到容器中的 同一个bean实例。因此,只要客户端应用程序保留bean存根(或本地客户端的引用),bean实例中的所有字段变量都将保留其值。

业务接口

  EJB 3.0 API上的有状态会话bean的业务接口也是简单传统Java接口。业务接口为有状态会话bean所需。但这种接口并不总是需要定义。如果不定义,它们会 自动生成。根据在bean类中使用的注释,生成的接口类型可能是本地的或远程的;如果不存在注释,则其为本地接口。Bean的所有公共方法都将作为自动生 成的业务接口的一部分被包括在内。

主接口

  有状态会话bean不需要主接口。

bean类

  有状态会话bean必须通过有状态注释进行注释,或者在部署描述符中表示为有状态会话bean。bean类无需实现javax.ejb.Session Bean接口。有状态会话bean可实现SessionSynchronization接口。

   有状态TraderBean的实现非常简单。我们将实现类注释为@Stateful,并使用Java对象(如Integer、String)备份在会话 bean接口中定义的bean属性。在客户端会话开始时,Java对象会在创建每个bean实例时对其进行初始化。下面是TraderBean类的完整代 码。注意,有状态会话bean类必须实现可序列化接口,以便容器可以序列化bean实例并保存它们,从而在不使用这些实例时保留其状态信息。

@Stateful
public class TraderBean implements Trader, Serializable {

public String symbol = "";
public int quantity = 0;

public void buy (String symbol, int quantity){
System.out.println("Buying "+quantity+ " of "+ symbol);
}
public void sell (String symbol, int quantity);{
System.out.println("Selling "+quantity+ " of "+ symbol);
}
public String getSymbol(){
return symbol;
}
public int getQuantity(){
return quantity;
}
// Other getter methods for the attributes ...
}
会话bean客户端

  下面是一个示例客户端:

Trader tr = null;
if (tr == null) {
try {
InitialContext ctx = new InitialContext();
tr = (Trader) ctx.lookup(
Trader.class.getName());

} catch (Exception e) {
e.printStackTrace ();
}
}

// Make use of the tr object
有状态会话bean的回调

  有状态会话bean支持以下生命周期事件的回调:构造、销毁、激活和钝化。EJB 3.0规范定义了多个bean可用来在其生命周期中指定回调方法的注释。容器在会话bean生命周期的不同阶段自动调用这些注释方法。可以使用以下注释标记bean类中的任何方法:

  • @PostConstruct:容器会在实例化bean实例后立即调用该注释方法。此注释对于有状态和无状态会话bean都可用。
  • @PreDestroy:容器在从其对象池中销毁不使用的或过期的bean实例前调用此注释方法。此注释对于有状态和无状态会话bean都可用。
  • @PrePassivate:如果某个有状态会话bean实例闲置时间过长,则容器可能钝化它,并将其保存在缓存中。此注释标记的方法在容器钝化bean实例前调用。此注释仅对有状态会话bean可用。
  • @PostActivate:当客户端再次使用被钝化的有状态会话bean时,将创建一个新的实例,并重新保存bean状态。标记此注释的方法在准备激活bean实例时调用。此注释仅对有状态会话bean可用。
  • @Init: 此注释为有状态会话bean指定初始化方法。它与@PostConstruct注释的不同点在于,在有状态会话bean中,多个方法可以被标记为 @Init。但是,每个bean实例只能有一个被调用的@Init方法。EJB 3.0容器根据bean的创建方式确定要调用的@Init方法(请参阅EJB 3.0规范以获取详细信息)。@PostConstruct方法在@Init方法之后调用。

  另一个用于有状态会话bean的生命周期方法注释是@Remove标记。它不是一个回调方法,因为应用程序(而不是容器)在bean存根上调用@Remove方法以移除容器对象池中的bean实例。

实体bean

  EJB 3.0实体是轻量级持久性域对象。实体bean使用@Entity注释标记,实体bean中所有未使用@Transient注释标记的属性/字段都被视为 持久性的。实体bean持久性字段通过JavaBean样式的属性公开,或者仅作为公共/受保护Java类字段。

  实体bean可使用helper类表示实体bean状态,但这些类的实例不具有持久性。相反,其存在与拥有实体bean的实例密切相关;同时这些对象不可跨实体共享。

主接口

  实体bean不需要主接口。

业务接口

  实体bean不需要业务接口。它们都是可选的。

实体类
  • 实体类必须使用实体注释进行注释,或者在XML描述符中表示为实体。
  • 实体类必须具有一个无参数的构造函数。同时实体类还可以具有其他构造函数。
  • 无参数的构造函数必须是公共的或受保护的。
持久性字段或属性

  对于单值持久性属性,方法签名为:

  • T getProperty()
  • void setProperty(T t)
代码示例

  从以下的代码示例中可以发现,实体bean是使用@Entity标记注释的。在该示例中,有一些成员变量以及与它们对应的getter和setter方法。该代码示例还显示了如何注释CMR关系。

  一对多关系使用@OneToMany标记显示。在此示例中,Customer bean具有与Orderscode> bean的一对多关系(一个客户可以具有多个订单)。

  类似地,Customercode>具有与Phonescode> bean的多对多关系。一些业务方法将在业务接口中定义并在bean中实现,例如addPhone(),它用于添加电话记录并将其与客户相关联:

@Entity
public class Customer implements Serializable {
private Long id;
private String name;
private Address address;
private Collection orders = new HashSet();
private Set phones = new HashSet();

// No-arg constructor
public Customer() {}
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@OneToMany
public Collection getOrders() {
return orders;
}
public void setOrders(Collection orders) {
this.orders = orders;
}
@ManyToMany
public Set getPhones() {
return phones;
}
public void setPhones(Set phones) {
this.phones = phones;
}
// Business method to add a phone number to the customer
public void addPhone(PhoneNumber phone) {
this.getPhones().add(phone);
// Set the phone's ref to this customer
phone.setCustomer(this);
}
}
}

消息驱动bean

  现在我们来看最后一种EJB类型:消息驱动bean。

业务接口

  消息驱动bean (MDB)业务接口是由用于bean的消息类型确定的消息监听器接口。该接口为javax.jms.MessageListener。消息驱动bean必 须为其支持的消息传递类型实现适当的消息监听器接口,或者必须使用@MessageDriven注释或部署描述符指定其消息监听器接口。

bean类

  在EJB 3.0中,MDB bean类使用@MessageDriven注释进行注释,该注释指定此MDB监视的消息队列(如队列/mdb)。

  Bean类需要实现MessageListener接口,该接口仅定义了一个onMessage()方法。当消息到达此MDB监视的队列中时,容器将调用bean类的onMessage()方法,并将传入消息作为调用参数传入。

   在我们的示例中,TraderBean.onMessage()方法检索消息正文,分析出参数,执行交易,并将结果保存到静态数据管理器类中。服务请求 消息上的sent时间戳充当计算记录的惟一ID(对低流量Web站点效果很好)。check.jsp JSP页面基于消息ID获取并显示计算记录:

@MessageDriven(activateConfig =
{
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="queue/mdb")
})
public class TraderBean implements MessageListener {

public void onMessage (Message msg) {
try {
TextMessage tmsg = (TextMessage) msg;
Timestamp sent =
new Timestamp(tmsg.getLongProperty("sent"));
StringTokenizer st =
new StringTokenizer(tmsg.getText(), ",");

buy ("SNPS",1000);

RecordManager.addRecord (sent, "BUY SUCCESSFUL");

} catch (Exception e) {
e.printStackTrace ();
}
}
// ... ...
}
发送消息

  要使用消息驱动bean,客户端(如此例中的JSP页面、trader.jsp)需要使用标准JMS API通过队列名称(队列/mdb)的方式获取MDB的目标消息队列,然后将消息发送到该队列:

try {
InitialContext ctx = new InitialContext();
queue = (Queue) ctx.lookup("queue/mdb");
QueueConnectionFactory factory =
(QueueConnectionFactory) ctx.lookup("ConnectionFactory");
cnn = factory.createQueueConnection();
sess = cnn.createQueueSession(
false,QueueSession.AUTO_ACKNOWLEDGE);
} catch (Exception e) {
e.printStackTrace ();
}
TextMessage msg = sess.createTextMessage("SNPS",1000);
sender = sess.createSender(queue);
sender.send(msg);
消息驱动Bean的回调

  支持以下的消息驱动bean生命周期事件回调:

  • PostConstruct
  • PreDestroy

如何对待以前的实体模型?

  出于兼容性方面的原因,以前的实体模型将仍然作为EJB的一部分,并将始终是EJB的一部分。专家组目前正在查看EJB 3.0中许多可能对使用以前的编程模型的人有用的新特性,并在考虑使其对那些使用以前的编程模型的人可用。EJB 3.0计划扩展EJB-QL以用于EJB 2.1样式的CMP实体bean。因此,如果您希望继续使用以前的编程模型,那么完全可以这么做,并同时使用某些新功能。

结束语

  EJB 3.0通过简化开发、推动测试驱动开发和更多地关注简单传统Java对象(POJO)(而非复杂API),从而向前跨越了一大步,使EJB编程体验近乎于一件乐事。本文未详细介绍的一个重要方面是规范中定义的新持久性框架。有关详细信息,请查看EJB 3.0 API规范并下载EJB 3.0持久性文档。

  BEA Systems公司在其BEA WebLogic Server中积极致力于其EJB3实现策略。一旦确定,关于实现和时间安排的详细信息就会在dev2dev上提供。

参考资料

  原文出处:http://dev2dev.bea.com/pub/a/2006/01/ejb-3.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值