通过前几篇的文章,我们已经可以通过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() 方法返回相应对象即可。
注意事项:
- 以上面的代码为例,我们通过getBean("conn"); 方法得到的并不是配置的class: ConnectionFactoryBean的对象,而是调用其getObject() 方法获取的Connection对象,那如果我们就想得到ConnectionFactoryBean对象该怎么办呢? 可以通过ctx.getBean("&conn"); 在id前加一个& 就得到了实现类的对象。
- isSingleton();这个方法是用来限定对象创建个数的,如果返回false, 那么每次获取都会创建一个新的对象。如果true,多次获取得到的都是同一个对象,也就是我们所说的单例对象。我们在使用的时候,要根据对象的特点返回相应的结果。如连接对象不能共用,因为里边有事务,不能相互干扰,所以返回false.如果像SqlSessionFactoryBean这种重量级资源,且线程安全就返回true;
- mysql在高版本的连接创建是需要指定SSL证书,我们可以在url后追加?useSSL=false来解决。
- 类似于数据库连接地址,用于名,密码等信息可在ConnectionFactoryBean类中,将这些值设置为成员变量,指定get,set方法。通过属性值set注入,也方便后期我们使用配置文件做解耦合。
<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容器进行了管理,完成了整合。我们可以简单看下源码:
关于和Mybatis的整合,我们后面会详细介绍
再提一嘴,有时候经常会有一道面试题,问:BeanFactory 和 FactoryBean的区别,FactoryBean我们已经介绍完了,那什么是BeanFactory, 他就是spring工厂的最基本的基类,向我们常用的ApplicationContext以及各类的工厂都实现了这个接口,所以相当于他是所有spring工厂的最核心的抽象接口。比如我们常用的getBean("") 方法就是在BeanFactory中定义的。
参考资料:
孙帅spring详解:www.bilibili.com/video/BV185…