Spring5系列(五) | 聊聊FactoryBean

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: Spring5系列(五) | 聊聊FactoryBean

通过前几篇的文章,我们已经可以通过spring进行对象的创建及赋值。通过这样的方式,我们已经可以我们自己创建的类交给spring容器进行管理。spring可以帮我们创建对象,并且我们也分析了,spring帮我们创建对象的方式,就是通过反射调用构造方法实现的。那么问题来了,如果有一些类我们不能通过构造方法的方式创建对象该怎么办呢?或者说,如果有一些对象已经存在了,我不希望spring帮我创建了,但是它通过他的容器进行管理,应该怎么办呢? 这个问题问的有点抽象了,可能乍一听,很难理解。那么我们接下来举例来研究一下

一. 真的有这样的对象么

有,真的有,比如我们之前学习过jdbc,在jdbc中有一个Connection对象,是帮我们连接数据库的,这个对象是怎么创建的呢?我们来回顾一下。

Class.forName("com.mysql.jdbc.Driver");
Connectionconn=DriverManager.getConnection("jdbc:mysql://localhost:3306/db","root","123456");
returnconn;
// 省略异常

这种情况下,就发现这个Connection并不是通过简单的new的方式来创建的。此时我们如果把这个对象交由spring来创建,得到的对象肯定是不对的。

<beanid="conn"class="com.sql.Connection"/>

要注意上面是反例。是错误的写法,那么此时应该怎么办呢,很明显Connection对象的创建比较复杂,spring底层无法通过简单的new的方式进行创建,而最好让spring框架能够把对象的创建权利交给我们,但是有需要交给工厂管理。 针对这种情况,spring就为我们提供了一个解决这种方式的钩子-  FactoryBean.在讲解之前我们先约定两个概念:

简单对象: 可以直接通过new的方式创建出来的对象

复杂对象: 不能直接通过new的方式构建的对象,如Connection,SqlSessionFactory.

二. FactoryBean接口

为了解决上述问题,spring为我们提供了一个接口,叫做FactoryBean:,并且这个类在spring内部也是一个经常被使用的接口。它的主要作用就是帮我们创建复杂对象。这个接口是一个泛型接口,实现的时候,可以指定泛型,里边有三个方法需要我们实现。我们来实验一下。

publicclassConnectionFactoryBeanimplementsFactoryBean<Connection>{
@OverridepublicConnectiongetObject(){
// 用于书写创建复杂对象的代码,并把复杂对象作为对象的返回值 返回// 这里省略异常Class.forName("com.mysql.jdbc.Driver");
Connectionconn=DriverManager.getConnection("jdbc:mysql://localhost:3306/db","root","123456");
returnconn;
  }
@OverridepublicClass<Connection>getObjectType(){
// 返回 所创建复杂对象的Class对象returnConnection.class;
  }
@OverridepublicbooleanisSingleton() {
// 需要 创建一次: true// 每调用一次,就创建一次: falsereuturnfalse;
  }
}

这个接口中有三个方法,其中最重要的方法就是getObject(); 这个方法的目的就是从spring容器中获取对象的时候,得到的对象就是调用getObject() 方法得到的对象。我们验证一下。

<beanid="conn"class="com.xxx.ConnectionFactoryBean"/>

这里用junit 做测试

@Testpublicvoidtest(){
ApplicationContextctx=newClasssPathXmlApplicationContext("/applicationContext.xml");
Objectobj=ctx.getBean("conn");
}

这个一定要注意,我们通过getBean获取的不是ConnectionFactoryBean对象,而是Connection对象。如果class中指定的类型是FactoryBean接口的实现类,那么通过id值获取的是这个类调用getObject()所得到的对象,在本程序中,获取conn的时候,由于他对应的类是ConnectionFactoryBean ,是FactoryBean接口的实现了,所以获取对象的时候,得到的是getObject() 方法的返回值,也就是Connection。 这样通过这个接口就让程序员来控制对象的创建过程。

三. 原理分析及注意事项

其实也不难想到,通过配置的class,加上反射就可以得到ConnectionFacotoryBean对象,在使用instanceof 判断是否属于FactoryBean的子类,如果返回结果为true, 就直接调用getObject() 方法返回相应对象即可。

注意事项:

  1. 以上面的代码为例,我们通过getBean("conn"); 方法得到的并不是配置的class: ConnectionFactoryBean的对象,而是调用其getObject() 方法获取的Connection对象,那如果我们就想得到ConnectionFactoryBean对象该怎么办呢? 可以通过ctx.getBean("&conn"); 在id前加一个& 就得到了实现类的对象。
  2. isSingleton();这个方法是用来限定对象创建个数的,如果返回false, 那么每次获取都会创建一个新的对象。如果true,多次获取得到的都是同一个对象,也就是我们所说的单例对象。我们在使用的时候,要根据对象的特点返回相应的结果。如连接对象不能共用,因为里边有事务,不能相互干扰,所以返回false.如果像SqlSessionFactoryBean这种重量级资源,且线程安全就返回true;
  3. mysql在高版本的连接创建是需要指定SSL证书,我们可以在url后追加?useSSL=false来解决。
  4. 类似于数据库连接地址,用于名,密码等信息可在ConnectionFactoryBean类中,将这些值设置为成员变量,指定get,set方法。通过属性值set注入,也方便后期我们使用配置文件做解耦合。
<beanid="conn"class="com.xxx.ConnectionFactoryBean"><propertyname="driverName"value="com.mysql.jdbc.Driver"/><propertyname="url"value="jdbc::mysql://localhost:3306/db?userSSL=false"/><propertyname="username"value="root"/><propertyname="password"value="123456"/></bean>

四. 实例工厂和静态工厂

上边我们提到了通过FactoryBean接口,来创建复杂对象。但是有些时候,如果我们的类中已经存了创建对象的方法,并且这个类没有办法去实现FactoryBean接口,而我们又想通过spring工厂去管理应该怎么办呢?比如一些第三方类库,我们只能得到他的.class文件,无法修改他的源码该怎么办呢。这个时候spring给我们提供了实例工厂和静态工厂的方式来实现。

publicclassConnectionFactory {
publicConnectiongetConnection() {
Class.forName("com.mysql.jdbc.Driver");
Connectionconn=DriverManager.getConnection("jdbc:mysql://localhost:3306/db","root","123456");
returnconn; 
  }
}

假设我们又上面一个类,已经提供了创建对象的方法,但是这个类无法实现接口了,我们应该怎么办呢?可以通过下面的方式获取对象。

<beanid="connFactory"class="com.xxx.FactoryBean"/><beanid="conn"factory-bean="connFactory"factory-method="getConnection"/>

通过factory-bean去指定刚才的这个类,通过factory-method去指定获取对象的方法,这样我们就可以通过conn来获取这个对象了。 也就是这里的factory-method就相当于之前的getObject()方法。我们通过spring工厂获取conn就能得到Connection对象了。这就是所谓的实例工厂。

如果我们存在一个静态的获取对象的方法,就要使用静态工厂了。

publicclassStaticConnectionFactory {  
publicstaticConnectiongetConnection() {
Class.forName("com.mysql.jdbc.Driver");
Connectionconn=DriverManager.getConnection("jdbc:mysql://localhost:3306/db","root","123456");
returnconn; 
  }  
}

静态工厂直接指定factory-method即可,因为不需要对象就可以调用。

<beanid="conn"class="com.xxx.StaticFactoryBean"factory-method="getConnection"/>

五. 总结 本篇文章主要介绍了spring提供了一种将对象的创建过程交给程序员自主处理的方式。主要就是通过FactoryBean工厂,实例工厂和静态工厂三种方式。 而这种方式其实主要应用在和一些第三方框架的整合时候。因为一些第三方框架一般都不是直接使用源码,所以对应的源码我们是无法修改的,但是他里边又提供了创建对象的方法,这个时候我们我们就可以通过这样的方式,将第三方框架中的对象交给spring工厂来管进行管理。

举个例子:我们在使用Mybatis框架的时候,我们需要用到一个SqlSessionFactory的工厂类,这个工厂类可以帮我们获取操作数据库的会话Session. 这个类也不是通过简单new的方式能够创建的,所以我们就可以通过一个实现FactoryBean的方式,在getObject方法中来创建这个对象。所以当我们做spring整合Mybatis的时候,就会有这样一个配置:

<beanid="sqlSessionFactoryBean"class="org.mybatis.spring.SqlSessionFactoryBean"><propertyname="dataSource"ref="dataSource"></property><propertyname="typeAliasesPackage"value="com.xxx.entity"></property><propertyname="mapperLocations"><list><value>classpatrh:/com.xxx.mapper/*Mapper.xml</value></list></property></bean>复制代码

而这个SqlSessionFactoryBen就是由mybatis提供的专门用于和spring做整合的,他就是一个FactoryBean的实现类,在里边的SqlSessionFactoryBean的getObject() 方法中返回了SqlSessionFactory对象,这样就把他交给了spring容器进行了管理,完成了整合。我们可以简单看下源码:


image.png

关于和Mybatis的整合,我们后面会详细介绍

再提一嘴,有时候经常会有一道面试题,问:BeanFactory 和 FactoryBean的区别,FactoryBean我们已经介绍完了,那什么是BeanFactory, 他就是spring工厂的最基本的基类,向我们常用的ApplicationContext以及各类的工厂都实现了这个接口,所以相当于他是所有spring工厂的最核心的抽象接口。比如我们常用的getBean("") 方法就是在BeanFactory中定义的。

参考资料:

孙帅spring详解:www.bilibili.com/video/BV185…


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
4月前
|
Java 关系型数据库 MySQL
Spring5深入浅出篇:Spring中的FactoryBean对象
Spring5深入浅出篇:Spring中的FactoryBean对象
|
Java Spring 容器
Spring中BeanFactory和FactoryBean的区别?
一位工作了4年的小伙伴,去京东面试被问到这样一个问题,Spring中的BeanFactory和FactoryBean有什么区别?因为没有看过源码,当时就感觉这是一个文字游戏,感觉没什么区别? 那今天,我就给大家来聊清楚。另外,往期面试题解析中配套的文档我已经准备好,想获得的可以在我的煮叶简介中找到。好了,我们先来看BeanFactory。
57 0
|
4月前
|
XML Java 数据格式
spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象
spring中怎么通过静态工厂和动态工厂获取对象以及怎么通过 FactoryBean 获取对象
67 0
|
4月前
|
Java 数据库连接 API
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
85 0
|
3月前
|
Java Spring
聊聊Spring中两种创建Bean的方式:BeanDefinition.setInstanceSupplier() 和 FactoryBean
聊聊Spring中两种创建Bean的方式:BeanDefinition.setInstanceSupplier() 和 FactoryBean
|
4月前
|
XML Java 数据格式
Spring5源码(8)-BeanFactory和FactoryBean的区别
Spring5源码(8)-BeanFactory和FactoryBean的区别
52 0
|
4月前
|
XML Java 数据格式
③【Spring】整合第三框架的常用机制:FactoryBean
③【Spring】整合第三框架的常用机制:FactoryBean
71 0
|
10月前
|
XML Java 数据格式
Spring中BeanFactory和FactoryBean详解
Spring中BeanFactory和FactoryBean详解
241 1
|
Java Spring 容器
【Spring源码】 BeanFactory和FactoryBean是什么?
面试官:“看过Spring源码吧,简单说说Spring中BeanFactory和FactoryBean的区别是什么?”
17456 6
【Spring源码】 BeanFactory和FactoryBean是什么?
|
Java C++ Spring
Spring5源码 - 08 BeanFactory和FactoryBean 源码解析 & 使用场景
Spring5源码 - 08 BeanFactory和FactoryBean 源码解析 & 使用场景
132 0