Spring | 创建复杂对象

Spring | 创建复杂对象

Spring 创建复杂对象

在之前通过 ApplicationContext 工厂的方法创建的对象都属于简单对象,也就是说这些对象可以直接通过 new 关键字来创建,所以 Spring 底层可以通过反射的方式直接创建对象。但是,实际开发中会有很多复杂对象,无法通过直接 new 来创建,例如 JDBC 中的数据库连接对象 Connection,MyBatis 中的 SqlSessionFactory 对象,在 Spring 中这些复杂对象的创建可以通过另外三种方法创建:FactoryBean 接口、实例工厂、静态工厂

FactoryBean 接口

  1. 创建一个类实现 FactoryBean 接口,实现其中的三个方法,以 JDBC Connection 对象为例
// 创建一个类实现 FactoryBean 并添加泛型:实际要创建的对象的类型:Connection
public class ConnectionFactoryBean implements FactoryBean<Connection> {
    // 实现 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;
    }
}
  1. 配置文件的配置:添加这个 FactoryBean<bean> 标签
<bean id="conn" class="cool.yzt.factorybean.ConnectionFactoryBean"/>
  1. 创建对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Connection conn = ctx.getBean("conn",Connection.class);

注意

  1. 如果一个类实现了 FactoryBean 接口和其中的方法,再配置 <bean>,那么通过 ApplicationContext 获取的对象不会是实际 <bean> 标签中的类。比如上例中的 ConnectionFactoryBean,而是该类中 getObject() 方法返回的类型
  2. 如果想获得 FactoryBean 类型的对象,比如 ConnectionFactoryBean 这个类本身的对象,只需在传递参数时使用 & 运算符, ctx.getBean("&conn")
  3. 如果 isSingleton() 方法中返回 true,表明要创建的对象是单例的,也就是在 Spring 读取配置文件时就会把该对象创建一次,如果返回 false,则每一次调用 getBean() 方法都会创建一个新的对象并返回,显然,数据库连接对象需要返回 false,保证每一个连接对象都是不同的
  4. 简单分析 FactoryBean 实现原理:Spring 根据配置文件的 <bean> 标签创建对象时,会先做一步判断,如果判断该类是接口 FactoryBean 的子类,那么不再通过反射的方式调用该类的构造方法创建对象,而是调用该类的 getObject() 方法获得对象,再返回给调用者,因为该类实现了 FactoryBean 接口,所以其中必定有 getObject() 方法,这就是接口回调
  5. FactoryBean 是 Spring 中创建复杂对象的原生方式,后期 Spring 整合其他框架也会大量应用 FactoryBean

实例工厂

注意到,创建复杂对象时,必须实现 Spring 中的 FactoryBean 接口,如果我们从零开发一个项目,这样的方法固然方便,但是,考虑某些时候可能是在已有代码上进行维护或升级,不方便直接对原有代码进行修改,这时候就需要用到 Spring 框架的实例工厂对已有代码进行整合,可以避免 Spring 框架的侵入。例如,已存在一个 ConnectionFactory 类用于获取数据库连接对象,而该类没有实现 FactoryBean 接口。

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 对象,然后创建 idconn 的对象,来自于工厂对象 connectionFactory 的工厂方法 getConnection

<bean id="connectionFactory" class="cool.yzt.factorybean.ConnectionFactory"/>
<bean id="conn" factory-bean="connectionFactory" factory-method="getConnection"/>

获取使用创建的对象

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Connection conn = ctx.getBean("conn",Connection.class);

静态工厂

实际上,创建复杂对象的工厂有时并不需要创建其自身的实体对象,调用者只需要调用其静态方法就可以获得对应的对象,例如之前写过的数据库连接 JDBC 工具类和 Jedis 工具类,这时使用静态工厂更加方便,首先改造刚才的 ConnectionFactory

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 返回 idconn 的对象

<bean id="conn" class="cool.yzt.factorybean.ConnectionFactoryStatic" factory-method="getConnection"/>

获取使用创建的对象

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Connection conn = ctx.getBean("conn",Connection.class);

总结 Spring 工厂创建对象

Spring工厂创建对象

控制 Spring 工厂创建对象的次数

之前说到,有些对象只需创建一次,有些对象需要每一次都创建新的,以节省内存。所以我们需要控制创建对象的次数,在 Spring 中,通过配置文件中 <bean> 标签的 scope 属性就可以控制对象创建的次数,如果该属性的值为 singleton,则表明创建的对象是单例的,该对象会在 Spring 读取配置文件时创建对象,用户调用 getBean 方法获取的对象都是同一个,如果该属性值为 prototype,则表明用户每调用一次 getBean 方法都会获取一个新的对象。特别的,对于实现 FactoryBean 接口的类,可以通过 isSingleton() 方法的返回值来控制。

JavaEE 开发中,常见的只创建一次的对象有 SqlSessionFactoryDAOService等,每次都创建新的对象有 SqlSessionConnection 等。

参考

孙哥说Spring5