参考自B站UP主视频《孙哥说Spring5》
什么是复杂对象?
Spring 工厂创建复杂对象的三种方式
(1)FactoryBean接口
开发步骤
实现FactoryBean
接口
- 实现
getObject
,getObjectType
,isSingleton
方法 getObject()
:用于书写创建复杂对象时的代码。getObjectType()
:返回创建的复杂对象的类型。isSingleton
:用于决定是否单例。
public class ConnectionFactoryBean implements FactoryBean<Connection> {
// 用于书写创建复杂对象时的代码
@Override
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring", "root", "1234");
return conn;
}
// 返回创建的复杂对象的类型
@Override
public Class<Connection> getObjectType() {
return Connection.class;
}
// 是否单例
@Override
public boolean isSingleton() {
return false; // 每一次都创建新的复杂对象
// return true; // 只创建一次这种类型的复杂对象
}
}
Spring 配置文件的配置
- 如果
class
中指定的类型是FactoryBean
接⼝的实现类,那么通过id值
获得的是这个类所创建的复杂对象。 - 比如下面
class
指定的是ConnectionFactoryBean
,获得的是Connection
对象。
<!--class 指定了 ConnectionFactoryBean, 获得的是该类创建的复杂对象 Connection -->
<bean id="conn" class="com.yusael.factorybean.ConnectionFactoryBean"/>
此处通过这个id
获得到的是getObject()
方法返回的conn
如果就想获得 FactoryBean
类型的对象,需要加个 &
,ctx.getBean("&conn")
ConnectionFactoryBean cfb = (ConnectionFactoryBean) ctx.getBean("&conn");
isSingleton
方法返回 true
只会创建⼀个复杂对象,返回 false
每⼀次都会创建新的对象;
- 需要根据这个对象的特点 ,决定是返回
true
(例如:SqlSessionFactory
) 还是false
(例如:Connection
);
上面的代码中,直接将字符串信息写在代码中,这样耦合性很大,可以改为依赖注入的方式
把 ConnectionFactoryBean
中依赖的 4 个字符串信息 ,通过配置⽂件进行注⼊。
@Getter@Setter // 提供 get set 方法
public class ConnectionFactoryBean implements FactoryBean<Connection> {
// 将依赖的字符串信息变为成员变量, 利用配置文件进行注入。
private String driverClassName; // com.mysql.jdbc.Driver
private String url; // jdbc:mysql://localhost:3306/spring
private String username; // root
private String password; // 1234
@Override
public Connection getObject() throws Exception {
Class.forName(driverClassName);
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
@Override
public Class<Connection> getObjectType() {
return Connection.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
配置文件中添加注入信息
<!--体会依赖注入, 好处: 解耦合, 今后要修改连接数据库的信息只需要修改配置文件, 无需改动代码-->
<bean id="conn" class="com.yusael.factorybean.ConnectionFactoryBean">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
FactoryBean 实现原理
原理:接口回调
问题:
- 为什么 Spring 规定
FactoryBean
接⼝实现getObject()
? - 为什么
ctx.getBean("conn")
获得的是复杂对象Connection
⽽非ConnectionFactoryBean
?
Spring 内部运行流程:
- 配置文件中通过
id
可以获得ConnectionFactoryBean
类的对象 ,进而通过instanceof
判断出是FactoryBean
接⼝的实现类; - Spring 按照规定
getObject()
—>Connection
; - 返回
Connection
;
总结
Spring 中⽤于创建复杂对象的⼀种方式,也是 Spring 原⽣提供的,后续 Spring 整合其他框架时会⼤量应⽤ FactoryBean 方式。
(2)实例工厂
为什么要用实力工厂的方式创建对象?
- 避免 Spring 框架的侵⼊,实现FactoryBean接口就是Spring框架的侵入
- 整合遗留系统,如果通过实现 FactoryBean接口 可能会出现冲突、又或者只能拿到字节码文件而不能拿到源代码文件
实现步骤
先写一个工厂类
public class ConnectionFactory {
public Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useSSL=false", "root", "1234");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return conn;
}
}
然后在配置文件中注入
<!--实例工厂-->
<!-- 先创建出工厂实例 -->
<bean id="connFactory" class="com.yusael.factorybean.ConnectionFactory"/>
<!-- 通过工厂实例里的方法创建复杂对象 -->
<!-- 如果要获得 conn 这个实例,可以通过工厂 connFactory ,调用 getConnection 方法获得 -->
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"/>
(3)静态工厂
和实例工厂的差别就是:获取对象的方法是个静态方法;
// 实例工厂获得实例的方式
new ConnectionFactory().getConnection();
// 静态工厂获得实例的方式
StaticConnectionFactory.getConnection();
实现步骤
先写一个静态工厂,StaticConnectionFactory 类
public class StaticFactoryBean {
// 静态方法
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useSSL=false", "root", "1234");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return conn;
}
}
配置文件
<!--静态工厂不需要实例,直接调用方法就可以获得-->
<bean id="conn" class="com.yusael.factorybean.StaticFactoryBean" factory-method="getConnection"/>
总结
- 对于复杂对象的创建,一般推荐使用 FactoryBean接口 来创建对象
控制 Spring 工厂创建对象的次数
在前面的创建方式中,要么是单例创建,要么是每次都创建一个新的实例
但是上述的方式创建的是复杂对象,那如果是简单对象的创建呢?
该如何控制简单对象的创建次数呢?
对于简单对象,如何控制创建次数?
配置文件中配置,Spring中默认是 singleton
sigleton
:只会创建⼀次简单对象,默认值;prototype
:每⼀次都会创建新的对象;
<!--控制简单对象创建次数-->
<bean id="scope" scope="singleton" class="com.yusael.scope.Scope"/>
对于FactoryBean接口的实现类,可以通过isSingleton
方法控制
- 如果是实例工厂或者静态工厂,没有
isSingleton
⽅法,与简单对象一样通过scope
控制。
问题来了:为什么我们要控制对象的创建次数呢?每次都创建一个实例好像也没有什么问题啊。
好处就是节省不必要的内存浪费。
对于一些重量级的、可以被共用的、线程安全的对象,没必要每次使用的时候都创建一个实例
- 特别是一些创建过程比较复杂的对象,每一次创建会带来内存的消耗
- 例如
SqlSessionFactory
很占内存、DAO层
、Service层
相反的,对于一些不能被共用的,线程不安全的对象,可以每次创建的时候都创建一个新的实例
- 例如:
Connection
、SqlSession | Session
、Strust2中的Action
拓展:SpringMVC中的Controller也是默认是单例的,但是单例模式下容易出现的问题就是controller中定义很多的属性,那么单例肯定会出现竞争访问,不同用户共享数据变量是不安全的。因此尽量不要在Controller中定义成员变量,否则就使用 prototype 。