四、Spring源码-IOC容器BeanFactory

4.1 BeanFactory介绍

  我们前面说过,Spring的IoC容器是一个IoC Service Provider,但是,这只是它被冠以IoC之名的部分原因,我们不能忽略的是“容器”。Spring的IoC容器是一个提供IoC支持的轻量级容器,除了基本的IoC支持,它作为轻量级容器还提供了IoC之外的支持。如在Spring的IoC容器之上,Spring还提供了相应的AOP框架支持、企业级服务集成等服务。Spring的IoC容器和IoC Service Provider所提供的服务之间存在一定的交集,二者的关系如图4-1所示。

在这里插入图片描述

Spring提供了两种容器类型: BeanFactory 和 ApplicationContext 。

  • BeanFactory 。基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景, BeanFactory 是比较合适的IoC容器选择。

  • ApplicationContext 。 ApplicationContext 在 BeanFactory 的基础上构建,是相对比较高级的容器实现,除了拥有 BeanFactory 的所有支持, ApplicationContext 还提供了其他高级特性,比如事件发布、国际化信息支持等,这些会在后面详述。 ApplicationContext 所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于 BeanFactory 来说, ApplicationContext 要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之 BeanFactory 也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext 类型的容器是比较合适的选择。

  通过图4-2,我们可以对 BeanFactory 和 ApplicationContext 之间的关系有一个更清晰的认识。

spring_factory

注意:  ApplicationContext 间接继承自 BeanFactory ,所以说它是构建于 BeanFactory 之上的IoC容器。此外,你应该注意到了, ApplicationContext 还继承了其他三个接口,它们之间的关系,我们将在第5章中详细说明。另外,在没有特殊指明的情况下,以 BeanFactory 为中心所讲述的内容同样适用于 Applica-tionContext ,这一点需要明确一下,二者有差别的地方会在合适的位置给出解释。

  BeanFactory ,顾名思义,就是生产Bean的工厂。当然,严格来说,这个“生产过程”可能不像说起来那么简单。既然Spring框架提倡使用POJO,那么把每个业务对象看作一个JavaBean对象,或许更容易理解为什么Spring的IoC基本容器会起这么一个名字。作为Spring提供的基本的IoC容器,BeanFactory 可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象间依赖关系的绑定。

  BeanFactory 就像一个汽车生产厂。你从其他汽车零件厂商或者自己的零件生产部门取得汽车零件送入这个汽车生产厂,最后,只需要从生产线的终点取得成品汽车就可以了。相似地,将应用所需的所有业务对象交给 BeanFactory 之后,剩下要做的,就是直接从 BeanFactory 取得最终组装完成并且可用的对象。至于这个最终业务对象如何组装,你不需要关心, BeanFactory 会帮你搞定。

  所以,对于客户端来说,与 BeanFactory 打交道其实很简单。最基本地, BeanFactory 肯定会公开一个取得组装完成的对象的方法接口,就像代码清单4-1中真正的 BeanFactory 的定义所展示的那样。

代码清单4-1 BeanFactory 的定义

public interface BeanFactory {

	String FACTORY_BEAN_PREFIX = "&";
	
	Object getBean(String name) throws BeansException;
	
	<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	boolean containsBean(String name);

	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
	
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;

	String[] getAliases(String name);
}

  上面代码中的方法基本上都是查询相关的方法,例如,取得某个对象的方法( getBean )、查询某个对象是否存在于容器中的方法( containsBean ),或者取得某个bean的状态或者类型的方法等。因为通常情况下,对于独立的应用程序,只有主入口类才会跟容器的API直接耦合。

4.2 BeanFactory 的对象注册与依赖绑定方式

  BeanFactory 作为一个IoC Service Provider,为了能够明确管理各个业务对象以及业务对象之间的依赖绑定关系,同样需要某种途径来记录和管理这些信息。上一章在介绍IoC Service Provider时,我们提到通常会有三种方式来管理这些信息。而 BeanFactory 几乎支持所有这些方式,很令人兴奋,不是吗?

4.2.1 直接编码方式

  其实,把编码方式单独提出来称作一种方式并不十分恰当。因为不管什么方式,最终都需要编码才能“落实”所有信息并付诸使用。不过,通过这些代码,起码可以让我们更加清楚 BeanFactory 在底层是如何运作的。下面来看一下我们的FX新闻系统相关类是如何注册并绑定的(见代码清单4-4)。

代码清单4-4 通过编码方式使用 BeanFactory 实现FX新闻相关类的注册及绑定

/**
 * FX提供新闻
 *
 * @author Rab
 * @since 2022-04-24
 */
public class FXNewsProvider {

    private IFXNewsListener newsListener;

    private IFXNewsPersister newsPersister;

    public FXNewsProvider() {

    }

    public FXNewsProvider(IFXNewsListener newsListener, IFXNewsPersister newsPersister) {
        this.newsListener = newsListener;
        this.newsPersister = newsPersister;
    }

    public void getAndPersistNews() {
        newsListener.getNews();
        newsPersister.persistNews();
    }

    public IFXNewsListener getNewsListener() {
        return newsListener;
    }

    public void setNewsListener(IFXNewsListener newsListener) {
        this.newsListener = newsListener;
    }

    public IFXNewsPersister getNewsPersister() {
        return newsPersister;
    }

    public void setNewsPersister(IFXNewsPersister newsPersister) {
        this.newsPersister = newsPersister;
    }
}

/**
 * @author Rab
 * @since 2022-04-24
 */
public class DowJonesNewsListener implements IFXNewsListener {

    @Override
    public void getNews() {
        System.out.println("DowJonesNewsListener");
    }
}

/**
 * @author Rab
 * @since 2022-04-24
 */
public class DowJonesNewsPersister implements IFXNewsPersister {
    @Override
    public void persistNews() {
        System.out.println("DowJonesNewsPersister");
    }
}

/**
 * App
 */
public class App {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        RootBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class);

        RootBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class);

        RootBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class);

        // 将bean定义注册到容器中
        beanFactory.registerBeanDefinition("djNewsProvider", newsProvider);
        beanFactory.registerBeanDefinition("djListener", newsListener);
        beanFactory.registerBeanDefinition("djPersister", newsPersister);

        // 指定依赖关系
        // 1.通过构造方式注入
//        ConstructorArgumentValues argValues = new ConstructorArgumentValues();
//        argValues.addIndexedArgumentValue(0, newsListener);
//        argValues.addIndexedArgumentValue(1, newsPersister);
//        newsProvider.setConstructorArgumentValues(argValues);

        // 2.通过setter方法注入
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.addPropertyValue(new PropertyValue("newsListener", newsListener));
        propertyValues.addPropertyValue(new PropertyValue("newsPersister", newsPersister));
        newsProvider.setPropertyValues(propertyValues);

        FXNewsProvider newsProviderBean = (FXNewsProvider) beanFactory.getBean("djNewsProvider");
        newsProviderBean.getAndPersistNews();
    }
}

  BeanFactory 只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理, DefaultListableBeanFactory就是这么一个比较通用的 BeanFactory 实现类。 DefaultListableBeanFactory 除了间接地实现了 BeanFactory 接口,还实现了 BeanDefinitionRegistry 接口,该接口才是在 BeanFactory 的实现中担当Bean注册管理的角色。基本上, BeanFactory 接口只定义如何访问容器内管理的Bean的方法,各个 BeanFactory 的具体实现类负责具体Bean的注册以及管理工作。BeanDefinitionRegistry 接口定义抽象了Bean的注册逻辑。通常情况下,具体的 BeanFactory 实现类会实现这个接口来管理Bean的注册。它们之间的关系如图4-3所示。

spring_registry

  打个比方说, BeanDefinitionRegistry 就像图书馆的书架,所有的书是放在书架上的。虽然你还书或者借书都是跟图书馆(也就是 BeanFactory ,或许BookFactory可能更好些)打交道,但书架才是图书馆存放各类图书的地方。所以,书架相对于图书馆来说,就是它的“BookDefinitionRegistry”。

  每一个受管的对象,在容器中都会有一个 BeanDefinition 的实例(instance)与之相对应,该BeanDefinition 的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。当客户端向 BeanFactory 请求相应对象的时候, BeanFactory 会通过这些信息为客户端返回一个完备可用的对象实例。 RootBeanDefinition 和 ChildBean-Definition 是 BeanDefinition 的两个主要实现类。

4.2.2 外部配置文件方式

  Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。当然,如果你愿意也可以引入自己的文件格式,前提是真的需要。

  采用外部配置文件时,Spring的IoC容器有一个统一的处理方式。通常情况下,需要根据不同的外部配置文件格式,给出相应的 BeanDefinitionReader 实现类,由 BeanDefinitionReader 的相应实现类负责将相应的配置文件内容读取并映射到 BeanDefinition ,然后将映射后的 BeanDefinition 注册到一个 BeanDefinitionRegistry ,之后, BeanDefinitionRegistry 即完成Bean的注册和加载。当然,大部分工作,包括解析文件格式、装配 BeanDefinition 之类的工作,都是由 BeanDefinition-Reader 的相应实现类来做的, BeanDefinitionRegistry 只不过负责保管而已。整个过程类似于如下代码:

BeanDefinitionRegistry beanRegistry = <某个 BeanDefinitionRegistry 实现类,通常为➥
DefaultListableBeanFactory>;
BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);
beanDefinitionReader.loadBeanDefinitions("配置文件路径");
// 现在我们就取得了一个可用的BeanDefinitionRegistry实例

1.Properties配置格式的加载

  Spring提供了 org.springframework.beans.factory.support.PropertiesBeanDefinitionReader 类用于Properties格式配置文件的加载,所以,我们不用自己去实现 BeanDefinitionReader ,只要根据该类的读取规则,提供相应的配置文件即可。

  对于FXNews系统的业务对象,我们采用如下文件内容(见代码清单4-5)进行配置加载。

代码清单4-5 Properties格式表达的依赖注入配置内容

djNewsProvider.(class)=..FXNewsProvider
# ----------通过构造方法注入的时候-------------
djNewsProvider.$0(ref)=djListener
djNewsProvider.$1(ref)=djPersister
# ----------通过setter方法注入的时候---------
# djNewsProvider.newsListener(ref)=djListener
# djNewsProvider.newPersistener(ref)=djPersister
djListener.(class)=..impl.DowJonesNewsListener
djPersister.(class)=..impl.DowJon
esNewsPersister

  这些内容是特定于Spring的 PropertiesBeanDefinitionReader 的,要了解更多内容,请参照Spring的API参考文档。我们可以很容易地看明白代码清单4-5中的配置内容所要表达的意思。

  • djNewsProvider 作为 beanName ,后面通过 .(class) 表明对应的实现类是什么,实际上使用djNewsProvider.class=… 的形式也是可以的,但Spring 1.2.6之后不再提倡使用,而提倡使用 .(class) 的形式。其他两个类的注册, djListener 和 djPersister ,也是相同的道理。

  • 通过在表示 beanName 的名称后添加 .$[number] 后缀的形式,来表示当前 beanName 对应的对象需要通过构造方法注入的方式注入相应依赖对象。在这里,我们分别将构造方法的第一个参数和第二个参数对应到 djListener 和 djPersister 。需要注意的一点,就是 $0 和 $1 后面的(ref) , (ref) 用来表示所依赖的是引用对象,而不是普通的类型。如果不加 (ref) ,PropertiesBeanDefinitionReader 会将 djListener 和 djPersister 作为简单的String类型进行注入,异常自然不可避免啦。

  • FXNewsProvider 采用的是构造方法注入,而为了演示setter方法注入在Properties配置文件中又是一个什么样子,以便于你更全面地了解基于Properties文件的配置方式,我们在下面增加了setter方法注入的例子,不过进行了注释。实际上,与构造方法注入最大的区别就是,它不使用数字顺序来指定注入的位置,而使用相应的属性名称来指定注入。 newsListener 和newPersistener 恰好就是我们的 FXNewsProvider 类中所声明的属性名称。这印证了之前在比较构造方法注入和setter方法注入方式不同时提到的差异,即构造方法注入无法通过参数名称来标识注入的确切位置,而setter方法注入则可以通过属性名称来明确标识注入。与在Properties中表达构造方法注入一样,同样需要注意,如果属性名称所依赖的是引用对象,那么一定不要忘了 (ref) 。

  当这些对象之间的注册和依赖注入信息都表达清楚之后,就可以将其加载到 BeanFactory 而付诸使用了。而这个加载过程实际上也就像我们之前总体上所阐述的那样,代码清单4-6中的内容再次演示了类似的加载过程。

代码清单4-6 加载Properties配置的 BeanFactory 的使用演示

public static void main(String[] args) {
	DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
	BeanFactory container = (BeanFactory)bindViaPropertiesFile(beanRegistry);
	FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
	newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry) {
	PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(registry);
	reader.loadBeanDefinitions("classpath:../../binding-config.properties");
	return (BeanFactory)registry;
}

  基于Properties的加载方式就是这么简单,所有的信息配置到Properties文件即可,不用再通过冗长的代码来完成对象的注册和依赖绑定。这些工作就交给相应的 BeanDefinitionReader 来做吧!哦,我的意思是,让给 PropertiesBeanDefinitionReader 来做。

2.XML配置格式的加载

  XML配置格式是Spring支持最完整,功能最强大的表达方式。当然,一方面这得益于XML良好的语意表达能力;另一方面,就是Spring框架从开始就自始至终保持XML配置加载的统一性。同Properties配置加载类似,现在只不过是转而使用XML而已。Spring 2.x之前,XML配置文件采用DTD(Document Type Definition)实现文档的格式约束。2.x之后,引入了基于XSD(XML Schema Definition)的约束方式。不过,原来的基于DTD的方式依然有效,因为从DTD转向XSD只是“形式”上的转变,所以,后面的大部分讲解还会沿用DTD的方式,只有必要时才会给出特殊说明。

  如果FX新闻系统对象按照XML配置方式进行加载的话,配置文件内容如代码清单4-7所示。

代码清单4-7 FX新闻系统相关类对应XML格式的配置内容

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<bean id="djNewsProvider" class="..FXNewsProvider">
		<constructor-arg index="0">
			<ref bean="djNewsListener"/>
		</constructor-arg>
		<constructor-arg index="1">
			<ref bean="djNewsPersister"/>
		</constructor-arg>
	</bean>
	<bean id="djNewsListener" class="..impl.DowJonesNewsListener">
	</bean>
	<bean id="djNewsPersister" class="..impl.DowJonesNewsPersister">
	</bean>
</beans>

  我想这段内容不需要特殊说明吧,应该比Properties文件的内容要更容易理解。如果想知道这些内容背后的更多玄机,往后看吧!

  有了XML配置文件,我们需要将其内容加载到相应的 BeanFactory 实现中,以供使用,如代码清单4-8所示。

代码清单4-8 加载XML配置文件的 BeanFactory 的使用演示

public static void main(String[] args){
	DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
	BeanFactory container = (BeanFactory)bindViaXMLFile(beanRegistry);
	FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
	newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaXMLFile(BeanDefinitionRegistry registry) {
	XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
	reader.loadBeanDefinitions("classpath:../news-config.xml");
	return (BeanFactory)registry;
	//return new XmlBeanFactory(new ClassPathResource("../news-config.xml"));
}

  与为Properties配置文件格式提供 PropertiesBeanDefinitionReader 相对应,Spring同样为XML格式的配置文件提供了现成的 BeanDefinitionReader 实现,即 XmlBeanDefinitionReader 。XmlBeanDefinitionReader 负责读取Spring指定格式的XML配置文件并解析,之后将解析后的文件内容映射到相应的 BeanDefinition ,并加载到相应的 BeanDefinitionRegistry 中(在这里是 DefaultListableBeanFactory )。这时,整个 BeanFactory 就可以放给客户端使用了。

  除了提供 XmlBeanDefinitionReader 用于XML格式配置文件的加载,Spring还在 DefaultListableBeanFactory 的基础上构建了简化XML格式配置加载的 XmlBeanFactory 实现。从以上代码最后注释掉的一行,你可以看到使用了 XmlBeanFactory 之后,完成XML的加载和 BeanFactory 的初始化是多么简单。

4.2.3 注解方式

  可能你没有注意到,我在提到 BeanFactory 所支持的对象注册与依赖绑定方式的时候,说的是BeanFactory “几乎”支持IoC Service Provider可能使用的所有方式。之所以这么说,有两个原因。

  • 在Spring 2.5发布之前,Spring框架并没有正式支持基于注解方式的依赖注入;
  • Spring 2.5发布的基于注解的依赖注入方式,如果不使用classpath-scanning功能的话,仍然部分依赖于“基于XML配置文件”的依赖注入方式。

  另外,注解是Java 5之后才引入的,所以,以下内容只适用于应用程序使用了Spring 2.5以及Java 5或者更高版本的情况之下。

  如果要通过注解标注的方式为 FXNewsProvider 注入所需要的依赖,现在可以使用 @Autowired 以及 @Component 对相关类进行标记。代码清单4-9演示了FXNews相关类使用指定注解标注后的情况。

/**
 * @author Rab
 * @since 2022-04-24
 */
public class Person {

    private String name;

    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

/**
 * @author Rab
 * @since 2022-04-24
 */
@Configuration
public class MainConfig {

    @Bean
    public Person person1() {
        return new Person("rab", 18);
    }
}

/**
 * @author Rab
 * @since 2022-04-24
 */
public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);

        Person person = applicationContext.getBean(Person.class);

        System.out.println(person);
    }
}

  与为XML配置文件格式提供 XMLBeanDefinitionReader 相对应,Spring同样为注解方式提供了现成的 BeanDefinitionReader 实现,即 AnnotationBeanDefinitionReader

4.3 Bean的注入

Spring注解说明

4.4 工厂方法与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 的实例):

<bean id="foo" class="...Foo">
	<property name="barInterface">
		<ref bean="bar"/>
	</property>
</bean>
<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"/>

  class 指定静态方法工厂类, factory-method 指定工厂方法名称,然后,容器调用该静态方法工厂类的指定工厂方法( getInstance ),并返回方法调用后的结果,即 BarInterfaceImpl 的实例。也就是说,为 foo 注入的 bar 实际上是 BarInterfaceImpl 的实例,即方法调用后的结果,而不是静态工厂方法类( StaticBarInterfaceFactory )。我们可以实现自己的静态工厂方法类返回任意类型的对象实例,但工厂方法类的类型与工厂方法返回的类型没有必然的相同关系。

  某些时候,有的工厂类的工厂方法可能需要参数来返回相应实例,而不一定非要像我们的getInstance() 这样没有任何参数。对于这种情况,可以通过 来指定工厂方法需要的参数。

2.非静态工厂方法(Instance Factory Method)

  既然可以将静态工厂方法实现类的工厂方法调用结果作为bean注册到容器中,我们同样可以针对基于工厂类实例的工厂方法调用结果应用相同的功能,只不过,表达方式可能需要稍微变一下。

  现在为 BarInterface 提供非静态的工厂方法实现类,该类定义如下代码所示:

public class NonStaticBarInterfaceFactory{
	public BarInterface getInstance(){
		return new BarInterfaceImpl();
	}
...
}

  因为工厂方法为非静态的,我们只能通过某个 NonStaticBarInterfaceFactory 实例来调用该方法(哦,错了,是容器来调用),那么也就有了如下的配置内容:

<bean id="foo" class="...Foo">
	<property name="barInterface">
		<ref bean="bar"/>
	</property>
</bean>
<bean id="barFactory" class="...NonStaticBarInterfaceFactory"/>
<bean id="bar" factory-bean="barFactory" factory-method="getInstance"/>

  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 接口,给出自己的对象实例化逻辑代码。当然,不使用 FactoryBean ,而像通常那样实现自定义的工厂方法类也是可以的。不过, 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 的定义代码

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 id="nextDayDateDisplayer" class="...NextDayDateDisplayer">
	<property name="dateOfNextDay">
		<ref bean="nextDayDate"/>
	</property>
</bean>
<bean id="nextDayDate" class="...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);

ssertEquals(((DateTime)nextDayDate).getDayOfYear(),((DateTime)factoryValue).getDayOfYear());

  Spring容器内部许多地方了使用 FactoryBean 。下面是一些比较常见的 FactoryBean 实现,你可以参照 FactoryBean 的Javadoc以了解更多内容。

  • JndiObjectFactoryBean
  • LocalSessionFactoryBean
  • SqlMapClientFactoryBean
  • ProxyFactoryBean
  • TransactionProxyFactoryBean

4.5 偷梁换柱之术

  在学习以下内容之前,先提一下有关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;
	}
}

配置为

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false"></bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
	<property name="newsBean">
		<ref bean="newsBean"/>
	</property>
</bean>

  当多次调用 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 方法声明符合规定的格式,并在配置文件中通知容器,当该方法被调用的时候,每次返回指定类型的对象实例即可。方法声明需要符合的规格定义如下:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

  也就是说,该方法必须能够被子类实现或者覆写,因为容器会为我们要进行方法注入的对象使用Cglib动态生成一个子类实现,从而替代当前对象。既然我们的 getNewsBean() 方法已经满足以上方法声明格式,剩下唯一要做的就是配置该类,配置内容如下所示:

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
</bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
	<lookup-method name="getNewsBean" bean="newsBean"/>
</bean>

  通过 的 name 属性指定需要注入的方法名, bean 属性指定需要注入的对象,当getNewsBean 方法被调用的时候,容器可以每次返回一个新的 FXNewsBean 类型的实例。所以,这个时候,我们再次检查执行结果,输出的实例引用应该是不同的:

persist bean:..domain.FXNewsBean@18aaa1e
persist bean:..domain.FXNewsBean@a6aeed

2.殊途同归

  除了使用方法注入来达到“每次调用都让容器返回新的对象实例”的目的,还可以使用其他方式达到相同的目的。下面给出其他两种解决类似问题的方法,供读者参考。

  • 使用 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");
    	}
    }
    

    配置简化为

    <bean id="newsBean" class="..domain.FXNewsBean" singleton="false"></bean>
    <bean id="mockPersister" class="..impl.MockNewsPersister"></bean>
    

    如此,可以预见到,输出的结果将与我们所预期的相同:

    persist bean:..domain.FXNewsBean@121cc40
    persist bean:..domain.FXNewsBean@1e893df
    

    实际上,方法注入动态生成的子类,完成的是与以上类似的逻辑,只不过实现细节上不同而已。

  • 使用 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 的相关配置

    <bean id="newsBean" class="..domain.FXNewsBean" singleton="false"></bean>
    <bean id="newsBeanFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
    	<property name="targetBeanName">
    		<idref bean="newsBean"/>
    	</property>
    </bean>
    <bean id="mockPersister" class="..impl.MockNewsPersister">
    	<property name="newsBeanFactory">
    		<ref bean="newsBeanFactory"/>
    	</property>
    </bean>
    

    看,真有效!

    persist bean:..domain.FXNewsBean@ecd7e
    persist bean:..domain.FXNewsBean@1d520c4
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值