飞道的博客

【Spring】一篇文章快速搞懂BeanFactory和FactoryBean的区别

345人阅读  评论(0)

目录

一、BeanFactory

1.1 源码

1.2 使用场景

二、FactoryBean

2.1 源码

2.2 示例

2.2.1 方法一

2.2.2 方法二

2.3 FactoryBean的两种用法

2.3.1 简化xml配置,隐藏细节

2.3.2 返回不同Bean的实例

2.4 使用场景

三、BeanFactory和FactoryBean的区别以及共同点


一、BeanFactory

BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它负责生产和管理bean的一个工厂,我们可以通过它获取工厂管理的对象。在Spring中,BeanFactoryIOC容器的核心接口,它的职责包括实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。它定义了getBean()containsBean()等管理Bean的通用方法。但BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 :

  • DefaultListableBeanFactory
  • XmlBeanFactory
  • ApplicationContext

 

其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。

 

1.1 源码


  
  1. public  interface BeanFactory {
  2.      /**
  3. 用于区分factoryBean和bean,后面会讲到
  4.     /*String FACTORY_BEAN_PREFIX = "&";
  5.     /**
  6.      返回byName返回bean的实例
  7. */
  8.      Object getBean(String name) throws BeansException;
  9.     <T>  getBean(String name, Class<T> requiredType) throws BeansException;
  10.      Object getBean(String name, Object... args) throws BeansException;
  11.     <T>  getBean(Class<T> requiredType) throws BeansException;
  12.     <T>  getBean(Class<T> requiredType, Object... args) throws BeansException;
  13.      /**
  14.      * Return a provider for the specified bean, allowing for lazy on-demand retrieval
  15.      * of instances, including availability and uniqueness options.
  16.      * @param requiredType type the bean must match; can be an interface or superclass
  17.      * @return a corresponding provider handle
  18.      * @since 5.1
  19.      * @see #getBeanProvider(ResolvableType)
  20.      */
  21.     <T>  ObjectProvider<T> getBeanProvider(Class<T> requiredType);
  22.     <T>  ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
  23.      /**
  24.  判断工厂中是否包含给定名称的bean定义,若有则返回true
  25. */
  26.      boolean containsBean(String name);
  27.      /**
  28.     判断bean是否为单例
  29. */
  30.      boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
  31.      /**
  32. 判断bean是否为多例
  33. */
  34.      boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
  35.      /**
  36. 检查具有给定名称的bean是否匹配指定的类型。
  37.      */
  38.      boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
  39.      boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
  40.      /**
  41. 返回给定名称的bean的Class,如果没有找到指定的bean实例,则排除*/
  42.      @Nullable
  43.     Class<?> getType(String name)  throws NoSuchBeanDefinitionException;
  44.      /**
  45. 返回给定bean名称的所有别名 
  46. */
  47.     String[] getAliases(String name);
  48. }

 

1.2 使用场景

  • 从Ioc容器中获取Bean(byName or byType)
  • 检索Ioc容器中是否包含指定的Bean
  • 判断Bean是否为单例

 

二、FactoryBean

使用XML配置spring容器的时候,Spring通过反射机制利用<bean>的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息。配置方式的灵活性是受限的,比如一个类大量依赖了其他的对象属性,此时就算是使用自动装配,不需要再显式的写出bean之间的依赖关系,但是其依赖的对象也需要将其装配到spring容器中,也需要为它所依赖的多有对象都创建bean标签将他们注入,如果这个类依赖了上百个对象,那么这个工作量无疑是非常大的。

Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean<T>的形式。

Spring中共有两种bean,一种为普通bean,另一种则为工厂bean

以Bean结尾,表示它是一个Bean,不同于普通Bean的是:它是实现了FactoryBean<T>接口的Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。

 

2.1 源码


  
  1. public  interface FactoryBean<T{
  2.      //从工厂中获取bean
  3.      @Nullable
  4.      getObject() throws Exception;
  5.      //获取Bean工厂创建的对象的类型
  6.      @Nullable
  7.     Class<?> getObjectType();
  8.      //Bean工厂创建的对象是否是单例模式
  9.      default boolean isSingleton() {
  10.          return  true;
  11.     }
  12. }

从它定义的接口可以看出,FactoryBean表现的是一个工厂的职责。 即一个Bean A如果实现了FactoryBean接口,那么A就变成了一个工厂,根据A的名称获取到的实际上是工厂调用getObject()返回的对象,而不是A本身,如果要获取工厂A自身的实例,那么需要在名称前面加上'&'符号。

  • getObject('name')返回工厂中的实例
  • getObject('&name')返回工厂本身的实例

 

通常情况下,bean 无须自己实现工厂模式,Spring 容器担任了工厂的角色;但少数情况下,容器中的 bean 本身就是工厂,作用是产生其他 bean 实例。由工厂 bean 产生的其他 bean 实例,不再由 Spring 容器产生,因此与普通 bean 的配置不同,不再需要提供 class 元素。

 

2.2 示例

我们现在要将下面这个TempDaoFactoryBean类交给工厂去创建管理


  
  1. public  class TempDaoFactoryBean {
  2.      private String msg1;
  3.      private String msg2;
  4.      private String msg3;
  5.      public void test() {
  6.         System.out.println( "FactoryBean");
  7.     }
  8.      public void setMsg1(String msg1) {
  9.          this.msg1 = msg1;
  10.     }
  11.      public void setMsg2(String msg2) {
  12.          this.msg2 = msg2;
  13.     }
  14.      public void setMsg3(String msg3) {
  15.          this.msg3 = msg3;
  16.     }
  17.      public String getMsg1() {
  18.          return msg1;
  19.     }
  20.      public String getMsg2() {
  21.          return msg2;
  22.     }
  23.      public String getMsg3() {
  24.          return msg3;
  25.     }
  26. }

 我们有两种方法可以选择:

方法一:通过spring的xml的方式对其进行配置.

方法二:定义一个CarProxy类,实现factoryBean接口.

 

2.2.1 方法一

如果使用传统方式配置下面Car的<bean>时,Car的每个属性分别对应一个<property>元素标签,就算是使用自动装配也要写很多<bean>标签,十分的麻烦

 

2.2.2 方法二

定义DaoFactoryBean实现FactoryBean接口


  
  1. /**
  2.  * FactoryBean由名字可以看出,是以bean结尾的,就说明这是一个bean,是由IOC容器管理的一个bean对象
  3.  *
  4.  * 如果你的类实现了FactoryBean
  5.  * 那么spring容器当中会存储两个对象:一个是getObject()方法返回的对象(TempDaoFactoryBean),还有一个就是当前对象(DaoFactoryBean)
  6.  *
  7.  * getObject()返回的对象(TempDaoFactoryBean)存储在spring容器中给这个对象设置的beanName是当前类指定的对象,也就是 @Component("daoFactoryBean") 中的daoFactoryBean
  8.  * 当前对象(DaoFactoryBean)在spring容器中设置的beanName是在@Component("")指定name的基础上加一个“&”,这里也就是&daoFactoryBean
  9.  *
  10.  * ClassCastException类型转换异常
  11.  */
  12. public  class DaoFactoryBean implements FactoryBean {
  13.      // DaoFactoryBean这个工厂bean管理的对象
  14.      private String msg;
  15.      // 使用setter方法将其注入
  16.      public void setMsg(String msg) {
  17.          this.msg = msg;
  18.     }
  19.      public void testBean() {
  20.         System.out.println( "testBean");
  21.     }
  22.      @Override
  23.      public Object getObject() throws Exception {
  24.          // 在FactoryBean内部创建对象实例
  25.         TempDaoFactoryBean temp =  new TempDaoFactoryBean();
  26.         String[] msfArray = msg.split( ",");
  27. temp.setMsg1(msfArray[ 0]);
  28.         temp.setMsg2(msfArray[ 1]);
  29.         temp.setMsg3(msfArray[ 2]);
  30.          return temp;
  31.     }
  32.      @Override
  33.      public Class<?> getObjectType() {
  34.          return TempDaoFactoryBean.class;
  35.     }
  36.      /**
  37.      * 是否是单例
  38.      * @return
  39.      */
  40.      @Override
  41.      public boolean isSingleton() {
  42.          return  true;
  43.     }
  44. }

 使用xml将这个factoryBean装配到spring容器中


  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.         xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  4.         xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5.    
  6.   <bean id="daoFactory" class="priv.cy.dao.DaoFactoryBean">
  7.          <property name="msg" value="msg1,msg2,msg3"> </property>
  8.      </bean>
  9. </beans>

 

测试类:


  
  1. public  class Test {
  2.      public static void main(String[] args) {
  3.         AnnotationConfigApplicationContext annotationConfigApplicationContext
  4.                 =  new AnnotationConfigApplicationContext(AppConfig.class);
  5.         TempDaoFactoryBean tempDaoFactoryBean = (TempDaoFactoryBean) annotationConfigApplicationContext.getBean( "daoFactory");
  6.         System.out.println(tempDaoFactoryBean.getMsg1());
  7.         System.out.println(tempDaoFactoryBean.getMsg2());
  8.         System.out.println(tempDaoFactoryBean.getMsg3());
  9.     }
  10. }

 

执行结果:

 

因为当我们getBean时,spring对实现了FactoryBean接口的类实现了特殊处理

当调用getBean("daoFactory")时,Spring通过反射机制发现DaoFactoryBean实现了FactoryBean的接口

这时Spring容器就调用接口方法中的getObject()方法返回。如果希望获取CarFactoryBean的实例,

则需要在使用getBean(beanName)方法时在beanName前显示的加上"&"前缀:如getBean("&car");  

 

2.3 FactoryBean的两种用法

 

2.3.1 简化xml配置,隐藏细节

如果一个类有很多的属性,我们想通过Spring来对类中的属性进行值的注入,势必要在配置文件中书写大量属性配置,造成配置文件臃肿,那么这时可以考虑使用FactoryBean来简化配置

 

新建bean


  
  1. public  class Student {
  2.      /** 姓名 */
  3.      private String name;
  4.      /** 年龄 */
  5.      private  int age;
  6.      /** 班级名称 */
  7.      private String className;
  8.      public Student() {
  9.     }
  10.      public Student(String name, int age, String className) {
  11.          this.name = name;
  12.          this.age = age;
  13.          this.className = className;
  14.     }
  15.      public String getName() {
  16.          return name;
  17.     }
  18.      public void setName(String name) {
  19.          this.name = name;
  20.     }
  21.      public int getAge() {
  22.          return age;
  23.     }
  24.      public void setAge(int age) {
  25.          this.age = age;
  26.     }
  27.      public String getClassName() {
  28.          return className;
  29.     }
  30.      public void setClassName(String className) {
  31.          this.className = className;
  32.     }
  33.      @Override
  34.      public String toString() {
  35.          return  "Student{" +  "name='" + name +  '\'' +  ", age=" + age +  ", className='" + className +  '\'' +  '}';
  36.     }
  37. }

 

实现FactoryBean接口


  
  1. public  class StudentFactoryBean implements FactoryBean<Student{
  2.      private String studentInfo;
  3.      @Override
  4.      public Student getObject() throws Exception {
  5.          if ( this.studentInfo ==  null) {
  6.              throw  new IllegalArgumentException( "'studentInfo' is required");
  7.         }
  8.         String[] splitStudentInfo = studentInfo.split( ",");
  9.          if ( null == splitStudentInfo || splitStudentInfo.length !=  3) {
  10.              throw  new IllegalArgumentException( "'studentInfo' config error");
  11.         }
  12.         Student student =  new Student();
  13.         student.setName(splitStudentInfo[ 0]);
  14.         student.setAge(Integer.valueOf(splitStudentInfo[ 1]));
  15.         student.setClassName(splitStudentInfo[ 2]);
  16.          return student;
  17.     }
  18.      @Override
  19.      public Class<?> getObjectType() {
  20.          return Student.class;
  21.     }
  22.      public void setStudentInfo(String studentInfo) {
  23.          this.studentInfo = studentInfo;
  24.     }
  25. }

 

新建day03.xml配置文件


  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.         xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  4.         xmlns:p= "http://www.springframework.org/schema/p"
  5.         xsi:schemaLocation= "http://www.springframework.org/schema/beans
  6.         http://www.springframework.org/schema/beans/spring-beans.xsd">
  7.      <!--注意:class是StudentFactoryBean而不是Student-->
  8.      <bean id="student" class="com.lyc.cn.day03.StudentFactoryBean" p:studentInfo="张三,25,三年二班"/>
  9. </beans>

 

测试类


  
  1. public  class MyTest {
  2.      @Before
  3.      public void before() {
  4.         System.out.println( "---测试开始---\n");
  5.     }
  6.      @After
  7.      public void after() {
  8.         System.out.println( "\n---测试结束---");
  9.     }
  10.      @Test
  11.      public void testStudentFactoryBean() {
  12.         ApplicationContext applicationContext =  new ClassPathXmlApplicationContext( "day03.xml");
  13.         System.out.println(applicationContext.getBean( "student"));
  14.         System.out.println(applicationContext.getBean( "&student"));
  15.     }
  16. }

 

运行


  
  1. ---测试开始---
  2. Student{name= '张三', age= 25, className= '三年二班'}
  3. org.springframework.beans.factory_bean.StudentFactoryBean@1ae369b7
  4. ---测试结束---

 

这样我们就实现了通过BeanFactory接口达到了简化配置文件的作用。另外大家也可以发现getBean(“student”)返回的Student类的实例;而getBean("&student")返回的是StudentFactoryBean实例,即工厂bean其本身。

 

2.3.2 返回不同Bean的实例

既然FactoryBean是一种工厂bean,那么我们就可以根据需要的类型,返回不同的bean的实例,通过代码简单说明一下

 

新建bean


  
  1. public  interface Animal {
  2.      void sayHello();
  3. }
  4. public  class Cat implements Animal {
  5.      @Override
  6.      public void sayHello() {
  7.         System.out.println( "hello, 喵喵喵...");
  8.     }
  9. }
  10. public  class Dog implements Animal {
  11.      @Override
  12.      public void sayHello() {
  13.         System.out.println( "hello, 汪汪汪...");
  14.     }
  15. }

创建了一个Animal接口极其两个实现类Cat和Dog,并进行简单输出,那么如何通过FactoryBean来通过配置返回不同的Animal实例呢

 

新建AnimalFactoryBean


  
  1. public  class AnimalFactoryBean implements FactoryBean<Animal{
  2.      private String animal;
  3.      @Override
  4.      public Animal getObject() throws Exception {
  5.          if ( null == animal) {
  6.              throw  new IllegalArgumentException( "'animal' is required");
  7.         }
  8.          if ( "cat".equals(animal)) {
  9.              return  new Cat();
  10.         }  else  if ( "dog".equals(animal)) {
  11.              return  new Dog();
  12.         }  else {
  13.              throw  new IllegalArgumentException( "animal type error");
  14.         }
  15.     }
  16.      @Override
  17.      public Class<?> getObjectType() {
  18.          if ( null == animal) {
  19.              throw  new IllegalArgumentException( "'animal' is required");
  20.         }
  21.          if ( "cat".equals(animal)) {
  22.              return Cat.class;
  23.         }  else  if ( "dog".equals(animal)) {
  24.              return Dog.class;
  25.         }  else {
  26.              throw  new IllegalArgumentException( "animal type error");
  27.         }
  28.     }
  29.      public void setAnimal(String animal) {
  30.          this.animal = animal;
  31.     }
  32. }

 

修改day03.xml配置文件,增加bean

<bean id="animal" class="com.lyc.cn.day03.AnimalFactoryBean" p:animal="cat"/>

 

在MyTest中添加测试用例


  
  1. @Test
  2. public void testAnimalFactoryBean() {
  3.     ApplicationContext applicationContext =  new ClassPathXmlApplicationContext( "day03.xml");
  4.     Animal animal = applicationContext.getBean( "animal", Animal.class);
  5.     animal.sayHello();
  6. }

 

运行


  
  1. ---测试开始---
  2. hello, 喵喵喵...
  3. ---测试结束---

可以看到,配置文件里我们将animal配置成了cat,那么返回的就是cat的实例,也是简单工厂的一个实现

 

 

2.4 使用场景

说了这么多,为什么要有FactoryBean这个东西呢,有什么具体的作用吗?

 

FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象。

我们知道AOP实际上是Spring在运行时创建了一个代理对象,也就是说这个对象,是我们在运行时创建的,而不是一开始就定义好的,这很符合工厂方法模式。更形象地说,AOP代理对象通过Java的反射机制,在运行时创建了一个代理对象,在代理对象的目标方法中根据业务要求织入了相应的方法。这个对象在Spring中就是——ProxyFactoryBean。

所以,FactoryBean为我们实例化Bean提供了一个更为灵活的方式,我们可以通过FactoryBean创建出更为复杂的Bean实例。

 

还有比如我们spring需要整合mybatis,在没有spring-mybatis的情况下(spring-mybatis会帮助你将MyBatis 代码无缝地整合到Spring ),我们需要将mybatis核心类SqlSessionFactory注入到spring容器,那么思考使用最常用的两种方式:

  1. 注解,可是mybatis是个我们引用的独立的项目.与我们自己的项目源码无关,我们无法去修改它的源码,在它的源码上添加注解,所以不能使用注解的方法
  2. xml,sqlSessionFacory需要注入许多的依赖,如果使用XML来配置,需要我们写大量的配置标签,非常不方便维护。

所以可以选择一个代理类去处理sqlSessionFacory,也就是我们在整合spring+mybatis时使用的SqlSessionFactoryBean,这个类是由mybatis提供的用来方便我们快速配置mybatis的factoryBean,通过这个类把很多繁琐的配置代码封装了起来,类似于装饰者模式,SqlSessionFactoryBean里面管理了sqlSessionFacory并且对他进行相关配置设置操作,我们只需要将SqlSessionFactoryBean注入到spring容器中,并在在xml向这个factoryBean传入一些简单的配置信息,SqlSessionFactoryBean就会帮我们自动配置好sqlSessionFacory,很多复杂的配置都帮我们填充好了,然后我们就可以通过SqlSessionFactoryBean获取已经配置完成的sqlSessionFacory。

 

三、BeanFactoryFactoryBean的区别以及共同点

共同点:都是接口

区别:

  • BeanFactory 以Factory结尾,表示它是一个工厂类,用于管理Bean的一个工厂。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。该接口是IoC容器的顶级接口,是IoC容器的最基础实现,也是访问Spring容器的根接口,负责对bean的创建,访问等工作
  • 对FactoryBean而言,以Bean结尾,说明这是一个交给容器去管理的bean。这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。

参考资料:https://blog.csdn.net/lyc_liyanchao/article/details/82424122


转载:https://blog.csdn.net/cy973071263/article/details/104758856
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场