第一部分: 介绍Web Bean
我们现在真的临近发布一个Web bean的社区预览草案。该草案的目的是收集关于我们定义的组件模型,依赖管理模型和扩展上下文模型的反馈,希望让人们对Web
Bean感兴趣。我们也需要在其他的EE6 相关专家组的前面开展我们的工作,以至于它们可以考虑如何同我们已定义的一些机制尽可能的重用和集成。然而,该规范本质就
是用高级技术性语言写的,因此在此blog是第一个以系列文章的友好形式介绍Web bean(也就是说我的翻译是国人第一份,哈哈。)当社区预览草案发布以后,请花时
间下载和审查它。但是请先读此系列文章。
历史回顾
首先介绍一下背景。Web bean 是由 JBoss 创造的,帮助填平Java EE5的裂隙。EE 5平台对用成熟的技术包括EJB3,JTA,JCA和JPA来访问事务资源提供了极大
支持。当然,此平台也以支持广泛的Web表现技术例如Java Servlets,JSP和JSF作为特征。然而,Web层和事务层彼此独立发展,已经失去了发展一个为访问企业事务
资源的Web应用共享的组件模型的机会。今天,Web Bean正在以JBoss,Sun,Oracle和Google作为代表,以及一些独立成员支持下发展。组件模型受到Google Guice
和Seam的极大影响。
Java EE的一个统一组件模型
Web bean是一个同两层技术相兼容的组件模型。Web bean同JSF和EJB3集成,允许一个EJB3会话bean作为一个JSF可管理的活动,因此同这两个组件模型兼容。另外,Web bean提供一个会话模型和持久化上下文管理,因此解决了影响JSF和JPA的状态管理问题和优化事务管理问题。总而言之,Web bean 使建立通过JPA访问数据库的Java EE Web应用程序更容易开发。当web bean为JSF和EJB3的集成提供一个轻快实现时,组件模型变得更通用。特别是,它支持没有JSF或没有EJB3的情况下的使用。较早提出的一个问题是某种程度上Web bean被限制在EE和EJB3环境下。专家组无异议的决定是:
- 一个Web bean不必是一个EJB;
- Web bean应该可以在容器外执行。
第一个决定只认识到不是每一个组件都需要EJB服务(例如声明式事务,授权等)的事实。然而,Web bean将不复制此项功能,所以当需要这些服务时,Web bean需要写成会话bean。第二个决定允许组件在应用服务器外面集成或单元测试,允许代码级重用,例如一个批处理过程。
一些会员,尤其Bob Lee,认为我们已做的工作只是在EE平台外面有用,并且特别是组件模型应考虑Java SE中的使用。然而,作为规范领导和根据我们JSR提议的语言,我做出决定清晰的指定目标环境是Java EE,限制我们的争论在EE开发者需要什么。
如果将来,有来自社区和JCP的压力开放Web bean的一部分(例如高级Guice样式依赖注入引擎)的话,我们能够在那时跟从在EJB3专家组有JPA建立的先例,定义EE平台以外的行为。
Web bean 组件
精确地说一个Web bean是什么?
一个Web bean是一个包含业务逻辑的应用组件。一个Web bean可以从Java 代码中被调用,或者通过统一了的EL调用。一个Web bean 可以访问事务资源。两个Web bean的依赖由Web bean容器自动管理。大多数Web Bean是有状态和上下文的。一个Web bean的生命周期总是由容器管理。让我们稍微回顾一下。它对于上下文意味着什么呢?因为Web bean是有状态的,所以它同我有哪一个bean实例有关。同一个无状态组件模型(无状态会话bean)或者单例组件模型(例如serlets)不一样,一个组件的不同客户端看到组件的不同状态。客户端可见状态依赖于客户端有哪一个组件实例的引用。
然而,象一个无状态会话bean或单例模型一样,象JSF,但是与有状态会话bean不一样,客户端不通过显式创建和破坏它来控制实例的生命周期。作为代替,一个上下文定义:
所以在相同作用域执行的客户端(例如其它Web bean)将看到同一实例。但是,在不同作用域的客户端将看到不同实例。上下文模型一个很大的优势是它允许象创建服务一样创建有状态组件。客户端不需要它使用的管理的组件生命周期关注它自己,也不需要知道生命周期是什么。组件通过传送消息交互,组件实现定义了它们自己状态的生命周期。之所以此组件是松散耦合的,是因为:
- 它们通过定义良好的公共API交互
- 它们的生命周期完全被解耦
我们能够用实现相同API和有一个不影响其它组件实现的不同生命周期的另一个不同组件替代组件。实际上,Web bean为在足够人工拦截的重写组件实现定义了一个部署时的高级功能,这将在我们将来安装时看到。更正规的是,根据该规范:
一个Web bean组件包括:
- 组件类型
- 或这是一个bean实现类或者是解析方法
- 一系列的API类型
- 一系列(可能为空)的绑定批注类型
- 作用域
- 组件名
让我们看一看这些条目对于组建开发者来说意味着什么。
组件类型
我们现在关于组件类型所需要知道的是一个Web bean开发者可能定义一些享元作为批注,例如允许全部组件集合在特定系统部署中有条件的安装的@Mock, @Staging 或者 @AustralianTaxLaw。我们在后一节中的更多讨论这个独一无二的强大功能。
一个非常简单的Web bean可能仅使用内置的组件类型@Component:
@Component
public class Credentials { ... }
组件前场景类型批注识别此类作为一个Web bean到Web bean容器中。
API 类型,绑定批注和依赖注入
Web bean通常通过依赖注入获得对于其它Web bean的引用。任何注入型参数指定一个组件必须满足的契约。契约为:
一个API是用户定义类或者接口。(如果组件是一个EJB会话bean,那么此API类型是@Local接口。)一个绑定批注是一个本身用@BindingType注解的用户定义批注。容器搜索满足契约(实现此API和支持绑定批注)的组件,注入那个组件。例如,如果这个是注入点:
@In @CreditCard PaymentProcessor paymentProcessor;
以下组件将被注入:
@CreditCard @Component
public class CreditCardPaymentProcessor
implements PaymentProcessor { ... }
Web bean定一个高级但是直接解析算法,它帮助容器决定如果超过一个组件满足一个指定契约的话将做什么。我们将在以后文章中详细介绍。
组件作用域
作用域定义组件实例的生命周期和可见性。Web bean上下文模型是可扩展的,The scope defines the lifecycle and visibility of instances of the component. The Web Beans context model is extensible, 包含模糊作用域。然而,对于规范来说某个重要的作用域是内置的,有Web bean容器来提供。例如,任何Web 应用程序都可访问一个会话作用域:
@SessionScoped @Component
public class ShoppingCart { ... }
我们将在以后文章中详细介绍作用域。
组件名和统一表达式语言(EL)
所有Web bean可以通过名字在统一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的方式:
- 通过一个XML部署描述文件web-beans.xml
- 通过写一个解析方法
我们在将来的文章中介绍web-beans.xml文件。
一个解析方法是一在当前上下文中没有实例存在时由容器调用它来获得一个组件实例的方法。例如:
@SessionScoped @Component
public class Login {
User user;
...
public void login() {
user = ...;
}
@Resolves @LoggedIn User getCurrentUser() {
return user;
}
}
一个解析方法是第一个类Web bean组件。再一次的告诉大家,后面章节继续介绍它。
登录
让我们通过梳理以下前面的例子来描述一下这些观点。我们将实现用户登录/注出。首先我们将定义一个组件包含用户名和密码,这些将在登录过程中用到:
@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 action="#{login.login}" rendered="#{!login.isLoggedIn}"/>
<h:commandButton 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 bean组件模型提供帮助。有更多的东西需要讨论,我希望您将有时间看一看此系列文章的剩余部分。
——————来自牛哄哄的Hibernate之父 Gavin King
凡是有该标志的文章,都是该blog博主Caoer(草儿)原创,凡是索引、收藏
、转载请注明来处和原文作者。非常感谢。