了解 Java EE 5
 
2009-07-02 作者:Roland Barcia等 来源:IBM
 
本文内容包括:
概要介绍新的 Java™ EE 5 规范、它的许多重要增强功能以及它对 WebSphere® 应用程序的意义。

引言

纵观 Java Platform, Enterprise Edition (Java EE) 规范的历史可以看出,每次重大修订都是由一个重要主题推动的。例如,第一次发布 J2EE™ 1.2 时,伴随的重要主题是首次将单独的规范绑定在一起,后来,在 J2EE 1.4 中,关注的重要主题则是 Web 服务。图 1 显示了 Java EE 的摘要历史,列出了每个版本的重要功能以及促成每次修订的一些重要外部影响。

图 1. Java EE 的历史
图 1. Java EE 的历史
 

与一些新技术的大多数早期版本一样,Java EE 规范的以前版本中存在一些“难点”,其中包括:

  • 业务逻辑编程的复杂性。
  • 持久性编程模型的复杂性和性能。
  • 表示层/逻辑混合。
  • Web 服务的类型、复杂性、文档模型、扩展和性能。
  • 多成员团队开发。
  • 漫长的编辑-编译-调试周期。

这时,毫无疑问,Java EE 5 规范的主题就是简化,这一目标已通过改善以下领域的开发体验得到实现:

  • 简化业务逻辑开发。
  • 简化测试和依赖关系管理。
  • 简化 O/R 持久性。
  • 增强 Web 服务编程模型。

Java EE 5 中的许多升级都受到商业和开放源代码领域创新技术的影响,例如 Hibernate、Spring、Service Data Object (SDO) 以及其他技术。另外,还预期对规范的级别进行升级,做一些小幅度的改进。

为您使用 Java EE 5 做准备,本文重点介绍新规范的一些主要功能,如 EJB 3.0、Java Persistance Architecture (JPA)、Web 服务、JAX-WS 及其他一些内容,然后简单地介绍我们对 Java EE 6 的一些可能的预期。

EJB 3.0

对于 Java EE 5 中的所有技术增强而言,Enterprise JavaBean™ (EJB) 3.0 是最为显著的,已对它的外观进行了很大的更改,明显简化了开发。EJB 3.0 规范已拆分为三个子规范:

  • EJB 3.0 简化 API:定义用于编码 EJB 组件(特别是会话 Bean 和消息驱动的 Bean)的新简化的 API。
  • 核心契约和要求:定义 Bean 和 EJB 容器之间的 EJB 契约。
  • Java 持久性体系结构 API:为持久性定义新实体 Bean 模型。

下一部分描述 EJB 3.0 和 JPA API 的更新。

EJB 3.0 简化

POJO(传统 Java 对象)是最近经常提到的一个术语,它是指编写为简单 Java 类的代码。由于 EJB 2.x 程序要求您扩展特定的类、提供多个接口并编写部署描述符,因此它们被视为“重载”的 Java 对象,而不再是简单的对象;而且,还需要 J2EE 容器来运行和测试它们。EJB 3.0 的更改包括:

  • EJB 组件不再要求主接口。另外,不再需要 EJB 组件提供不同的接口或扩展任何特定于 EJB 的类。
  • J2SE 5.0 标注现在是实现 EJB 3.0 组件的一个主要辅助方法。通过指定特殊的标注,开发人员可以创建 EJB 组件的 POJO 类,并将其作为 XML 的备选方案。
  • EJB 3.0 引入了业务接口概念,而非单独的远程和本地接口。下面给出了它的一个示例:

    清单 1
     
                                
    public interface Stock
    {
    	public double getQuote(String symbol);
    }

    您的 Bean 类可以实现以下接口:

    清单 2
     
                                
    @Stateless public class StockBean implements Stock
    	public double getQuote(String symbol)
    	{
    		return 100.33;
    	}
    }
    

    在上面的示例中,@Stateless 标注意味着此类现在是一个无状态的会话 Bean,并且使用业务接口来调用它。还可以在不实现特定接口的情况下对无状态会话 Bean 进行编码,而且可以通过容器生成一个:

    清单 3
     
                                
    @Stateless public class StockBean
    	public double getQuote(String symbol)
    	{
    		return 100.33;
    	}
    }

    缺省情况下,除非特别标注,否则,所有公共方法都包括在业务接口中。例如,如果使用 @BusinessMethod 至少指定一个方法,则只有使用 @BusinessMethod 指定的方法才包括在业务接口中:

    清单 4
     
                                
    @Stateless public class StockBean
    	@BusinessMethod public double getQuote(String symbol)
    	{
    		return 100.33;
    	}
    }

    现在已经创建了一个接口,那么如何指定一个方法是远程的还是本地的?当然,可以使用一个标注:

    清单 5
     
                                
    @Stateless public class StockBean
    	@Remote public double getQuote(String symbol)
    	{
    		return 100.33;
    	}
    }

    您可以标注业务接口或者 Bean 类本身。当选择生成业务接口时,在 Bean 类上启用标注非常有用。 

EJB 3.0 规范的更新包括以下内容:

  1. 容器服务
  2. 回调
  3. 拦截器
  4. 依赖项注入
  1. 容器服务

    EJB 组件由于隐式支持事务管理和安全性,因此非常受欢迎。EJB 3.0 规范使用标注(和 XML)来应用容器服务。下面给出了一个示例,介绍如何在无状态会话 Bean 上指定事务属性。

    清单 6

                                
    @Stateless public class StockBean
    {
    
    @TransactionAttribute(TransactionAttributeType.REQUIRESNEW)
    	public double getQuote(String symbol)
    	{
    		return 100.33;
    	}
    }

    此标注意味着,该方法将在新事务中运行。请参见规范,了解不同标注的具体语法和语义,但相同事务和安全功能都有标注。还可以使用 XML 部署描述符应用容器服务,而XML 部署描述符可以在部署时覆盖启用灵活性的标注。 
  2. 回调

    回调是什么情况?在 EJB 3.0 之前,必须在 Bean 类上实现回调方法,如 ejbCreate();Bean 类必须实现所有方法,无论是否使用它们。在大多数情况下,这些方法实现是空的。现在还通过标注并使用回调方法或回调侦听器类处理回调。下面是一个示例,说明如何编写代码来使用回调方法响应回调:

    清单 7

                                
    @Stateless public class StockBean implements Stock
    	public double getQuote(String symbol)
    	{
    		return 100.33;
    	}
    
    	@PostConstruct initializeCache()
    	{
    	}
    }

    以上代码可让您在创建 Bean 实例之后实现代码。如果希望使用回调侦听器,则可以创建回调侦听器类:

    清单 8
     
                                
    public class MyCallbackListener
    {
    	@PrePassivate public clearCache(Object obj)
    	{
    		Stock stock = (Stock) obj;
    		//perform logic
    	}
    }

    不属于 Bean 类的回调类必须获取 java.lang.Object 参数。容器然后传递 Bean 实例。Bean 类通过使用特殊回调标注在 Bean 类级别添加回调侦听器类:

    清单 9
     
                                
    @CallbackListener MyCallbackListener
    @Stateless public class StockBean implements Stock
    	public double getQuote(String symbol)
    	{
    		return 100.33;
    	}
    }

    回调方法比较好,因为在代码中包括回调方法是有条件的,这与实现接口不同。 
  3. 拦截器

    EJB 规范另一增强功能是使用拦截器。EJB 组件以前缺少的功能是无法对预处理/后处理和横切关注点(与 Servlet 筛选器对 Servlet 的作用类似)等内容执行面向方面的开发 (AOP)。现在可以开发一个拦截器类并将其应用到 Bean。下面是一个审核 StockBean 类调用的拦截器类的示例:

    清单 10

                                
    public class StockRequestAudit {
    	@AroundInvoke
    	public Object auditStockOperation(InvocationContext inv) throws
    	Exception {
    		try {
    			Object result = inv.proceed();
    			Auditor.audit(inv.getMethod().getName(), inv.getParameters[0]);
    			return result;
    		} catch (Exception ex) {
    			Auditor.auditFailure(ex);
    			throw ex;
    		}
    	}
    }

    上面的拦截器拦截对目标 EJB 方法的调用,然后调用 InvocationContext 上的 proceed() 方法。这可让该调用方法流经实际被调用的 EJB 方法。在返回目标 EJB 方法之后,它使用 InvocationTarget 中的元数据来获取被调用的目标 EJB 组件的方法名和参数。然后可以将拦截器应用到 Bean 类:

    清单 11
     
                                
    @Stateless @Interceptors({StockRequestAudit})
    public class StockBean implements Stock
    	public double getQuote(String symbol)
    	{
    		return 100.33;
    	}
    }

    另外,还可以开发一个在 Bean 类内部实现的拦截器方法,而且可以指定多个拦截器,在指定多个拦截器时,它们的调用顺序由其在 Bean 类中定义的顺序指定。还可以使用 XML 在 Bean 以外应用拦截器,这在 AOP 中为首选方法,因为您希望向 Bean 透明地应用横切关注点。 
  4. 依赖项注入

    依赖于数据源之类的 EJB 代码依赖项和 EJB 客户端调用 EJB 组件的方式难以进行 EJB 开发测试。为解决此问题,EJB 3.0 规范引入了依赖项注入机制。EJB 没有使用 JNDI 查找,而是通过注入代码定义一个资源引用。下面是 EJB Bean 的一个示例,此 EJB Bean 需要调用另一个 EJB 组件并使用数据源执行 JDBC 工作:

    清单 12  

                                
    @Stateless public class StockBean implements Stock
    {
    @EJB(name="MarketBean", businessInterface="Market")
    Market market;
    
    @Resource(name="StockDB, resourceType="javax.sql.DataSource")
    DataSource stockDS
    
    	public double getQuote(String symbol)
    	{
    		Connection con = stockDS.getConnection();
    		//DO JDBC work
    
    			return market.getCurrentPrice(symbol);
    	}
    }

    依赖项注入可以通过多种方式进行,例如通过 setter 方法或类变量。有关详细信息,请参见规范

Java 持久性体系结构 (JPA)

EJB 持久性的规范已发生了显著变化。该规范称为容器管理的持久性 (CMP),它没有定义映射层,而是由容器映射;因此它由供应商实现映射。受多个商业和开源产品和技术(如 Hibernate、Java Data Objects (JDO) 和 TopLink)的影响,EJB 3.0 引入了新的持久性样式,即基于 POJO 的持久性:

  • 在成功模式上建模。
  • 简化 JDBC 访问模式。
  • 可以与 Web 服务集成。
  • 不受容器的阻碍;可以在 Java EE 或 Java SE 环境中使用 JPA。
  • 标准化 O/R 映射元数据。
  • 支持自顶向下、中间相遇和自底向上的开发。
  • 支持断接和连接的对象状态,省去了对单独的数据传输对象的需要。图 2 显示了一个示例。

图 2. 支持对象状态
图 2. 支持对象状态
 

JPA API 的更新包括以下内容:

  1. 类型:实体和表
  2. 实例:Java 对象
  3. 属性:Java 属性和列标注
  4. 依赖对象:嵌入式 Java 对象
  5. 派生属性:瞬态标注
  6. 键属性:标注的字段和键类
  7. 关系:标注和联合列
  8. 约束:标注和数据库
  9. 继承:标注——单个表、联合表和按类表
  1. 类型:实体和表

    JPA 的类型是实体(不再称为实体 Bean),这些实体可以映射到表。映射的主要机制是通过标注实体类。下面是客户 Java 对象和 CUSTOMER 表之间的一个映射示例:

    清单 13  

                                
    @Entity
    @Table(name="CUSTOMER")
    public class Customer implements Serializable {
    ...

    类被标注为一个实体并使用客户标注。部署描述符可用作备用或覆盖机制。 
  2. 实例:Java 对象

    应用程序在运行时与 Java 对象交互。通过使用称为实体管理器的特殊对象,应用程序可以查询或保持对象。使用 EJB 容器中的依赖项注入将实体管理器注入到应用程序(在 Java SE 环境中也可以通过 EntityManagerFactory 查找)。下面是一个示例:

    清单 14

                                
    @PersistenceContext (unitName="db2")
    private EntityManager em;

    应用程序可以检索对象或者将它们传递到实体管理器。下面是使用实体管理器通过主键查找对象的应用程序示例:

    清单 15
     
                                
    Customer customer = (Customer)em.find(Customer.class,customerId);

    下面是另一个示例,说明创建 Java 对象,并通过将其传递到实体管理器而保存到数据库:

    清单 16
     
                                
    CustomerOrder newOrder = new CustomerOrder();
    newOrder.setStatus("OPEN");
    newOrder.setTotal(new Integer(0));
    newOrder.setCustomerId(customerId);
    em.persist(newOrder);
     
  3. 属性:Java 属性和列标注

    属性是类中的 Java 属性。Java 属性可以通过 @column 标注映射到数据库列。共有两种属性映射形式:字段或属性(缺省):

    清单 17

                                
    @Entity(access=FIELD)
    @Table(name="PRODUCT")
    public class Product implements Serializable {
    
    	@Id
    	@Column(name="SKU")
    	Integer sku;
    
    	@Column(name="DESC")
    	String description;
    
    	@Column(name="PRICE")
    	Integer cost;
     
  4. 依赖对象:嵌入式 Java 对象

    JPA 支持依赖对象。可以创建一个特殊的可嵌入对象,可以通过使用 @Embeddable 标注类对其进行定义:

    清单 18

                                
    @Embeddable
    public class CustomerAddress {
    private int streetAddress;
    private String city;
    private String state;
    private String country;
    ...
    }

    然后可以将该对象定义为实体类上的一个字段:

    清单 19
     
                                
    @Entity
    @Table(name="CUSTOMER")
    public class Customer {
    	private String name;
    	private CustomerAddress ca;
    
    @Embedded
    @AttributeOverrides({
    @AttributeOverride(name="streetAddress", column=@Column("
    STRT_ADD")),
    @AttributeOverride(name="city", column=@Column("CITY"))
    ... //more
    })
    	public CustomerAddress getCustomerAddress()
    {
    
    ...
    }

    使用特殊的属性覆盖,可以将可嵌入类的字段映射到实体中。备用方法是直接将列映射到可嵌入类。 
  5. 派生属性:瞬态标注

    缺省情况下,所有字段在 JPA 中都是持久性的;但是,您可以将一个字段标注为瞬态的,然后可以使用逻辑派生任何字段。下面是执行派生字段的查询:

    清单 20

                                
    @Transient
    public Integer getTotal() {
    
    	Query totalLineItems = em.createNamedQuery("getOrderTotal");
    	totalLineItems.setParameter("orderId",orderId);
    	Integer result = (Integer)totalLineItems.getSingleResult();
    	return result;
    }
     
  6. 键属性:标注的字段和键类

    JPA 支持多种主键以及各种键的生成。在下面的简单示例中,您可以使用 @Id 标注将实体上的字段标注为主键:

    清单 21

                                
    @Entity
    @Table(name="CUSTOMER")
    public class Customer implements Serializable {
    
    	private Integer id;
    	private String name;
    	private CustomerOrder currentOrder;
    	
    		
    	@Id
    	@Column(name="CUST_ID")
    	public Integer getId() {
    		return id;
    	}
    	public void setId(Integer id) {
    		this.id = id;
    	}
    ...

    还可以为具有组合键的实体创建一个主键类:

    清单 22
     
                                
    public class LineItemId implements Serializable {
    
    	private Integer orderId;
    	private Integer productId;
    	public LineItemId() {
    		super();
    		// TODO Auto-generated constructor stub
    	}
    	@Column(name="ORDER_ID")
    	public Integer getOrderId() {
    		return orderId;
    	}
    	public void setOrderId(Integer orderId) {
    		this.orderId = orderId;
    	}
    	
    	@Column(name="PRODUCT_ID")
    	public Integer getProductId() {
    		return productId;
    	}
    	public void setProductId(Integer productId) {
    		this.productId = productId;
    	}
    	public boolean equals(Object arg0) {
    		if (arg0 == this) return true;
    		    if (!(arg0 instanceof LineItemId)) return false;
    			LineItemId other = (LineItemId)arg0;
    			if(other.getOrderId().equals(orderId) &&
    				other.getProductId().equals(productId))
    			{
    				return true;
    			}
    			return false;
    		
    	}
    	public int hashCode() {
    		return orderId + productId.hashCode();
    	}
    
    }

    然后可以使用 @IdClass 标注在实体上定义组合键:

    清单 23
     
                                
    @Entity
    @Table(name="LINEITEM")
    @IdClass(LineItemId.class)
    public class LineItem implements Serializable {
    

    您的实体类必须在类上具有匹配字段,或者它可以作为可嵌入键嵌入该键:

    清单 24
     
                                
    @Entity
    @Table(name="LINEITEM")
    @IdClass(LineItemId.class)
    public class LineItem implements Serializable {
    	private LineItemId lineItemId;
    
    	@EmbeddedId
    public LineItemId getLineItemId()
    	{
    		return lineItemId;
    	}
    
    ...

    另一个主要增强是支持生成主键。通过使用 @Id 标注的生成属性,可以选择一个不同的策略。例如,您可以选择向 DB2 标识列委派主键生成,如下所示:

    清单 25
     
                                
    @Id(generate=GeneratorType.IDENTITY)
    @Column(name="ORDER_ID")
    public Integer getOrderId() {
    	return orderId;
    }
    public void setOrderId(Integer orderId) {
    	this.orderId = orderId;
    }

    其他受支持机制包括序列和表生成。 
  7. 关系:标注和联合列

    JPA 对实体之间的关系提供强大的支持。JPA 支持一对一、一对多、多对一和多对多关系。在 JPA 中,关系不是双向的,这与 EJB 2.x 中相同。相反,对象将其他实体声明为成员,并添加标注来定义关系。可以使用特殊属性将关系转换为双向的。下面是具有两个不同关系的 CustomerOrder 类的示例:

    清单 26

                                
    @Entity
    @Table(name="CUSTOMER")
    public class Customer implements Serializable {
    
    	
    	private Integer id;
    	private String name;
    	private CustomerOrder currentOrder;
    	
    		
    	@Id
    	@Column(name="CUST_ID")
    	public Integer getId() {
    		return id;
    	}
    	public void setId(Integer id) {
    		this.id = id;
    	}
    	
    	@Column(name="NAME")
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	@OneToOne(cascade=CascadeType.ALL , fetch=FetchType.EAGER )
    	@JoinColumn(name="OPEN_ORDER_ID",referencedColumnName="ORDER_ID")
    	public CustomerOrder getCurrentOrder() {
    		return currentOrder;
    	}
    	public void setCurrentOrder(CustomerOrder currentOrder) {
    		this.currentOrder = currentOrder;
    	}
    
    }

    在此关系中,通过在 Customer 类中标注 CustomerOrder 属性,将 Customer 与 CustomerOrder 定义为一对一关系。还定义了 JoinColumn 信息。名称属性在 Customer 类映射到的表上定义外键,参考列定义 Table CustomerOrder 映射到的主键。对关系的任何约束都定义为 @OneToOne 标注的属性。CustomerOrder 类如下所示。CustomerOrder 定义了一个 Customer 属性;不过 CustomerOrder 对象有一个返回到 Customer 的一对多关系。这是因为 Customer 只能有一个当前顺序;实际上,一个客户可以有多个顺序,因此 CustomerOrder 显示了这一点。不过,此处的 mappedBy 属性用于定义由 Customer 类将 CustomerOrder 映射到关系的另一侧。此类如下所示:

    清单 27
     
                                
    public class CustomerOrder implements Serializable {
    	private Integer orderId;
    	private String status;
    	private Integer total;
    	private Integer customerId;
    	private Collection<LineItem> lineItems;
    	private Customer customer;
    	
    	@Column(name="CUSTOMER_ID")
    	public Integer getCustomerId() {
    		return customerId;
    	}
    	public void setCustomerId(Integer customerId) {
    		this.customerId = customerId;
    	}
    	
    	@Id(generate=GeneratorType.IDENTITY)
    	@Column(name="ORDER_ID")
    	public Integer getOrderId() {
    		return orderId;
    	}
    	public void setOrderId(Integer orderId) {
    		this.orderId = orderId;
    	}
    	@Column(name="STATUS")
    	public String getStatus() {
    		return status;
    	}
    	public void setStatus(String status) {
    		this.status = status;
    	}
    	@Column(name="TOTAL")
    	public Integer getTotal() {
    		return total;
    	}
    	public void setTotal(Integer total) {
    		this.total = total;
    	}
    
    	@ManyToOne(fetch=FetchType.EAGER,optional=false)
    	  @JoinColumn(name="CUSTOMER_ID",
    	     referencedColumnName="CUST_ID",insertable=false,updatable=false,
    	     nullable=false,unique=true) 
    	  public Customer getCustomer() {
    	     return customer;
    	  }
    	  public void setCustomer(Customer customer) {
    	     this.customer = customer;
    	  }
    	  @OneToMany(mappedBy="customerOrder", cascade=CascadeType.ALL ,
    	     fetch=FetchType.EAGER)
    	  public Collection<LineItem> getLineItems() {
    	     return lineItems;
    	  }
    	  public void setLineItems(Collection<LineItem> lineItems) {
    	     this.lineItems = lineItems;
    
    	}
    
    }

    CustomerOrder 中的另一关系包含 LineItem 对象的集合。这是一个一对多关系。注意,JRE 5 定义泛型的使用,指定集合的类型。此处还使用了特殊的 mappedBy 属性,使关系的另一侧映射成为双向关系。

    清单 28
     
                                
    @Entity
    @Table(name="LINEITEM")
    @IdClass(LineItemId.class)
    public class LineItem implements Serializable {
    	
    	private Product product;
    	private Integer orderId;
    	private Integer productId;
    	private Integer quantity;
    	private Integer total;
    	private CustomerOrder customerOrder;
    	
    	@Column(name="QUANTITY")
    	public Integer getQuantity() {
    		return quantity;
    	}
    	public void setQuantity(Integer quantity) {
    		this.quantity = quantity;
    	}
    	
    	@Column(name="AMOUNT")
    	public Integer getTotal() {
    		return total;
    	}
    	public void setTotal(Integer total) {
    		this.total = total;
    	}
    	
    	  @ManyToOne(fetch=FetchType.EAGER,optional=false)
    	  @JoinColumn(name="PRODUCT_ID",
    	     referencedColumnName="SKU",insertable=false,updatable=false,
    	     nullable=false,unique=true)
    	  public Product getProduct() {
    	     return product;
    	  }
    	  public void setProduct(Product product) {
    	     this.product = product;
    	  }
    	
    	@Column(name="ORDER_ID")
    	public Integer getOrderId() {
    		return orderId;
    	}
    	public void setOrderId(Integer orderId) {
    		this.orderId = orderId;
    	}
    	
    	@Column(name="PRODUCT_ID")
    	public Integer getProductId() {
    		return productId;
    	}
    	public void setProductId(Integer productId) {
    		this.productId = productId;
    	}
    	  @ManyToOne(fetch=FetchType.EAGER,optional=false)
    	  @JoinColumn(name="ORDER_ID",
    	     referencedColumnName="ORDER_ID",insertable=false,updatable=false,
    	     nullable=false,unique=true)
    	  public CustomerOrder getCustomerOrder() {
    	     return customerOrder;
    	  }
    	  public void setCustomerOrder(CustomerOrder customerOrder) {
    	     this.customerOrder = customerOrder;
    	  }
    
    }
    LineItem 类有一个 CustomerOrder 属性。这里定义了一个多对一关系,如上面所示。类似地,LineItem 类与产品对象也具有多对一关系。

    另一映射类型是,表可能有一对一关系,但对象模型只有一个对象。换句话说,您希望在跨多个表映射单一对象(在某种程度上与依赖对象相反)。可以通过添加一个或多个辅助表做到这一点。下面是跨 Customer 和 Order 表映射客户对象的一个示例。

    清单 29

                                
    @Entity
    @Table(name="CUSTOMER")
    @SecondaryTable(name="ORDER ",
    pkJoin=@PrimaryKeyJoinColumn(name="CUST_ID"))
    public class Customer { ... }
     
  8. 约束:标注和数据库

    JPA 除支持各种数据库约束外,还可以使用它定义对各种关系的约束:

    清单 30

                                
    @OneToMany(mappedBy="customerOrder", cascade=CascadeType.ALL , 
    fetch=FetchType.EAGER)
    	public Collection<LineItem> getLineItems() {
    		return lineItems;
    	}
    	public void setLineItems(Collection<LineItem> lineItems) {
    		this.lineItems = lineItems;
    	}

    在本例中显示了级联影响。例如,如果删除了客户,也将删除客户的顺序。下面是一个具有更多约束的示例:

    清单 31
     
                                
    @ManyToOne(fetch=FetchType.EAGER,optional=false)
    @JoinColumn(name="CUSTOMER_ID", referencedColumnName="CUST_ID",
    	insertable=false,updatable=false, nullable=false,unique=true) 
    
    public Customer getCustomer() {
    	return customer;
    }
    public void setCustomer(Customer customer) {
    	this.customer = customer;
    }

    在上例中,跨外键(也是主键的一部分)定义了多对一关系。在本例中,不允许任何人更新或插入同样标记为唯一和不得为空的列。此关系也指定为不可选。JPA 还可让您在特定实体上定义唯一约束,如下所示:

    清单 32
     
                                
    @Entity
    @Table(
    name="EMPLOYEE",
    uniqueConstraints=
    {@UniqueConstraint(columnNames={"EMP_ID", "EMP_NAME"})}
    )

  9. 继承:标注——单个表、联合表和按类表

    JPA 规范定义了对映射继承的三种类型的支持;其中只有一种在当前规范中是强制的:

    • 单个表:对象树映射到一个表。
    • 联合表:(可选)将子类映射到与父类映射到的表具有外键关系的表。
    • 按类表:(可选)每个具体的子类映射到一个表,并且包含超类属性的列。

    下面是一个使用单表策略映射超类和子类的示例:

    清单 33

                                
    @Entity
    @Table(name="CUST")
                                @Inheritance(strategy=SINGLE_TABLE,
                                discriminatorType=STRING,
    
                                discriminatorValue="CUST")
    public class Customer { ... }
    
    
    @Entity
    @Inheritance(discriminatorValue="VCUST")
    public class ValuedCustomer extends Customer { ... }

    下面是一个使用联合表策略的示例:

    清单 34
     
                                
    @Entity
    @Table(name="CUST")
                                @Inheritance(strategy=JOINED,
                                discriminatorType=STRING,
                                discriminatorValue="CUST")
    public class Customer { ... }
    
    @Entity
    @Table(name="VCUST")
                                @Inheritance(discriminatorValue="VCUST")
                                @PrimaryKeyJoinColumn(name="CUST_ID")
    public class ValuedCustomer extends Customer { ... }

请参阅参考资料,以了解 JPA 中提供的更多其他功能。

JAX-WS

Java EE 5 还为 Web 服务引入了一个新的编程模型:JAX-WS。在了解不同之处之前,您需要知道从 JAX-RPC 到 JAX-WS 哪些内容没有发生变化:

  • JAX-WS 仍然支持 SOAP 1.1 over HTTP 1.1,因此互操作性将不会受到影响。仍然可以在网上传递相同的消息。
  • JAX-WS 仍然支持 WSDL 1.1,因此您所学到的有关该规范的知识仍然有用。新的 WSDL 2.0 规范已经接近完成,但在 JAX-WS 2.0 相关工作结束时其工作仍在进行中。

系列文章 Web 服务提示与技巧:JAX-RPC 与 JAX-WS 的比较包含关于 JAX-WS 的详细信息,下面大致列出了从 JAX-RPC 1.1 到 JAX-WS 2.0 都发生了哪些更改:

  • SOAP 1.2:JAX-RPC 和 JAX-WS 都支持 SOAP 1.1。JAX-WS 还支持 SOAP 1.2。
  • XML/HTTP:WSDL 1.1 规范在 HTTP 绑定中定义,这意味着利用此规范可以在不使用 SOAP 的情况下通过 HTTP 发送 XML 消息。JAX-RPC 忽略了 HTTP 绑定。而 JAX-WS 添加了对其的支持。
  • WS-I Basic Profile:JAX-RPC 支持 WS-I Basic Profile (BP) V1.0。JAX-WS 支持 BP 1.1。(WS-I 即 Web 服务互操作性组织。)
  • 新的 Java 功能:JAX-RPC 映射到 Java 1.4。JAX-WS 映射到 Java 5.0。JAX-WS 依赖于 Java 5.0 中的很多新功能。(Java EE 5 是 J2EE 1.4 的后续版本,添加了对 JAX-WS 的支持,但仍然支持 JAX-RPC,这可能会对 Web 服务新手造成混淆。)
  • 数据映射模型:JAX-RPC 具有自己的映射模型,此模型大约涵盖了所有模式类型中的 90%。那些没有包括的模式被映射到 javax.xml.soap.SOAPElement(JAX-WS 数据映射模型是 JAXB,该模型映射所有 XML 模式)。
  • 接口映射模型:JAX-WS 的基本接口映射模型与 JAX-RPC 的区别并不大,不过 JAX-WS 模型使用新的 Java 5.0 功能,并引入了异步功能。
  • 动态编程模型:JAX-WS 的动态客户端模式与 JAX-RPC 模式差别很大。根据业界的需要进行了许多更改,其中包括面向消息的功能和动态异步功能。JAX-WS 还添加了动态服务器模型,而 JAX-RPC 则没有此模型。
  • 消息传输优化机制 (MTOM):JAX-WS 通过 JAXB 添加了对新附件规范 MTOM 的支持,因此应该能够实现附件互操作性。
  • 处理程序模型:从 JAX-RPC 到 JAX-WS 的过程中,处理程序模型发生了很大的变化。JAX-RPC 处理程序依赖于 SAAJ 1.2,而 JAX-WS 处理程序则依赖于新的 SAAJ 1.3 规范。

JAX-WS 还可以与 EJB 3.0 一起使用来简化编程模型。例如,下面的代码显示了将 EJB 3.0 POJO 转换为 Web 服务的简单方法:

清单 35
 
                
@WebService public interface StockQuote {
    public float getQuote(String sym);
}

@Stateless public class QuoteBean implements StockQuote {
	public float getQuote(String sym) { ... }
}

还添加了其他标注以便支持 Web 服务中更高级的功能。JAX-B 提供了 POJO(清单 36)和 XML 模式(清单 37)之间的标准映射,下面给出了它的一个示例:

清单 36
 
                
 @XmlType
public class Trade {
	@XmlElement(name="tickerSymbol")
	public String symbol;
	@XmlAttribute
	int getQuantity() {...}
	void setQuantity() {...}
}

清单 37

                
<xs:complexType name="trade">
	<xs:sequence>
	<xs:element 
		name="tickerSymbol"
		type="xs:string"/>
	</xs:sequence>
	<xs:attribute name="quantity"
		type="xs:int"/>
</xs:complexType>

JavaServer Faces

JavaServer™ Faces (JSF) 到目前为止已有数年的历史,并且受大多数 Java EE 应用服务器支持,例如 IBM® WebSphere Application Server。在 Java EE 5 中,JSF 现在是 Java EE 5 规范的一部分。JSF 为 Java EE 应用程序提供了许多好处:

  • 丰富、可扩展的 UI 组件。
  • 事件驱动。
  • 托管组件状态。
  • 呈现器/客户端独立性。
  • 验证程序。
  • 类型转换。
  • 外部化导航。
  • JavaServer Pages 和 Faces 通用表达语言。

展望 Java EE 6

最近对 JSR 316 (Java EE 6) 提出了一些议案,尽管对其规范定义为时尚早,但该议案突出了以下几个重要主题:

  • 可扩展性:通过添加更多扩展点和更多服务提供程序接口,可以将其他技术简洁高效地插入平台实现,从而实现了可增长性。
  • 概要:概要将根据 JCP 过程的定义参考 Java EE 平台,并且可能包括 Java EE 平台技术的子集或/和不属于基本 Java EE 平台的其他 JCP 技术。专家组还将定义 Java EE Web 概要的第一个版本,这是一个用于 Web 应用程序开发的 Java EE 平台的子集。
  • 技术修剪:Java EE 平台中的一些技术不再具有它们刚引入平台时的相关性。需要有一种方法,能够认真有序地将这些技术从平台中“修剪”掉,并让仍使用这些技术的开发人员受到的影响降到最小,同时使平台能够更健壮地成长。正如该过程定义的那样,专家组将考虑在将来的 Java EE 平台规范中应将某些技术标记为可被移除。这些可能被移除的技术包括:
    • EJB CMP,被 Java Persistence 有效地代替。
    • JAX-RPC,被 JAX-WS 有效地代替。
  • SOA 支持:Java EE 平台已经广泛应用于 SOA 应用程序。随着越来越多的企业认识到 SOA 体系结构的好处,对该平台的功能和互操作性的需要也相应提高。Java EE 6 需要考虑增加对 Web 服务的支持。尽管基本 Web 服务支持现在是 Java SE 6 平台的一部分,但此规范将需要这些技术的更新版本,以便提供更多的 Web 服务支持。服务组件体系结构 (SCA) 定义一些可以在 SOA 环境中由组合应用程序使用的工具。专家组在考虑将所有 SCA 定义的工具都包含在 Java EE 6 平台中是否合适。
  • 其他增加内容:专家组提议在 Java EE 6 中包括下列新的 JSR:
    • 用于容器的 JSR-196 Java 身份验证 SPI。
    • 用于应用服务器的 JSR-236 计时器。
    • 用于应用服务器的 JSR-237 工作管理器。
    • JSR-299 Web Bean。
    • JSR-311 JAX-RS:用于 RESTful Web 服务的 Java API。

预计在以下领域进行进一步更新:

  • Enterprise JavaBeans。
  • Java Persistence API。
  • Servlet。
  • JavaServer Faces。
  • JAX-WS。
  • Java EE Connector API。

具体要包括哪些技术将由专家组根据合作伙伴和客户要求确定。其中一些规范还要考虑快速发展的 Web 2.0 空间。

结束语

Java EE 5 是一个功能强大而重要的发行版,是用于企业开发的最完善的版本。显然,已经采取了一些重要步骤解决了围绕以前 Java 开发的一些问题。EJB 3.0 和 JPA 是功能强大而又易用的技术,而且 JAX-WS 中的增强功能使得 Web 服务的开发比以往更加容易。

参考资料

学习 获得产品和技术

火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织