前言
一般情况下,Spring通过反射机制利用bean的class属性指定实现类来实例化bean。在某些情况下,实例化bean的过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息,配置方式的灵活性是受限制的,这时采用编码的方式可能会得到一个更简单的方案。Spring为此提供了一个org.Springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。
FactoryBean的简单使用
FactoryBean接口对于Spring框架来说占有重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明为FactoryBean<T>的形式,如下:
public interface FactoryBean<T> { T getObject() throws Exception; Class<?> getObjectType(); default boolean isSingleton() { return true; } }
可以看出,FactoryBean接口定义了3个方法:
(1)T getObject():返回FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单例实例缓存池中。
(2)boolean isSingleton():返回由FactoryBean创建bean实例的作用域是singleton还是prototype。
(3)Class<T> getObjectType():返回FactoryBean创建的bean类型。
当配置文件中<bean>的class属性配置的实现类是FactoryBean时,通过getBean()方法返回的不是FactoryBean本身,而是FactoryBean里getObject()方法所返回的对象,相当于FactoryBean里的getObject()代理了getBean()方法。
例如:如果使用传统的配置文件方式配置下面的Hero的<bean>时,Hero的每个属性都会对应一个<property>元素标签。
public class Hero { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
传统方式配置文件为:
<bean id="hero" class="com.joe.mytag.Hero"> <property name="age" value="25"></property> <property name="name" value="Joe"></property> </bean>
如果用FactoryBean的方式实现就会灵活一些,下面举例为“,”分割符的方式一次性地为Hero的所有属性指定配置项:
public class HeroFactoryBean implements FactoryBean<Hero> { public String getHeroInfo() { return heroInfo; } public void setHeroInfo(String heroInfo) { this.heroInfo = heroInfo; } private String heroInfo; @Override public Hero getObject() throws Exception { Hero hero = new Hero(); String[] info = heroInfo.split(","); hero.setAge(Integer.parseInt(info[1])); hero.setName(info[0]); return hero; } @Override public Class<?> getObjectType() { return Hero.class; } @Override public boolean isSingleton() { return false; } }
使用了FactoryBean后,配置文件为:
<bean id="hero1" class="com.joe.mytag.HeroFactoryBean"> <property name="heroInfo" value="Jordan,35"></property> </bean>
测试:
public class Main { @SuppressWarnings("deprecation") public static void main(String[] args) { BeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring.xml")); Hero hero = (Hero) bf.getBean("hero1"); System.out.println("name: " + hero.getName() + " age: " + hero.getAge()); } }
输出结果:
十二月 19, 2018 2:42:31 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [spring.xml] name: Jordan age: 35
可以看出,成功。这只是一个简单的例子,如果有个JavaBean的属性多达十几二十个,如果使用传统的配置方式,那么就需要与属性种类一样多的<property>来进行配置,这样就会很麻烦。这时若使用FactoryBean来处理,则会方便很多。
解释一下实现FactoryBean后,Spring的调用逻辑:当调用getBean("hero1")时,Spring通过反射机制发现HeroFactoryBean实现了FactoryBean接口,这时Spring容器就调用接口方法,HeroFactoryBean里的getObject()方法返回。如果希望获取HeroFactoryBean的实例,则需要在使用getBean(beanName)方法时在beanName前显示的加上"&"前缀,例如调用getBean("&hero1")。如下:
public class Main { @SuppressWarnings("deprecation") public static void main(String[] args) { BeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring.xml")); Object hero = bf.getBean("&hero1"); System.out.println(hero.toString() ); } }
输出结果:
十二月 19, 2018 3:23:37 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [spring.xml] com.joe.mytag.HeroFactoryBean@c818063
参考:《Spring源码深度解析》 郝佳 编著: