1 前言
与EJB2.1相比,EJB3.0规范引入了两个重要概念:依赖注入(DI:Dependency
Injection)和截获器(Interceptor),本文首先介绍了这两个概念并给出相关示例,然后分析了EJB3.0规范在这两方面的不足之处,最终深入探讨了WebLogic
Server 10对它们的支持和扩展。
2 依赖注入
2.1 基本概念
依赖注入是从开源社区的一些著名轻量级容器(如Spring、Pico container)中所发展出来的概念,其主要思想就是由容器而不是对象本身来负责处理对象之间的依赖关系。与传统的服务定位器相比,依赖注入具有易测试、弱侵入性等优点,这也就是为什么在最新的Java
EE 5规范中引入它的原因。
对于EJB3.0来说,依赖注入就是由容器负责查找被依赖的对象并注入到依赖bean中,而bean本身不再需要进行JNDI或者context查询。此外,依赖注入发生在任何业务方法被调用之前,而且支持setter方法注入和域注入两种方式。
通过与标注结合使用,在bean类中声明依赖注入是非常简单的 (当然,也可以在部署描述符文件中声明依赖注入):
- @EJB用于注入EJB业务对象
- @PersistenceUnit 用于注入EntityManagerFactory
- @PersistenceContext 用于注入EntityManager
- @Resource 用于注入其它资源对象,如连接工厂、消息目标等
2.2 示例
@Stateless
public class ServiceBean implements Service {
private javax.sql.DataSource myDS;
@Resource(mappedName=“LocalDataSource")
public void setMyDS(javax.sql.DataSource ds) {this.myDS = ds;}
@EJB(beanName=“AccountBean")
private Account account;
} |
在无状态会话bean ServiceBean中,声明了两个依赖:一个是数据源,一个是业务接口。在运行期间,EJB3.0容器一旦创建了ServiceBean的实例,就会分别通过方法注入和域注入将数据源对象和业务对象注入到ServiceBean中。
3 截获器
3.1 基本概念
作为EJB3.0中提出的新概念,截获器是可以对bean的业务方法和生命周期事件进行拦截的组件。截获器需要由@Interceptors
或 发布描述符文件中相关的标签指定。截获器可以带有状态而且可以进行依赖注入,其生命周期与其所绑定的EJB
bean实例的生命周期一致。
定义在截获器中用于拦截目的的方法被称为截获器方法,其中,针对业务方法的截获器方法通过@AroundInvoke标注或发布描述符文件中相关的标签指定;针对生命周期回调的截获器方法通过@PostConstruct,
@PreDestroy等标注或发布描述符文件中对应的标签指定。
截获器分为四类:
- 缺省截获器:可作用于ejb-jar中定义的所有EJB bean。缺省截获器只能定义在DD中,不存在相应的标注
- 类级截获器:只能作用于所指定的EJB bean
- 方法级截获器:只能作用于所指定的某个EJB bean业务方法,方法级截获器不能用于拦截bean的生命周期事件
- bean 级截获器:又被称为自我截获器,因为截获器同时就是EJB bean本身,此时相关的截获器方法只能作用于该EJB
3.2 截获器链的调用顺序
因为可以为EJB定义多个截获器,所以存在截获器链的调用顺序问题,缺省情况下,以下原则被遵循(请参考下图):
图1:截获器链的缺省调用顺序
- 调用顺序依次是缺省截获器、类级截获器、方法级截获器以及bean级截获器
- 对于同类别的截获器,按照声明的顺序调用
- 总是优先调用父类的截获器方法。
此外,EJB3.0规范还提供了更灵活的选项,详细信息请参考EJB3.0规范中“截获器”一节:
- 在发布描述符文件中设置“exclude-default-interceptors”
可以取消对缺省截获器的调用,而应用“exclude-class-interceptors”则取消对类级截获器的调用
- 为了替换缺省的截获器链调用顺序,可以设置发布描述符文件的“interceptor-order”,下图给出了一种可能的截获器链调用顺序。
图2:由interceptor-order指定的一种可能的截获器链调用顺序
3.3 示例
@Stateless(name="Trader")
@Interceptors(ClassInterceptor.class)
public class TraderBean implements Trader {
@Interceptors(MethodInterceptor.class)
public String sell(String stockSymbol) {
…
}
@AroundInvoke
public Object selfInterceptor(InvocationContext) throws Exception {
…
}
} |
在无状态会话bean TraderBean中,声明了两个截获器:一个类级截获器,一个方法级截获器,此外,通过使用@AroundInvoke标注,TraderBean的方法selfInterceptor被声明为截获器方法,所以TraderBean就是bean级截获器。
4 EJB3.0规范在依赖注入和截获器方面的不足
从依赖注入方面来说,EJB3.0目前支持的注入类型是非常有限的,诸如List、Map之类的集合类型都不能被注入。
另一方面,EJB3.0的截获器可被看作是一种AOP模型,截获器类相当于方面(Aspect),而截获器方法相当于通知(Advice),但这种AOP是非常简单的:
- 不支持引入(Introduction);
- 不支持切入点(Pointcut)模式
- 通过标注告诉容器某个EJB需要使用截获器,从而使得这种AOP模型具有一定的侵入性
5 WebLogic Server 10 EJB3.0容器简介
5.1 Pitchfork 简介
Pitchfork是由Interface21与BEA合作开发的一个具有Apache License的开源项目,作为Spring的“增值”项目,开发Pitchfork有两个目的:
- 当前应用服务器可使用Pitchfork实现Java EE 5中的依赖注入,标注处理以及EJB3.0中的截获器。
- 在Spring容器中支持Java EE 5的标注
5.2 WebLogic Server 10 EJB3.0容器的架构
图3:WebLogic Server 10 EJB3.0容器的架构
WebLogic Server 10完全支持Java EE 5和EJB3.0,其EJB3.0
容器是基于Pitchfork来实现依赖注入与截获器的,而Pitchfork又利用了Spring的依赖注入和AOP。此时,WebLogic
Server 10的用户有两种选择:
- 使用EJB3.0标准模型: EJB bean实例由EJB容器创建,然后由Pitchfork实施Java
EE 5 的依赖注入和截获器,这是WebLogic Server 10的缺省配置。
- 使用Spring扩展模型:EJB bean实例由Spring容器创建并实施Spring的依赖注入,然后由Pitchfork实施Java
EE 5的依赖注入,EJB3.0截获器和Spring AOP。
6 Spring扩展模型
6.1 使用Spring扩展模型的主要开发步骤
- 下载某个版本的Spring类库(最好是由BEA指定的版本),至少需要包括以下jar文件:spring.jar,
aspectjweaver.jar, commons-logging.jar, log4j-1.2.14.jar,如果需要,还可以增加其它jar文件。
- 将这些jar文件加入到WebLogic Server 10的类路径中。
- 创建文本文件Weblogic.application.ComponentFacotry以标识当前应用将使用Spring扩展模型,其内容只有一行:Weblogic.application.SpringComponentFactory。
- 创建/META-INF/services子目录,然后放入前面创建的文件Weblogic.application.ComponentFacotry
- 建立Spring 配置文件,并放入到/META-INF目录下,如果是EJB应用程序,Spring配置文件的名字应该是spring-ejb-jar.xml,如果是Web应用程序,Spring配置文件的名字则是spring-web.xml.
- 为了使EJB bean能利用Spring的依赖注入与AOP,需要同时将其定义在spring-ejb-jar.xml(或spring-web.xml)中,从Spring角度来看,这就是一个普通的Spring
bean,只不过它的id或name必须与对应的EJB name相同。
- 将上述目录文件、相关的Java类、部署描述符等打包成标准的jar文件、war文件或ear文件。
需要注意的是,在Spring配置文件中定义的bean,如果它也是一个EJB bean,则该Spring
bean的class属性会被忽略以避免潜在的冲突。
6.2 示例
示例一:利用Spring的依赖注入。本例将演示两点,(1) 注入一个集合类型,(2) 注入一个Spring
POJO对象
(1) 开发一个bean类
@Stateless(name="AccountManagementBean")
@Remote({examples.AccountManagement.class})
public class AccountManagementBean
{
private List accountList;
private BusinessObject businessObject;
public boolean doBusiness(String account) {
if (accountList.contains(account)) {
businessObject.doBusiness();
return true;
}
return false;
}
public BusinessObject getBusinessObject() {
return businessObject;
}
public void setBusinessObject(BusinessObject businessObject) {
this.businessObject = businessObject;
}
public List getAccountList() {
return accountList;
}
public void setAccountList(List accountList) {
this.accountList = accountList;
}
}
|
(2) 一个POJO
public class BusinessObjectImpl implements BusinessObject
{
private String result;
public void doBusiness() {
System.out.println("I am doing business, the result is: " + result);
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
|
(3) Spring-ejb-jar.xml
…
<beans>
<bean name="BusinessObject" class="examples.BusinessObjectImpl">
<property name="result" value="success" />
</bean>
<bean id="AccountManagementBean">
<property name="businessObject" ref="BusinessObject" />
<property name="accountList">
<list>
<value>Safin</value>
<value>Amy</value>
<value>Albert</value>
<value>Yang</value>
</list>
</property>
</bean>
</beans>
|
示例二:利用Spring 的AOP。本例演示了混合使用JEE interceptor、基于代理的Spring
AOP以及AspectJ的Aspect
(1) 开发一个bean类
@Stateless(name="AccountManagementBean")
@Remote({examples.AccountManagement.class})
public class AccountManagementBean
{
public AccountManagementBean() {}
@Interceptors({examples.JEEInterceptor.class})
public boolean doBusiness(String account) {
System.out.println("I am doing business " + account);
return true;
}
}
|
(2) 开发一个JEE interceptor
public class JEEInterceptor {
@AroundInvoke
private Object intercept(InvocationContext inv) throws Exception{
System.out.println("I am in JEEInterceptor.intercept()");
return inv.proceed();
}
}
|
(3) 开发一个Advice
public class SimpleAdvice implements MethodInterceptor{
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("I am in Spring AOP interceptor");
return invocation.proceed();
}
}
|
(4) 开发一个Pointcut
public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method method, Class cls) {
return "doBusiness".equals(method.getName());
}
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class cls) {
return AccountManagementBean.class.isAssignableFrom(cls);
}
};
}
} |
(5) 开发一个AspectJ Aspect
@Aspect
public class Aspects {
@Around("execution(* examples.AccountManagementBean.doBusiness(..))")
public Object around(ProceedingJoinPoint pjp) {
System.out.println(“I am in AspectJ aspect);
Object retVal = null;
try {
retVal = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return retVal;
}
}
|
(6) Spring-ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean id="AccountManagementBean" />
<bean id="myAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice">
<bean class=”examples.SimpleAdvice” />
</property>
<property name="pointcut">
<bean class="examples.SimpleStaticPointcut" />
</property>
</bean>
<bean id="MyAspect" class="examples.Aspects" />
</beans> |
6.3 优点
提供Spring扩展模型具有如下好处:
- 可以在同一个发布单元中混合使用EJB bean和Spring POJO,而且一个EJB
bean可以同时是一个Spring POJO,因此被EJB容器和Spring容器同时管理
- 有效地克服了EJB3.0 规范在依赖注入和截获器方面的一些缺陷
- 为用户提供更灵活的选择:
- 如果标准的EJB 依赖注入以及截获器可以满足应用需求,或者可移植性是优先考虑的因素,则选用标准模型
- 如果标准的EJB依赖注入以及截获器不能满足应用需求,或者不用考虑可移植性,或者需要集成一些Spring应用,则应选用Spring扩展模型
7 Spring AOP在WebLogic Server 10 Spring扩展模型中的使用限制及说明
7.1 代理生成方式
Spring AOP采用的是一种基于代理的框架,目前有两种生成代理的方式:JDK动态代理和CGLIB。在WebLogic
Server 10的Spring扩展模型中,目前不支持CGLIB方式,这主要是因为生成的EJB类都是final的
7.2 代理创建
在Spring中,代理或者是通过诸如ProxyFactoryBean之类的工厂bean明确创建,或者是通过“自动代理”创建器隐含创建。
在WebLogic Server 10中,Pitchfork已经隐含提供了类似的“自动代理”机制,所以原有的Spring代理创建方式是无效的,换句话说,用户只需要在Spring配置文件中定义目标bean以及对应的Spring
Advisor和AspectJ Aspect, 不需要再定义ProxyFactoryBean和“自动代理”创建器,Pitchfork会自动为我们创建AOP代理
7.3 截获的顺序问题
在Spring扩展模型中,可以混合使用JEE截获器、Spring Advisor以及AspectJ
Aspect,此时的调用顺序为JEE截获器、Spring Advisor以及AspectJ
Aspect。
7.4 bean的实例化模式
缺省方式下,Spring bean是Singleton的,即Spring容器总是返回同一个bean实例,在Spring扩展模型中,这意味着同一个Spring
Advisor或AspectJ Aspect实例将被应用到不同的EJB bean实例上,如果Advisor或Aspect实例是有状态的,这可能会产生问题,为此我们推荐如下的使用方式:
|
|
|
|
带有状态的Advisor或Aspect |
没有状态的Advisor或Aspect |
SLSB & MDB |
不推荐用带有状态的Advisor或Aspect,
除非用户意识到相关的后果并认为这种
后果是可以接收的,此时的实例化模式
取决于具体应用 |
采用Singleton实例化模式 |
SFSB |
正常情况下应该采用的实例化模式是
Prototype,除非用户意识到采用
Singletong的后果并认为这种后果是可以
接收的 |
采用Singleton实例化模式 |
8 结束语
依赖注入和截获器的引入,使得EJB3.0克服了很多EJB2.1的缺点,但与开源社区中流行的轻量级容器相比,EJB3.0无论是在依赖注入的范围还是在AOP的易用性方面都还存在一定的不足。而最近BEA所推出的WebLogic
Server 10不但完全支持EJB3.0,而且对其进行了有效扩展,这使得一方面可以充分利用在开源社区中出于领导地位的Spring在依赖注入和AOP方面的强大能力,另一方面又紧密依托WebLogic
Server 10在事务、安全、集群方面领先的技术,从而为EJB3.0用户开发出易维护、可扩展、高效率的企业应用程序打下坚实的基础。