分类: Java
2010-04-19 15:39:01
4.3.4 继承?我也会!
除了单独存在的bean以及多个bean之间的横向依赖关系,我们也不能忽略“纵向上”各个bean之间的关系。确切来讲,我其实是想说“类之间的继承关系”。不可否认,继承可是在面向对象界声名远扬啊。
假设我们某一天真的需要对FXNewsProvider使用继承进行扩展,那么可能会声明如下代码所示的子类定义:
class SpecificFXNewsProvider extends FXNewsProvider
{
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;
...
}
实际上,我们想让该子类与FXNewsProvider使用相同的IFXNewsPersister,即DowJonesNews- Persister,那么可以使用如代码清单4-25所示的配置。
代码清单4-25 使用同一个IFXNewsPersister依赖对象的FXNewsProvider和SpecificFXNews-
Provider配置内容
但实际上,这种配置存在冗余,而且也没有表现两者之间的纵向关系。所以,我们可以引入XML中的bean的“继承”配置,见代码清单4-26。
代码清单4-26 使用继承关系配置的FXNewsProvider和SpecificFXNewsProvider
class="..SpecificFXNewsProvider">
我们在声明subNewsProvider的时候,使用了parent属性,将其值指定为superNewsProvider,这样就继承了superNewsProvider定义的默认值,只需要将特定的属性进行更改,而不要全部又重新定义一遍。
parent属性还可以与abstract属性结合使用,达到将相应bean定义模板化的目的。比如,我们还可以像代码清单4-27所演示的这样声明以上类定义。
代码清单4-27 使用模板化配置形式配置FXNewsProvider和SpecificFXNewsProvider
class="..FXNewsProvider">
class="..SpecificFXNewsProvider">
newsProviderTemplate的bean定义通过abstract属性声明为true,说明这个bean定义不需要实例化。实际上,这就是之前提到的可以不指定class属性的少数场景之一(当然,同时指定class和abstract="true"也是可以的)。该bean定义只是一个配置模板,不对应任何对象。superNews- Provider和subNewsProvider通过parent指向这个模板定义,就拥有了该模板定义的所有属性配置。当多个bean定义拥有多个相同默认属性配置的时候,你会发现这种方式可以带来很大的便利。
另外,既然这里提到abstract,对它就多说几句。容器在初始化对象实例的时候,不会关注将abstract属性声明为true的bean定义。如果你不想容器在初始化的时候实例化某些对象,那么可以将其abstract属性赋值true,以避免容器将其实例化。对于ApplicationContext容器尤其如此,因为默认情况下,ApplicationContext会在容器启动的时候就对其管理的所有bean进行实例化,只有标志为abstract的bean除外。
4.3.5 bean的scope
BeanFactory除了拥有作为IoC Service Provider的职责,作为一个轻量级容器,它还有着其他一些职责,其中就包括对象的生命周期管理。
本节主要讲述容器中管理的对象的scope这个概念。多数中文资料在讲解bean的scope时喜欢用“作用域”这个名词,应该还算贴切吧。不过,我更希望告诉你scope这个词到底代表什么意思,至于你怎么称呼它反而不重要。
scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。打个比方吧!我们都是处于社会(容器)中,如果把中学教师作为一个类定义,那么当容器初始化这些类之后,中学教师只能局限在中学这样的场景中;中学,就可以看作中学教师的scope。
Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另外三种scope类型,即request、session和global session类型。不过这三种类型有所限制,只能在Web应用中使用。也就是说,只有在支持Web应用的ApplicationContext中使用这三个scope才是合理的。
我们可以通过使用
DTD:
XSD:
让我们来看一下容器提供的这几个scope是如何限定相应对象的吧!
1. singleton
配置中的bean定义可以看作是一个模板,容器会根据这个模板来构造对象。但是要根据这个模板构造多少对象实例,又该让这些构造完的对象实例存活多久,则由容器根据bean定义的scope语意来决定。标记为拥有singleton scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出,也就是说,它与IoC容器“几乎”拥有相同的“寿命”。
图4-5是Spring参考文档中所给出的singleton的bean的实例化和注入语意演示图例,或许可以更形象地说明问题。
图4-5 singleton scope
需要注意的一点是,不要因为名字的原因而与GoF所提出的Singleton模式相混淆,二者的语意是不同的: 标记为singleton的bean是由容器来保证这种类型的bean在同一个容器中只存在一个共享实例; 而Singleton模式则是保证在同一个Classloader中只存在一个这种类型的实例。
可以从两个方面来看待singleton的bean所具有的特性。
q对象实例数量。singleton类型的bean定义,在一个容器中只存在一个共享实例,所有对该类型bean的依赖都引用这一单一实例。这就好像每个幼儿园都会有一个滑梯一样,这个幼儿园的小朋友共同使用这一个滑梯。而对于该幼儿园容器来说,滑梯实际上就是一个singleton的bean。
q 对象存活时间。singleton类型bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。
通常情况下,如果你不指定bean的scope,singleton便是容器默认的scope,所以,下面三种配置形式实际上达成的是同样的效果:
2. prototype
针对声明为拥有prototype scope的bean定 义,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但 是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对 象的销毁。也就是说,容器每次返回给请求方一个新的对象实例之后,就任由这个对象实例“自生自灭”了。
让我们继续幼儿园的比喻,看看prototype在这里应该映射到哪些事物。儿歌里好像有句“排排坐,分果果”,我们今天要分苹果咯!将苹果的bean定义的scope声明为prototype,在每个小朋友领取苹果的时候,我们都是分发一个新的苹果给他。发完之后,小朋友爱怎么吃怎么吃,爱什么时候吃什么时候吃。但是,吃完后要记得把果核扔到果皮箱哦! 而如果你把苹果的bean定义的scope声明为singleton会是什么情况呢?如果第一个小朋友比较谦让,那么他可能对这个苹果只咬一口,但是下一个小朋友吃多少就不知道了。当吃得只剩一个果核的时候,下一个来吃苹果的小朋友肯定要哭鼻子的。
所以,对于那些请求方不能共享使用的对象类型,应该将其bean定义的scope设置为prototype。这样,每个请求方可以得到自己对应的一个对象实例,而不会出现上面“哭鼻子”的现象。通常,声明为prototype的scope的bean定义类型,都是一些有状态的,比如保存每个顾客信息的对象。
从Spring 参考文档上的这幅图片(见图4-6),你可以再次了解一下拥有prototype scope的bean定义,在实例化对象并注入依赖的时候,它的具体语意是个什么样子。
图4-6 prototype scope
你用以下形式来指定某个bean定义的scope为prototype类型,效果是一样的:
3. request、session和global session
这三个scope类型是Spirng 2.0之后新增加的,它们不像之前的singleton和prototype那么“通用”,因为它们只适用于Web应用程序,通常是与XmlWebApplicationContext共同使用,而这些将在第6部分详细讨论。不过,既然它们也属于scope的概念,这里就简单提几句。
注意 只能使用scope 属性才能指定这三种“bean的scope类型”。也就是说,你不得不使用基于XSD文档声明的XML配置文件格式。
l request
request通常的配置形式如下:
Spring容器,即XmlWebApplicationContext会为每个HTTP请求创建一个全新的Request- Processor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求返回10个全新的RequestProcessor对象实例,且它们之间互不干扰。从不是很严格的意义上说,request可以看作prototype的一种特例,除了场景更加具体之外,语意上差不多。
l session
对于Web应用来说,放到session中的最普遍的信息就是用户的登录信息,对于这种放到session中的信息,我们可使用如下形式指定其scope为session:
Spring容器会为每个独立的session创建属于它们自己的全新的UserPreferences对象实例。与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面真是没什么差别。
l global session
还是userPreferences,不过scope对应的值换一下,如下所示:
global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。如果在普通的基于servlet的Web应用中使用了这个类型的scope,容器会将其作为普通的session类型的scope对待。
4. 自定义scope类型
在Spring 2.0之后的版本中,容器提供了对scope的扩展点,这样,你可以根据自己的需要或者应用的场景,来添加自定义的scope类型。需要说明的是,默认的singleton和prototype是硬编码到代码中的,而request、session和global session,包括自定义scope类型,则属于可扩展的scope行列,它们都实现了org.springframework.beans.factory.config.Scope接口,该接口定义如下:
public interface Scope {
Object get(String name, ObjectFactory objectFactory);
Object remove(String name);
void registerDestructionCallback(String name, Runnable callback);
String getConversationId();
}
要实现自己的scope类型,首先需要给出一个Scope接口的实现类,接口定义中的4个方法并非都是必须的,但get和remove方法必须实现。我们可以看一下 _efficinet_id_generator中提到的一个ThreadScope的实现(见代码清单4-28)。
代码清单4-28 自定义的ThreadScope的定义
public class ThreadScope implements Scope {
private final ThreadLocal threadScope = new ThreadLocal() {
protected Object initialValue() {
return new HashMap();
}
};
public Object get(String name, ObjectFactory objectFactory) {
Map scope = (Map) threadScope.get();
Object object = scope.get(name);
if(object==null) {
object = objectFactory.getObject();
scope.put(name, object);
}
return object;
}
public Object remove(String name) {
Map scope = (Map) threadScope.get();
return scope.remove(name);
}
public void registerDestructionCallback(String name, Runnable callback) {
}
...
}
更多Scope相关的实例,可以参照同一站点的一篇文章“More fun with Spring scopes”( com/eu/entry/more_fun_with_spring_scopes),其中提到PageScope的实现。
有了Scope的实现类之后,我们需要把这个Scope注册到容器中,才能供相应的bean定义使用。通常情况下,我们可以使用ConfigurableBeanFactory的以下方法注册自定义scope:
void registerScope(String scopeName, Scope scope);
其中,参数scopeName就是使用的bean定义可以指定的名称,比如Spring框架默认提供的自定义scope类型request或者session。参数scope即我们提供的Scope实现类实例。
对于以上的ThreadScope,如果容器为BeanFactory类型(当然,更应该实现Configurable- BeanFactory),我们可以通过如下方式来注册该Scope:
Scope threadScope = new ThreadScope();
beanFactory.registerScope("thread",threadScope);
之后,我们就可以在需要的bean定义中直接通过“thread”名称来指定该bean定义对应的scope为以上注册的ThreadScope了,如以下代码所示:
除了直接编码调用ConfigurableBeanFactory的registerScope来注册scope,Spring还提供了一个专门用于统一注册自定义scope的BeanFactoryPostProcessor实现(有关BeanFactoryPost- Processor的更多细节稍后将详述),即org.springframework.beans.factory.config.Custom- ScopeConfigurer。对于ApplicationContext来说,因为它可以自动识别并加载BeanFactoryPost- Processor,所以我们就可以直接在配置文件中,通过这个CustomScopeConfigurer注册来Thread- Scope(如代码清单4-29所示)。
代码清单4-29 使用CustomScopeConfigurer注册自定义scope
在以上工作全部完成之后,我们就可以在自己的bean定义中使用这个新增加到容器的自定义scope“thread”了,如下代码演示了通常情况下“thread”自定义scope的使用:
由于
4.3.6 工厂方法与FactoryBean
在强调“面向接口编程”的同时,有一点需要注意:虽然对象可以通过声明接口来避免对特定接口实现类的过度耦合,但总归需要一种方式将声明依赖接口的对象与接口实现类关联起来。否则,只依赖一个不做任何事情的接口是没有任何用处的。假设我们有一个像代码清单4-30所声明的Foo类,它声明了一个BarInterface依赖。
代码清单4-30 依赖于某一BarInterface接口的Foo类定义
public class Foo
{
private BarInterface barInstance;
public Foo()
{
// 我们应该避免这样做
// instance = new BarInterfaceImpl();
}
// ...
}
如果该类是由我们设计并开发的,那么还好说,我们可以通过依赖注入,让容器帮助我们解除接口与实现类之间的耦合性。但是,有时,我们需要依赖第三方库,需要实例化并使用第三方库中的相关类,这时,接口与实现类的耦合性需要其他方式来避免。
通常的做法是通过使用工厂方法(Factory Method)模式,提供一个工厂类来实例化具体的接口实现类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何变动。代码清单4-31演示了这种做法。
代码清单4-31 使用了工厂方法模式的Foo类可能定义
public class Foo
{
private BarInterface barInterface;
public Foo()
{
// barInterface = BarInterfaceFactory.getInstance();
// 或者
// barInterface = new BarInterfaceFactory().getInstance();
}
...
}
针对使用工厂方法模式实例化对象的方式,Spring的IoC容器同样提供了对应的集成支持。我们所要做的,只是将工厂类所返回的具体的接口实现类注入给主体对象(这里是Foo)。
注意 有关工厂方法模式的信息,可以参考设计模式方面的书籍或者网上有关资源。
1. 静态工厂方法(Static Factory Method)
假设某个第三方库发布了BarInterface,为了向使用该接口的客户端对象屏蔽以后可能对BarInterface实现类的变动,同时还提供了一个静态的工厂方法实现类StaticBarInterface- Factory,代码如下:
public class StaticBarInterfaceFactory
{
public static BarInterface getInstance()
{
return new BarInterfaceImpl();
}
}
为了将该静态工厂方法类返回的实现注入Foo,我们使用以下方式进行配置(通过setter方法注入方式为Foo注入BarInterface的实例):
class指定静态方法工厂类,factory-method指定工厂方法名称,然后,容器调用该静态方法工厂类的指定工厂方法(getInstance),并返回方法调用后的结果,即BarInterfaceImpl的实例。也就是说,为foo注入的bar实际上是BarInterfaceImpl的实例,即方法调用后的结果,而不是静态工厂方法类(StaticBarInterfaceFactory)。我们可以实现自己的静态工厂方法类返回任意类型的对象实例,但工厂方法类的类型与工厂方法返回的类型没有必然的相同关系。
某些时候,有的工厂类的工厂方法可能需要参数来返回相应实例,而不一定非要像我们的getInstance()这样没有任何参数。对于这种情况,可以通过
public class StaticBarInterfaceFactory
{
public static BarInterface getInstance(Foobar foobar)
{
return new BarInterfaceImpl(foobar);
}
}
为了让包含方法参数的工厂方法能够预期返回相应的实现类实例,我们可以像代码清单4-32所演示的那样,通过
代码清单4-32 使用
唯一需要注意的就是,针对静态工厂方法实现类的bean定义,使用
2. 非静态工厂方法(Instance Factory Method)
既然可以将静态工厂方法实现类的工厂方法调用结果作为bean注册到容器中,我们同样可以针对基于工厂类实例的工厂方法调用结果应用相同的功能,只不过,表达方式可能需要稍微变一下。
现在为BarInterface提供非静态的工厂方法实现类,该类定义如下代码所示:
public class NonStaticBarInterfaceFactory
{
public BarInterface getInstance()
{
return new BarInterfaceImpl();
}
...
}
因为工厂方法为非静态的,我们只能通过某个NonStaticBarInterfaceFactory实例来调用该方法(哦,错了,是容器来调用),那么也就有了如下的配置内容:
NonStaticBarInterfaceFactory是作为正常的bean注册到容器的,而bar的定义则与静态工厂方法的定义有些不同。现在使用factory-bean属性来指定工厂方法所在的工厂类实例,而不是通过class属性来指定工厂方法所在类的类型。指定工厂方法名则相同,都是通过factory-method属性进行的。
如果非静态工厂方法调用时也需要提供参数的话,处理方式是与静态的工厂方法相似的,都可以通过
3. FactoryBean
FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,请不要将其与容器名称BeanFactory相混淆。FactoryBean,其主语是Bean,定语为Factory,也就是说,它本身与其他注册到容器的对象一样,只是一个Bean而已,只不过,这种类型的Bean本身就是生产对象的工厂(Factory)。
当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现org.spring- framework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。当然,不使用Fac- toryBean,而像通常那样实现自定义的工厂方法类也是可以的。不过,FactoryBean可是Spring提供的对付这种情况的“制式装备”哦!
要实现并使用自己的FactoryBean其实很简单,org.springframework.beans.factory. FactoryBean只定义了三个方法,如以下代码所示:
public interface FactoryBean {
Object getObject() throws Exception;
Class getObjectType();
boolean isSingleton();
}
getObject()方法会返回该FactoryBean“生产”的对象实例,我们需要实现该方法以给出自己的对象实例化逻辑;getObjectType()方法仅返回getObject()方法所返回的对象的类型,如果预先无法确定,则返回null;isSingleton()方法返回结果用于表明,工厂方法(getObject())所“生产”的对象是否要以singleton形式存在于容器中。如果以singleton形式存在,则返回true,否则返回false;
如果我们想每次得到的日期都是第二天,可以实现一个如代码清单4-33所示的FactoryBean。
代码清单4-33 NextDayDateFactoryBean的定义代码
import org.joda.time.DateTime;
import org.springframework.beans.factory.FactoryBean;
public class NextDayDateFactoryBean implements FactoryBean {
public Object getObject() throws Exception {
return new DateTime().plusDays(1);
}
public Class getObjectType() {
return DateTime.class;
}
public boolean isSingleton() {
return false;
}
}
很简单的实现,不是嘛?
要使用NextDayDateFactoryBean,只需要如下这样将其注册到容器即可:
配置上看不出与平常的bean定义有何不同,不过,只有当我们看到NextDayDateDisplayer的定义的时候,才会知道FactoryBean的魔力到底在哪。NextDayDateDisplayer的定义如下:
public class NextDayDateDisplayer
{
private DateTime dateOfNextDay;
// 相应的setter方法
// ...
}
看到了嘛?NextDayDateDisplayer所声明的依赖dateOfNextDay的类型为DateTime,而不是NextDayDateFactoryBean。也就是说FactoryBean类型的bean定义,通过正常的id引用,容器返回的是FactoryBean所“生产”的对象类型,而非FactoryBean实现本身。
如果一定要取得FactoryBean本身的话,可以通过在bean定义的id之前加前缀&来达到目的。代码清单4-34展示了获取FactoryBean本身与获取FactoryBean“生产”的对象之间的差别。
代码清单4-34 使用&获取FactoryBean的实例演示
Object nextDayDate = container.getBean("nextDayDate");
assertTrue(nextDayDate instanceof DateTime);
Object factoryBean = container.getBean("&nextDayDate");
assertTrue(factoryBean instanceof FactoryBean);
assertTrue(factoryBean instanceof NextDayDateFactoryBean);
Object factoryValue = ((FactoryBean)factoryBean).getObject();
assertTrue(factoryValue instanceof DateTime);
assertNotSame(nextDayDate, factoryValue);
assertEquals(((DateTime)nextDayDate).getDayOfYear(),((DateTime)factoryValue).getDayOfYear());
Spring容器内部许多地方了使用FactoryBean。下面是一些比较常见的FactoryBean实现,你可以参照FactoryBean的Javadoc以了解更多内容。
q JndiObjectFactoryBean
q LocalSessionFactoryBean
q SqlMapClientFactoryBean
q ProxyFactoryBean
q TransactionProxyFactoryBean
4.3.7 偷梁换柱之术
在学习以下内容之前,先提一下有关bean的scope的使用“陷阱”,特别是prototype在容器中的使用,以此引出本节将要介绍的Spring容器较为独特的功能特性:方法注入(Method Injection)以及方法替换(Method Replacement)。
我们知道,拥有prototype类型scope的bean,在请求方每次向容器请求该类型对象的时候,容器都会返回一个全新的该对象实例。为了简化问题的叙述,我们直接将FX News系统中的FXNewsBean定义注册到容器中,并将其scope设置为prototype。因为它是有状态的类型,每条新闻都应该是新的独立个体;同时,我们给出MockNewsPersister类,使其实现IFXNewsPersister接口,以模拟注入FXNewsBean实例后的情况。这样,我们就有了代码清单4-35所展示的类声明和相关配置。
代码清单4-35 MockNewsPersister的定义以及相关配置
public class MockNewsPersister implements IFXNewsPersister {
private FXNewsBean newsBean;
public void persistNews(FXNewsBean bean) {
persistNewes();
}
public void persistNews()
{
System.out.println("persist bean:"+getNewsBean());
}
public FXNewsBean getNewsBean() {
return newsBean;
}
public void setNewsBean(FXNewsBean newsBean) {
this.newsBean = newsBean;
}
}
配置为
当多次调用MockNewsPersister的persistNews时,你猜会得到什么结果?如下代码可以帮助我们揭开答案:
BeanFactory container = new XmlBeanFactory(new ClassPathResource(".."));
MockNewsPersister persister = (MockNewsPersister)container.getBean("mockPersister");
persister.persistNews();
persister.persistNews();
输出:
persist bean:..domain.FXNewsBean@1662dc8
persist bean:..domain.FXNewsBean@1662dc8
从输出看,对象实例是相同的,而这与我们的初衷是相悖的。因为每次调用persistNews都会调用getNewsBean()方法并返回一个FXNewsBean实例,而FXNewsBean实例是prototype类型的,因此每次不是应该输出不同的对象实例嘛?
好了,问题实际上不是出在FXNewsBean的scope类型是否是prototype的,而是出在实例的取得方式上面。虽然FXNewsBean拥有prototype类型的scope,但当容器将一个FXNewsBean的实例注入MockNewsPersister之后,MockNewsPersister就会一直持有这个FXNewsBean实例的引用。虽然每次输出都调用了getNewsBean()方法并返回一个FXNewsBean的实例,但实际上每次返回的都是MockNewsPersister持有的容器第一次注入的实例。这就是问题之所在。换句话说,第一个实例注入后,MockNewsPersister再也没有重新向容器申请新的实例。所以,容器也不会重新为其注入新的FXNewsBean类型的实例。
知道原因之后,我们就可以解决这个问题了。解决问题的关键在于保证getNewsBean()方法每次从容器中取得新的FXNewsBean实例,而不是每次都返回其持有的单一实例。
1. 方法注入
Spring容器提出了一种叫做方法注入(Method Injection)的方式,可以帮助我们解决上述问题。我们所要做的很简单,只要让getNewsBean方法声明符合规定的格式,并在配置文件中通知容器,当该方法被调用的时候,每次返回指定类型的对象实例即可。方法声明需要符合的规格定义如下:
也就是说,该方法必须能够被子类实现或者覆写,因为容器会为我们要进行方法注入的对象使用Cglib动态生成一个子类实现,从而替代当前对象。既然我们的getNewsBean()方法已经满足以上方法声明格式,剩下唯一要做的就是配置该类,配置内容如下所示:
通过
persist bean:..domain.FXNewsBean@18aaa1e
persist bean:..domain.FXNewsBean@a6aeed
哇噢,很帅不是吗?
注意 FXNewsBean的取得实际上可以在相应方法中按需要自行实例化,而不一定非要注册到容器中,从容器中获取。我们只是为了引入prototype的使用“陷阱”以及方法注入功能,才将FXNewsBean以prototype类型注册到容器中供使用。当然,如果愿意你也可以以这种方式使用。在最终输出的结果中,对象引用的数字不一定就是上面的那样。因为每次注入的实例是不同的,所以对应实例的数字也可能不同。在此只需要关注每次同时输出的结果是否相同即可说明问题。
2. 殊途同归
除了使用方法注入来达到“每次调用都让容器返回新的对象实例”的目的,还可以使用其他方式达到相同的目的。下面给出其他两种解决类似问题的方法,供读者参考。
l 使用BeanFactoryAware接口
我们知道,即使没有方法注入,只要在实现getNewsBean()方法的时候,能够保证每次调用Bean- Factory的getBean("newsBean"),就同样可以每次都取得新的FXNewsBean对象实例。现在,我们唯一需要的,就是让MockNewsPersister拥有一个BeanFactory的引用。
Spring框架提供了一个BeanFactoryAware接口,容器在实例化实现了该接口的bean定义的过程中,会自动将容器本身注入该bean。这样,该bean就持有了它所处的BeanFactory的引用。BeanFactory- Aware的定义如下代码所示:
public interface BeanFactoryAware {
void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}
我们让MockNewsPersister实现该接口以持有其所处的BeanFactory的引用,这样MockNews- Persister的定义如代码清单4-36所示。
代码清单4-36 实现了BeanFactoryAware接口的MockNewsPersister及相关配置
public class MockNewsPersister implements IFXNewsPersister,BeanFactoryAware {
private BeanFactory beanFactory;
public void setBeanFactory(BeanFactory bf) throws BeansException {
this.beanFactory = bf;
}
public void persistNews(FXNewsBean bean) {
persistNews();
}
public void persistNews()
{
System.out.println("persist bean:"+getNewsBean());
}
public FXNewsBean getNewsBean() {
return beanFactory.getBean("newsBean");
}
}
配置简化为
如此,可以预见到,输出的结果将与我们所预期的相同:
persist bean:..domain.FXNewsBean@121cc40
persist bean:..domain.FXNewsBean@1e893df
实际上,方法注入动态生成的子类,完成的是与以上类似的逻辑,只不过实现细节上不同而已。
l 使用ObjectFactoryCreatingFactoryBean
ObjectFactoryCreatingFactoryBean是Spring提供的一个FactoryBean实现,它返回一个ObjectFactory实例。从ObjectFactoryCreatingFactoryBean返回的这个ObjectFactory实例可以为我们返回容器管理的相关对象。实际上,ObjectFactoryCreatingFactoryBean实现了BeanFactoryAware接口,它返回的ObjectFactory实例只是特定于与Spring容器进行交互的一个实现而已。使用它的好处就是,隔离了客户端对象对BeanFactory的直接引用。
现在,我们使用ObjectFactory取得FXNewsBean的实例,代码清单4-37给出了对应这种方式的MockNewsPersister实现声明。
代码清单4-37 使用ObjectFactory的MockNewsPersister定义
public class MockNewsPersister implements IFXNewsPersister {
private ObjectFactory newsBeanFactory;
public void persistNews(FXNewsBean bean) {
persistNews();
}
public void persistNews()
{
System.out.println("persist bean:"+getNewsBean());
}
public FXNewsBean getNewsBean() {
return newsBeanFactory.getObject();
}
public void setNewsBeanFactory(ObjectFactory newsBeanFactory) {
this.newsBeanFactory = newsBeanFactory;
}
}
有了以上的类定义之后,我们应该为MockNewsPersister注入相应的ObjectFactory,这也正是ObjectFactoryCreatingFactoryBean闪亮登场的时候,代码清单4-38给出了对应的配置内容。
代码清单4-38 使用ObjectFactoryCreatingFactoryBean的相关配置
看,真有效!
persist bean:..domain.FXNewsBean@ecd7e
persist bean:..domain.FXNewsBean@1d520c4
提示 也可以使用ServiceLocatorFactoryBean来代替ObjectFactoryCreatingFactory- Bean,该FactoryBean可以让我们自定义工厂接口,而不用非要使用Spring的ObjectFactory。可以参照该类定义的Javadoc取得更多信息,Javadoc中有详细的实例,足够让你了解该类的使用和功能。
3. 方法替换
与方法注入只是通过相应方法为主体对象注入依赖对象不同,方法替换更多体现在方法的实现层面上,它可以灵活替换或者说以新的方法实现覆盖掉原来某个方法的实现逻辑。基本上可以认为,方法替换可以帮助我们实现简单的方法拦截功能。要知道,我们现在可是在不知不觉中迈上了AOP的大道哦!
假设某天我看FXNewsProvider不爽,想替换掉它的getAndPersistNews方法默认逻辑,这时,我就可以用方法替换将它的原有逻辑给替换掉。
小心 这里只是为了演示方法替换(Method Replacement)的功能,不要真的这么做。要使用也要用在好的地方,对吧?
首先,我们需要给出org.springframework.beans.factory.support.MethodReplacer的实现类,在这个类中实现将要替换的方法逻辑。假设我们只是简单记录日志,打印简单信息,那么就可以给出一个类似代码清单4-39所示的MethodReplacer实现类。
代码清单4-39 FXNewsProviderMethodReplacer类的定义
public class FXNewsProviderMethodReplacer implements MethodReplacer {
private static final transient Log logger =
LogFactory.getLog(FXNewsProviderMethodReplacer.class);
public Object reimplement(Object target, Method method, Object[] args)
throws Throwable {
logger.info("before executing method["+method.getName()+
"] on Object["+target.getClass().getName()+"].");
System.out.println("sorry,We will do nothing this time.");
logger.info("end of executing method["+method.getName()+
"] on Object["+target.getClass().getName()+"].");
return null;
}
}
有了要替换的逻辑之后,我们就可以把这个逻辑通过
代码清单4-40 FXNewsProvider中使用方法替换的相关配置
...
现在,你猜调用FXNewsProvider的getAndPersistNews方法后,会得到什么结果?输出结果如下所示:
771 [main] INFO ..FXNewsProviderMethodReplacer
- before executing method[getAndPersistNews]
on Object[..FXNewsProvider$$EnhancerByCGLIB$$3fa709d3].
sorry,We will do nothing this time.
771 [main] INFO ..FXNewsProviderMethodReplacer
- end of executing method[getAndPersistNews]
on Object[..FXNewsProvider$$EnhancerByCGLIB$$3fa709d3].
我们把FXNewsProvider的getAndPersistNews方法逻辑给完全替换掉了。现在该方法基本上什么也没做,哇……
最后需要强调的是,这种方式刚引入的时候执行效率不是很高。而且,当你充分了解并应用Spring AOP之后,我想你也不会再回头求助这个特色功能。不过,怎么说这也是一个选择,场景合适的话,为何不用呢?
哦,如果要替换的方法存在参数,或者对象存在多个重载的方法,可以在