Java EE 是一个技术体系的统称,它包含了:
EJB 是J2EE 规范的核心内容,也与我们要说的Spring 的诞生密切相关。EJB 2.0 在2001 年8月发布,EJB 的工作方式如下:
EJB 提供了一种组件模式,使得开发人员可以仅关注系统业务方面的开发,而忽略中间件需求,比如:组件、远程调用、事务管理、持久化等等。在需要的时候开发人员可以随意将需求的中间件服务添加到系统中。至少从表面上看,这一切非常完美和有前途。但是你知道的,事实上并非如此。
EJB 存在的问题?
业务类需要与EJB 框架紧耦合,必须编写多个接口才能创建业务组件
EJB 2.X 要求组件接口interface
和业务逻辑实现类class
都必须从EJB 框架包中扩展接口,这使得开发人员编写的代码和EJB 框架的接口类之间产生了紧耦合。顺带的,我们还必须实现几个不必要的回调方法,例如ejbCreate(), ejbPassivate(), ejbActivate(), ...
。
为了开发一个EJB 组件,开发人员至少需要写三个不同的类,分别针对主程序、远程接口和业务对象:
/**
* 远程接口,使客户端可以远程调用EJB 组件的业务功能
*/
public interface PetService extends EJBObject {
void saveOwner(Owner owner) throws RemoteException;
}
/**
* 主接口,使客户端可以获取EJB 组件的句柄
*/
public interface PetServiceHome extends EJBHome {
PetService create() throws RemoteException, CreateException;
}
/**
* 无状态会话Bean
*/
public class PetServiceBean implements SessionBean {
private SessionContext sessionContext;
// 以下为EJB 要求实现的方法
public void ejbCreate(){}
public void ejbRemote(){}
public void ejbActivate(){}
public void ejbPassivate(){}
public void setSessionContext(SessionContext sessionContext){
this.sessionContext = sessionContext;
}
/**
* 业务方法
*/
public void saveOwner() throws java.rmi.RemoteException {
// 业务代码
}
}
RMI 带来不必要的性能开销。一个J2EE 服务器中同时有Servlet 容器和EJB 容器,不可接受的是Servlet 容器必须通过RMI 来调用EJB。为了避免RMI 这个问题,EJB 最终又引入了本地接口(还记得1里说的“至少”吗?)。
部署时需要些冗长的XML部署描述符,这很不直观且易出错。
难以在容器之外进行单元测试:JNDI 的依赖查找,使得对组件进行单元测试很困难,因为对JDNI 上下文的依赖。
面向过程:EJB 编程模型将开发人员引向了面向过程程序设计风格,数据和行为被分离而不是以内聚的形式在一起。在这里不辩论编程风格,只不过我们使用的Java 是面向对象的编程语言,因此我们肯定想充分利用Java 的优点,不是吗?
正是由于上述的种种问题,EJB 2.X 逐渐被人们厌恶。而与此同时,与EJB 编程模型完全不同的POJO 编程模型发展起来了。
POJO - Plain Old Java Object 一词被发明出来,是为了描述那些:不需要实现任何特定接口或者扩展自某一特定框架类的最简单的类。它也使得我们可以专注于从面向对象的角度来编写代码。
我们的主角来了:
基于POJO 编程模型产生了很多框架,在这其中Spring Framework 是最成功的一个框架,它已经成为事实上的开发JavaEE 应用程序的标准框架。
需要知道的一点是:在EJB 3.X 中,上述问题大部分都已经得到了解决,不过在此之前Spring Framework 已经发展壮大了。另外,在EJB 所有改进中最重要的一点就是EJB 规范引入了POJO 编程模型。显而易见,这在很大程度上要归功于Spring Framework 产生的影响。另外,当前的Spring Framework 已经兼容了很多EJB 规范。例如:@Resource, @PostConstruct, @PreDestroy
。
在EJB 2.X 这个历史时间阶段上,由于其本身的设计问题,导致使用EJB 模型来开发会提高复杂度,这其中最重要的问题是:业务类需要与EJB 框架紧耦合。
而在这个时间节点上诞生的Spring Framework ,给我们提供了更简单易用的一个选择。业务代码纯粹,我们业务代码不必依赖框架中服务的API,且这些服务是可插拔的。(业务代码纯粹、服务可插拔)
2002 年Rod Johnson 在 expert one-on-one J2EE Design and Development 一书中发表了Spring Framework 的第一个版本。
后来他又陆续发布了 expert one-on-one J2EE Development without EJB
以及Professional Java Development with the Spring Framework
Spring Framework 为我们带来了一个轻量级容器,使我们可以在没有被“侵入”的情况下写业务代码。
Java EE 的容器
在Java EE 中有一个“容器”的概念,它是指这样一个环境:在该环境中所有组件都被创建和装配,并且提供了所需要要的中间件服务。
Java EE 提供了多个容器:
Servlet 容器负责创建和管理Web 层组件
比如Servlet、JSP、Filter。
EJB 容器专注于业务层
管理EJB 组件。
轻量级容器
任何容器都应该向它所管理的组件提供一些基本服务。根据expert one-on-one J2EE Development without EJB 一书,可以列出如下所述的一些预期服务:
如果能够再进一步提供一些中间件服务,那就更好了:
一个轻量级容器(lightweight container)包含上述所有功能,但并不需要依赖那些API 来编写代码。也就是说轻量级容器没有侵入特性,在企业级Java 世界中,Spring Framework 就是最著名的轻量级容器。
容器及其管理的组件所提供的最重要的好处是:可插拔的体系结构。组件需要实现一些接口,并且可以通过类似的接口访问其他组件所提供的服务。组件不需要知道这些服务的具体实现类,因此实现是很容易替换的。容器的工作是:创建这些组件以及所依赖的服务,并将这些组件装配在一起。
控制反转:在组件类中,组件不需要实例化其所依赖的其它组件,而是在运行时由容器将被依赖的组件注入到这个组件中。这种模式被称为控制反转(Inversion of Controll),简称IoC,即对依赖的控制权由组件自己反转到了容器。
IoC 是任何容器都要提供的基本功能,它主要有两种实现方式:依赖查找(dependency lookup)和依赖注入(dependency injection):
依赖查找
容器向其管理的组件提供了回调方法,组件通过回调方法来显式地获取它所需要的依赖。通常使用一个“查找上下文”来访问依赖组件。
依赖注入
组件必须提供合适的构造函数或者Setter 方法,以便容器可以为组件注入其依赖的其他组件。
在最初的J2EE 中,所使用的主要方法是依赖查找,上面提到的“查找上下文”在这里就是JNDI。随着Spring Framework 等很多轻量级容器的出现,依赖注入变得流行起来。如今,当开发人员再提及IoC 时,通常会被理解为依赖注入。
依赖注入的基本原则是:应用程序对象不应该负责查找它们所依赖的资源,而是由IoC 容器处理对象的创建和依赖注入。
一个好的容器应该同时支持构造函数注入和Setter 注入。
Setter 注入
当一个对象被实例化后其Setter 方法就会被马上调用。
优点
在组件被配置后,可以在运行时重新配置,这是因为Setter 方法本身是JavaBean 规范的内容,所以外部世界也可以在之后修改其依赖值。
缺点
有可能并不是所有的依赖项都可以在使用前被注入,从而使组件处于一种部分配置状态(Setter 循环依赖)。由于Setter 的顺序无法在组件中得到约定,可能会产生状态不一致的情况。
构造函数注入
优点
每一个被管理的组件都处于一致状态(循环依赖会直接报错),且在创建后可以马上使用。
缺点
无法在组件创建完毕后再对组件进行重新配置。
实际使用中,一般我们会混合使用这两种注入方式。
以上,我们知道了“为何使用Spring Framework?”,以及Spring Framework 为我们带来了什么。