我们现在即将发布《Web Beans》社区评审草案。该草案的目的是收集对我们定义的组件模型、依赖管理模型和可扩展上下文模型的反馈,并希望人们对此产生兴趣。我们还需要将我们的工作展示给其他 EE6 相关专家组,以便他们开始思考如何可能地重新使用和集成我们所定义的一些机制。然而,由于规范本质上是用高度技术性的语言编写的,因此这篇博客条目是关于 Web Beans 的友好性入门指南系列文章的第一篇。当社区评审草案发布时,请花时间下载并审阅它。但请先阅读这个系列/第一篇/。

一点历史背景

首先,一些背景信息。Web Beans 是由 JBoss 启动的,旨在帮助填补 Java EE 5 的空白。EE 5 平台对通过成熟的技术(如 EJB3、JTA、JCA 和 JPA)访问事务性资源有很强的支持。当然,该平台还提供各种广泛使用的 Web 呈现技术,如 Java Servlets、JSP 和 JSF。然而,/Web 层/和/事务层/是独立发展的,错失了开发一个供访问事务性企业资源的 Web 应用程序共享组件模型的机会。今天,Web Beans 由 JBoss、Sun、Oracle 和 Google 的代表以及几位个人成员推动。该组件模型深受 Google GuiceSeam 的影响。

Java EE 的统一组件模型

Web Beans 是一种兼容两个层次的技术的组件模型。Web Beans 与 JSF 和 EJB3 集成,允许 EJB3 会话豆作为 JSF 管理豆,从而统一两种组件模型。此外,Web Beans 提供了 /会话模型/ 和 /持久化上下文管理/,从而解决了影响 JSF 和 JPA 的状态管理问题和乐观事务管理问题。总之,Web Beans 使得构建通过 JPA 访问数据库的 Java EE 网络应用程序变得 /更加容易/。

虽然 Web Beans 为 JSF 和 EJB3 的集成提供了一个甜点,但该组件模型具有更广泛的应用。特别是,它支持在没有 JSF 或 EJB3 的情况下使用。一个早期的问题是 Web Beans 在多大程度上会被限制在 EE 和 EJB3 环境中。小组的一致决定是

  1. Web Bean 不 /必须/ 是一个 EJB
  2. Web Beans 应该能够在 EE 环境之外执行

第一个决定仅仅是承认这样一个事实,即并非每个组件都需要 EJB 提供的服务(事务划分、授权等)。然而,Web Beans 不会重复此功能,因此当需要这些服务时,Web Bean 应该被编写为会话豆。

第二个决定允许组件可以在应用服务器环境之外进行集成/单元测试,并允许在例如批处理过程中重用代码。

一些成员,特别是 Bob Lee,认为我们所做的工作在 EE 平台之外也同样有用,特别是组件模型应该考虑用于 Java SE。然而,作为规范负责人,考虑到我们的 JSR 提案的语言,我已做出决定明确指定目标环境为 Java EE,并将我们的讨论限制在 EE 开发者所需的内容上。

如果在未来,社区和 JCP 对 Web Beans 的部分内容(例如,复杂的 Guice 风格依赖注入引擎)施加压力,我们可以在那时遵循 EJB3 专家组中 JPA 建立的先例,并在 EE 平台之外定义行为。

Web Bean 组件

那么,究竟什么是 Web Bean 呢?

Web Bean 是一个包含业务逻辑的应用组件。Web Bean 可以直接从 Java 代码中调用,或者可以通过统一表达式语言 (EL) 调用。Web Bean 可以访问事务资源。Web Beans 容器自动管理 Web Bean 之间的依赖关系。大多数 Web Bean 都是 /有状态的/ 和 /上下文相关的/。Web Bean 的生命周期始终由容器管理。

让我们退一步。什么是 上下文相关的 呢?由于 Web Bean 可能是有状态的,所以重要的是 /哪一个/ 实例。与无状态组件模型(例如,无状态会话豆)或单例组件模型(例如,servlets)不同,组件的不同客户端以不同的状态看到组件。客户端可见的状态取决于客户端拥有的组件实例。

然而,与无状态或单例模型一样,以及与 JSF 一样,但与有状态会话豆不同,客户端不通过显式创建和销毁它来控制实例的生命周期。相反,一个 /上下文/ 定义

  • 实例的生命周期
  • 此实例对客户端可见的范围

因此,在相同 /范围/ 中执行的客户端(例如,其他 Web Bean)将看到相同的实例。但不同范围的客户端将看到不同的实例。

上下文模型的一个巨大优势是它允许将有状态的组件视为服务!客户端不需要关心管理它所使用的组件的生命周期,甚至不需要知道该生命周期是什么。组件通过传递消息进行交互,组件实现定义它们自身状态的生命周期。组件松散耦合,因为

  • 它们通过定义良好的公共 API 进行交互
  • 它们的生命周期完全解耦

我们可以用一个具有相同API但生命周期(作用域)不同的不同组件来替换一个组件,而不会影响其他组件的实现。实际上,Web Beans在部署时定义了复杂的设施来覆盖组件实现,我们将在下一部分中看到。

不要空谈。更正式地说,根据规范

Web Bean组件包含
  • 组件类型
  • 一个bean实现类或一个解析方法
  • 一组API类型
  • 一组(可能为空)的绑定注解类型
  • 作用域
  • 组件名称

让我们看看这些术语对组件开发者意味着什么。

组件类型

现在我们只需要了解关于/组件类型/的知识是,Web Beans开发者可以定义一些类型的注解作为标记,例如@Mock, @Staging@AustralianTaxLaw这允许整个组件集在特定部署中条件性地安装。我们将在下一部分中更多地讨论这个独特且强大的功能。

一个非常简单的Web Bean可能只使用内置的组件类型@Component:

@Component
public class Credentials { ... }

组件类型注解的存在将这个类识别为Web Bean,告知Web Beans容器。

API类型、绑定注解和依赖注入

Web Beans通常通过依赖注入获取其他Web Bean的引用。任何注入属性都指定了一个必须由要注入的组件满足的《q>契约》。这个契约是

  • 一个API
  • 一组(可能为空)的绑定注解

API是一个用户定义的类或接口。(如果组件是EJB会话Bean,API类型是@Local接口。)一个/绑定注解/是一个用户定义的注解,它自身被注解@BindingType.

容器搜索满足此契约的组件(实现API并支持绑定注解),然后注入该组件。

例如,如果这是注入点

@In @CreditCard PaymentProcessor paymentProcessor;

以下组件可以被注入

@CreditCard @Component
public class CreditCardPaymentProcessor 
    implements PaymentProcessor { ... }

Web Beans定义了一个复杂但直观的/解析算法/,帮助容器决定如果存在多个组件满足特定契约时应该做什么。我们将在下一部分中详细讨论。

组件作用域

作用域定义了组件实例的生命周期和可见性。Web Beans上下文模型是可扩展的,可以容纳任意作用域。然而,某些重要的作用域是规范中内置的,并由Web Beans容器提供。例如,任何Web应用程序都可以访问一个/会话/作用域

@SessionScoped @Component
public class ShoppingCart { ... }

我们将在下一部分中更多地讨论作用域。

组件名称和统一EL

所有Web Beans都可以在统一EL表达式中使用/名称/。定制Web Bean的名称很容易

@SessionScoped @Component @Named("cart")
public class ShoppingCart { ... }

然后我们就可以在JSF页面中轻松使用该组件

<h:dataTable value="#{cart.lineItems}" var="item">
    ....
</h:dataTable>

解析方法和web-beans.xml

大多数Web Bean是通过编写实现类并注解它来定义的。然而,还有两种额外的方式来定义Web Bean

  1. 通过名为web-beans.xml
  2. 编写一个解析方法

我们在下一部分中会介绍web-beans.xml

一个/解析方法/是一个由容器调用以获取组件实例的方法,当当前上下文中不存在实例时。例如

@SessionScoped @Component
public class Login {

    User user;
    ...
    
    public void login() {
        user = ...;
    }
    
    @Resolves @LoggedIn User getCurrentUser() {
        return user;
    }

}

解析方法是一等Web Beans组件。再次,我们将在下一部分中更多地讨论解析方法。

登录

让我们通过具体化之前的例子来阐述这些想法。我们将实现用户登录/登出功能。首先,我们将定义一个组件来存储登录过程中输入的用户名和密码

@Component
public class Credentials {
	
    private String username;
    private String password;
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    
}

该组件绑定到以下JSF表单中的登录提示

<f:form>
    <h:panelGrid columns="2" rendered="#{!login.isLoggedIn}">
        <h:outputLabel for="username">Username:</h:outputLabel>
        <h:inputText id="username" value="#{credentials.username}"/>
        <h:outputLabel for="password">Password:</h:outputLabel>
        <h:inputText id="password" value="#{credentials.password}"/>
    </h:panelGrid>
    <h:commandButton value="Login" action="#{login.login}" rendered="#{!login.isLoggedIn}"/>
    <h:commandButton value="Logout" acion="#{login.logout}" rendered="#{login.isLoggedIn}"/>
</f:form>

实际工作由一个会话作用域的组件完成,该组件维护当前登录用户的信息并暴露用户实体给其他组件

@SessionScoped @Component
public class Login {

    @In Credentials credentials;
    @In @UserDatabase EntityManager userDatabase;

    private User user;
    
    public void login() {
    	
        List<User> results = userDatabase.createQuery(
           "select u from User u where u.username=:username and u.password=:password")
           .setParameter("username", credentials.getUserName())
           .setParameter("password", credentials.getPassword())
           .getResultList();
        
        if ( !results.isEmpty() ) {
           user = results.get(0);
        }
        
    }
    
    public void logout() {
        user = null;
    }
    
    public boolean isLoggedIn() {
       return user!=null;
    }
    
    @Resolves @LoggedIn User getCurrentUser() {
        return user;
    }

}

当然,@LoggedIn是一个绑定注解

@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD})
@BindingType
public @interface LoggedIn {}

现在,任何其他组件都可以轻松注入当前用户

@Component
public class DocumentEditor {

    @In @Current Document document;
    @In @LoggedIn User currentUser;
    @In @DocumentDatabase EntityManager docDatabase;
    
    public void save() {
        document.setCreatedBy(currentUser);
        docDatabase.persist(document);
    }
    
}

请大家继续关注!

希望这能让您对Web Beans组件模型有一个初步的了解。还有很多东西可以讨论,希望您能抽出时间跟随本系列的其余部分。


返回顶部