FactoryBean的使用

前言

 一般情况下,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源码深度解析》 郝佳 编著:

作者:Joe
努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
原文地址:https://www.cnblogs.com/Joe-Go/p/10143550.html