前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring5系列(五) | 聊聊FactoryBean

Spring5系列(五) | 聊聊FactoryBean

作者头像
一缕82年的清风
修改2021-12-13 09:41:13
2940
修改2021-12-13 09:41:13
举报
文章被收录于专栏:lsqingfeng

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

一. 真的有这样的对象么

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

代码语言:javascript
复制
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db","root","123456");
return conn;

// 省略异常
复制代码

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

代码语言:javascript
复制
<bean id="conn" class="com.sql.Connection" />
复制代码

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

简单对象: 可以直接通过new的方式创建出来的对象 复杂对象: 不能直接通过new的方式构建的对象,如Connection,SqlSessionFactory.

二. FactoryBean接口

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

代码语言:javascript
复制
public class ConnectionFactoryBean implements FactoryBean<Connection>{
  
  @Override
  public Connection getObject(){
    // 用于书写创建复杂对象的代码,并把复杂对象作为对象的返回值 返回
    // 这里省略异常
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db","root","123456");
    return conn;
  }
  
  @Override
  public Class<Connection> getObjectType(){
    // 返回 所创建复杂对象的Class对象
    return Connection.class;
  }
  
  @Override
  public boolean isSingleton() {
    // 需要 创建一次: true
    // 每调用一次,就创建一次: false
    reuturn false;
  }
 
}
复制代码

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

代码语言:javascript
复制
<bean id="conn" class="com.xxx.ConnectionFactoryBean" /> 
复制代码

这里用junit 做测试

代码语言:javascript
复制
@Test
public void test(){
  ApplicationContext  ctx = new ClasssPathXmlApplicationContext("/applicationContext.xml");
  
Object obj = 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注入,也方便后期我们使用配置文件做解耦合。
代码语言:javascript
复制
<bean id="conn" class="com.xxx.ConnectionFactoryBean"  >
 	<property name="driverName" value="com.mysql.jdbc.Driver" />
  <property name="url" value="jdbc::mysql://localhost:3306/db?userSSL=false" />
  <property name="username" value="root" />
  <property name="password" value="123456" />
</bean>
复制代码

四. 实例工厂和静态工厂

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

代码语言:javascript
复制
public class ConnectionFactory {
  
  public Connection getConnection() {
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db","root","123456");
    return conn; 
  }
}

复制代码

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

代码语言:javascript
复制
<bean id="connFactory" class="com.xxx.FactoryBean" />
<bean id="conn" factory-bean="connFactory" factory-method="getConnection" />
复制代码

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

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

代码语言:javascript
复制
public class StaticConnectionFactory {  
  public static Connection getConnection() {
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db","root","123456");
    return conn; 
  }  
}
复制代码

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

代码语言:javascript
复制
<bean id="conn" class="com.xxx.StaticFactoryBean" factory-method="getConnection" />
复制代码

五. 总结

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

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

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

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

image.png
image.png
image.png
image.png

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

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

参考资料:

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/03/31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 真的有这样的对象么
  • 二. FactoryBean接口
  • 三. 原理分析及注意事项
  • 四. 实例工厂和静态工厂
  • 五. 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档