## Spring 创建复杂对象
在之前通过 `ApplicationContext` 工厂的方法创建的对象都属于简单对象,也就是说这些对象可以直接通过 `new` 关键字来创建,所以 Spring 底层可以通过反射的方式直接创建对象。但是,实际开发中会有很多复杂对象,无法通过直接 `new` 来创建,例如 JDBC 中的数据库连接对象 `Connection`,MyBatis 中的 `SqlSessionFactory` 对象,在 Spring 中这些复杂对象的创建可以通过另外三种方法创建:`FactoryBean` 接口、实例工厂、静态工厂
### FactoryBean 接口
1. 创建一个类实现 `FactoryBean` 接口,并实现其中的三个方法,以 JDBC `Connection` 对象为例
```java
// 创建一个类实现 FactoryBean 并添加泛型:实际要创建的对象的类型:Connection
public class ConnectionFactoryBean implements FactoryBean {
// 实现 getObject() 方法:方法体中书写实际创建复杂对象的代码,并将创建的对象返回
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/tbl","root","root");
return connection;
}
// 返回要创建的复杂对象的 class
@Override
public Class> getObjectType() {
return Connection.class;
}
// 创建的复杂对象是否是单例的
@Override
public boolean isSingleton() {
return false;
}
}
```
2. 配置文件的配置:添加这个 `FactoryBean` 的 `` 标签
```xml
```
3. 创建对象
```java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Connection conn = ctx.getBean("conn",Connection.class);
```
**注意**
1. 如果一个类实现了 `FactoryBean` 接口和其中的方法,再配置 ``,那么通过 `ApplicationContext` 获取的对象不会是实际 `` 标签中的类。比如上例中的 `ConnectionFactoryBean`,而是该类中 `getObject()` 方法返回的类型
2. 如果想获得 `FactoryBean` 类型的对象,比如 `ConnectionFactoryBean` 这个类本身的对象,只需在传递参数时使用 `&` 运算符, `ctx.getBean("&conn")`
3. 如果 `isSingleton()` 方法中返回 `true`,表明要创建的对象是单例的,也就是在 Spring 读取配置文件时就会把该对象创建一次,如果返回 `false`,则每一次调用 `getBean()` 方法都会创建一个新的对象并返回,显然,数据库连接对象需要返回 `false`,保证每一个连接对象都是不同的
4. 简单分析 `FactoryBean` 实现原理:Spring 根据配置文件的 `` 标签创建对象时,会先做一步判断,如果判断该类是接口 `FactoryBean` 的子类,那么不再通过反射的方式调用该类的构造方法创建对象,而是调用该类的 `getObject()` 方法获得对象,再返回给调用者,因为该类实现了 `FactoryBean` 接口,所以其中必定有 `getObject()` 方法,这就是接口回调
5. `FactoryBean` 是 Spring 中创建复杂对象的原生方式,后期 Spring 整合其他框架也会大量应用 `FactoryBean`
### 实例工厂
注意到,创建复杂对象时,必须实现 Spring 中的 `FactoryBean` 接口,如果我们从零开发一个项目,这样的方法固然方便,但是,考虑某些时候可能是在已有代码上进行维护或升级,不方便直接对原有代码进行修改,这时候就需要用到 Spring 框架的实例工厂对已有代码进行整合,可以避免 Spring 框架的侵入。例如,已存在一个 `ConnectionFactory` 类用于获取数据库连接对象,而该类没有实现 `FactoryBean` 接口。
```java
package cool.yzt.factorybean;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionFactory {
public Connection getConnection() {
Connection connection = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/tbl","root","root");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
```
然后修改配置文件,该配置文件表明,首先 Spring 会创建一个 `ConnectionFactory` 对象,然后创建 `id` 为 `conn` 的对象,来自于工厂对象 `connectionFactory` 的工厂方法 `getConnection`
```xml
```
获取使用创建的对象
```java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Connection conn = ctx.getBean("conn",Connection.class);
```
### 静态工厂
实际上,创建复杂对象的工厂有时并不需要创建其自身的实体对象,调用者只需要调用其静态方法就可以获得对应的对象,例如之前写过的数据库连接 JDBC 工具类和 Jedis 工具类,这时使用静态工厂更加方便,首先改造刚才的 `ConnectionFactory`
```java
package cool.yzt.factorybean;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionFactoryStatic {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/travel","root","root");
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
```
修改配置文件,该配置表明 `ConnectionFactoryStatic` 是一个静态工厂类,它会从静态方法 `getConnection` 返回 `id` 为 `conn` 的对象
```xml
```
获取使用创建的对象
```java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Connection conn = ctx.getBean("conn",Connection.class);
```
### 总结 Spring 工厂创建对象

## 控制 Spring 工厂创建对象的次数
之前说到,有些对象只需创建一次,有些对象需要每一次都创建新的,以节省内存。所以我们需要控制创建对象的次数,在 Spring 中,通过配置文件中 `` 标签的 `scope` 属性就可以控制对象创建的次数,如果该属性的值为 `singleton`,则表明创建的对象是单例的,该对象会在 Spring 读取配置文件时创建对象,用户调用 `getBean` 方法获取的对象都是同一个,如果该属性值为 `prototype`,则表明用户每调用一次 `getBean` 方法都会获取一个新的对象。特别的,对于实现 `FactoryBean` 接口的类,可以通过 `isSingleton()` 方法的返回值来控制。
JavaEE 开发中,常见的只创建一次的对象有 `SqlSessionFactory`、`DAO`、`Service`等,每次都创建新的对象有 `SqlSession`、`Connection` 等。
## 参考
[孙哥说Spring5](https://www.bilibili.com/video/BV185411477k)

Spring | 创建复杂对象