五、Spring工厂
5.1、简单对象和复杂对象
5.1.1、简单对象
简单对象指的就是可以直接通过调用构造方法(new)创建出来的对象。
5.1.2、复杂对象
复杂对象指的就是不可以直接通过调用构造方法(new)创建出来的对象。比如JDBC的Connection
对象、Mybatis的SqlSessionFactory
对象。
5.2、Spring创建复杂对象的三种方式
5.2.1、FactoryBean
5.2.1.1、FactoryBean接口
如果在applicationContext.xml
配置文件中配置的class
属性是FactoryBean
接口的实现类,那么通过id
属性获得的是这个类所创建的复杂对象(底层会调用重写的getObject()
方法)。
public class MyFactoryBean implements FactoryBean<Connection> { // 用于书写创建复杂对象的代码,并且把复杂对象作为方法的返回值返回 @Override public Connection getObject() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","123456"); return connection; } // 返回所创建的复杂对象的Class对象 @Override public Class<?> getObjectType() { return Connection.class; } // 配置是否是单例模式 @Override public boolean isSingleton() { return false; } 复制代码
<bean id="factoryBean" class="com.test.MyFactoryBean"> 复制代码
/** * 用于测试factoryBean */ @Test public void testFactoryBean(){ ClassPathXmlApplicationContext ctr = new ClassPathXmlApplicationContext("/applicationContext.xml"); Connection conn = (Connection) ctr.getBean("factoryBean"); System.out.println(conn); } 复制代码
5.2.1.2、FactoryBean接口的细节
- 如果我不想获得创建的复杂对象(Connection),想获得普通的简单对象(FactoryBean),我们仅仅只需在getBean(id)的前面加一个
&
即可。
import java.sql.Connection; import java.sql.DriverManager; import org.springframework.beans.factory.FactoryBean; /** * @Description * @Author XiaoLin * @Date 2021/2/24 19:47 */ public class MyFactoryBean implements FactoryBean<Connection> { @Override public Connection getObject() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager .getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false", "root", "1101121833"); return connection; } @Override public Class<?> getObjectType() { return Connection.class; } @Override public boolean isSingleton() { return true; } } 复制代码
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="factoryBean" class="MyFactoryBean"> </bean> </beans> 复制代码
import java.sql.Connection; import org.junit.Test; import org.springframework.beans.factory.FactoryBean; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Description * @Author XiaoLin * @Date 2021/2/24 19:50 */ public class MyFactoryBeanTest { /** * 用于测试复杂类型对象的创建 */ @Test public void testMyFactoryBeanTest(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml"); MyFactoryBean connection = (MyFactoryBean)applicationContext.getBean("&factoryBean");// 获取普通的简单对象FactoryBean,不获取复杂的Connection对象 System.out.println(connection); } } 复制代码
- isSingleton()方法,如果返回值为true时,他只会创建一个对象,返回false时会创建多个对象,一般根据对象的特点来判断返回true(SqlSessionFactory)还是false(Connection)。
5.2.1.3、BeanFactory实现原理图
5.2.1.4、FactoryBean总结
FactoryBean是Spring中用于创建复杂对象的一种方式 也是Spring原生提供的,后面框架整合会大量运用。
5.2.2、实例工厂
5.2.2.1、FactoryBean的弊端
使用FactoryBean的话有Spring的侵入,实现了FactoryBean接口,一旦离开了Spring,整个类都无法使用。
5.2.2.2、实例工厂的使用
// 实例工厂 public class ConnectionFactory { public Connection getConnection(){ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","1101121833"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return conn; } } 复制代码
<bean id="connFactory" class="com.factory.ConnectionFactory"/> <bean id="conn" factory-bean="connFactory" factory-method="getConnection"/> 复制代码
5.2.3、静态工厂
前面我们学了实例工厂,由于实例工厂的getConnection()
方法是实例方法,需要由对象来调用,所以需要先创建对象然后再通过对象来调用方法。
而静态工厂由于getConnection()
方法是静态方法,不需要由对象来调用,直接通过类进行调用。这就是实例工厂与静态工厂最大的区别。
public class ConnectionStaticBeanFactory { public static Connection getConnection(){ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql:///javaweb?characterEncoding=utf-8&useSSL=false","root","1101121833"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return conn; } } 复制代码
<bean id="staticBeanFactory" class="com.factory.ConnectionStaticBeanFactory" factory-method="getConnection"/> 复制代码
5.3、创建对象的细节
5.3.1、控制简单对象的创建次数
控制简单对象的创建次数我们只需要配置bean
标签的scope
属性值即可。他常用的有两个值:
- singleton:默认为单例模式,只会创建一个简单对象。
- prototype:每次都会创建一个新的对象。
<bean id="person" scope="singleton(prototype)" class="com.doamin.Person"/> 复制代码
5.3.2、控制复杂对象的创建次数
FactoryBean接口的isSingleton()
方法的返回值来进行控制(如果没有isSingleton()
方法,那么还是通过scope
属性来进行控制):
- 返回true:只会创建一次。
- 返回false:每一次都会创建一个新的对象。
5.3.3、控制对象创建次数的原因
可以被大家共享的对象(SqlSessionFactory、各种Dao、Service)可以只创建一次,不可以被大家共享的对象(Connection、SqlSession、Controller)可以创建多次,控制对象创建次数的最大好处是可以节省不必要的内存浪费。
5.4、对象的生命周期
生命周期指的是一个对象的创建、存活、消亡的一个完整过程。由Spring来负责对象的创建、存活、销毁。了解生命周期,有利于我们使用好Spring为我们创建的对象。
Spring帮我们创建的对象有三个阶段:
- 创建阶段
- 初始化阶段
- 销毁阶段
5.4.1、创建阶段
- 当 scope = "singleton" 时,Spring工厂创建的同时,对象会随之创建。如果我们不想在Spring工厂创建的同时创建,想在获取对象的时候创建,只需在配置文件的
bean
标签添加一个lazy-init = true
即可。
- 当 scope = "prototype" 时,Spring工厂会在获取对象的同时创建对象。
5.4.2、初始化阶段
Spring工厂在创建完对象后,会调用对象的初始化方法,完成对应的初始化操作。
初始化方法是由程序员根据需求提供初始化方法,由Spring工厂调用,最终完成初始化操作。他有两种调用的方式:
- 实现InitializingBean接口(有Spring侵入的问题)。
- 提供一个普通方法并修改配置文件。
5.4.2.1、InitializingBean接口
// 这个就是初始化方法,做一些初始化操作,Spring会进行调用 @Override public void afterPropertiesSet() throws Exception { // 初始化操作 } 复制代码
5.4.2.2、提供普通方法
由于实现InitializingBean接口存在Spring侵入的问题,所以Spring提供了另一个方法给我们进行初始化操作,那就是提供一个普通的方法,然后去配置文件中增加init-method="方法名"
熟悉的配置即可。
public void init(){ System.out.println("我是初始化方法"); } 复制代码
<bean id="product" class="com.domain.Product" init-method="init"/> 复制代码
5.4.2.3、注意
如果一个对象既实现了InitializingBean接口同时又提供了普通的初始化方法,那么两个初始化方法都会执行,先执行的是InitializingBean
接口的方法,再执行普通的初始化方法。
在执行初始化操作之前,会先进行属性的注入,注入在前,初始化在后。
初始化需要做的操作一般是数据库、IO、网络操作。
5.4.3、销毁阶段
在工厂关闭之前,Spring会在销毁对象前,会调用对象的销毁方法,完成销毁操作。
销毁方法是程序员根据需求定义销毁方法,由Spring工厂调用销毁方法,完成销毁操作。他也有两种方法:
- 实现DisposableBean接口。
- 定义普通的销毁方法在配置文件中配置。
5.4.3.1、实现DisposableBean接口
public class Product implements InitializingBean, DisposableBean { @Override public void destroy() throws Exception { System.out.println("销毁操作,资源释放"); } } 复制代码
5.4.3.2、定义普通方法
public class Product implements InitializingBean, DisposableBean { public void MyDestory(){ System.out.println("自己定义的销毁方法"); } } 复制代码
<bean id="product" class="com.domain.Product" destroy-method="MyDestory"/> 复制代码
5.4.3.3、注意
- 销毁方法的操作只适用于scope="singleton"。
- 销毁操作主要指的是一些资源的释放操作。
5.5、Spring整合配置文件
一般来说像数据库的一些配置信息我们都不会直接写在代码里面,会将他们抽取出来成一个配置文件,再利用Spring进行注入。我们只需要加入一个标签即可完成。
<!--告诉Spring你的db.properties在哪里--> <context:property-placeholder location="classpath:/db.properties"/> <!--用$(db.properties中的key)来进行取值--> <bean id="conn" class="com.factory.BeanFactory"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> 复制代码
六、自定义类型转换器
6.1、类型转换器
我们写在Spring配置文件中赋值的值都是String类型的,但是我们的实体类是Interger类型的值,按照语法来说,String类型的值是不可以直接赋值给Integer类型的,但是为什么能直接赋值呢?
因为Spring内部帮我们进行了自动的类型转换,Spring通过类型转换器将配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,从而完成注入。
6.2、自定义类型转换器
6.2.1、问题引入
@Data @AllArgsConstructor @NoArgsConstructor public class People { private String name; private Date birthday; } 复制代码
<bean id="people" class="com.domain.People"> <property name="name" value="XiaoLin"/> <property name="birthday" value="2021-2-6"/> </bean> 复制代码
我们运行代码之后发现报错了,说String类型的值不可以转化为Date类型的值,说明Spring内部没有这个转换器。
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday': no matching editors or conversion strategy found at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:262) at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:585) ... 39 more 复制代码
Spring内部没有提供特定类型转换器时,而程序员在应用的过程中又需要使用,所以需要程序员自己定义类型转换器。
6.2.2、代码实现
自定义类型转换器我们分为两步实现:
- 实现
Converter<转换前的类型, 转换后的类型>
接口,并且重写里面的方法。 - 在配置文件中进行转换器的注册
public class MyConverter implements Converter<String, Date> {// 他有两个泛型,一个是转换前的类型,另一个是转换后的类型 /* convert方法的作用是将String->Date parm:source代表的是配置文件中需要转换的内容 return:把转换好的值作为返回值,Spring会自动为属性赋值 */ @Override public Date convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date parse = null; try { parse = sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return parse; } } 复制代码
<!-- 类型转换器的注册,告诉Spring我们所创建的MyConverter类是类型转换器类, Spring提供了一个类ConversionServiceFactoryBean来完成类型转换器的注册--> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters" > <set> <!-- 注册类型转换器--> <ref bean="myConvert"/> </set> </property> </bean> 复制代码
6.2.3、注意细节
- 创建
ConversionServiceFactoryBean
标签的id
必须为conversionService
,不然不生效。 - 其实Spring已经内置了日期类型的转换器,但是他只支持以
/
作为分隔符的字符串的格式:2021/2/6,不支持其他的格式,如果你的字符串格式已经是这种,就无需再写自定义类型转换器。
七、BeanPostProcessor
7.1、概述
BeanPostProcessor称为后置处理Bean,他的作用是对Spring工厂所创建的对象进行二次加工,他是AOP的底层实现,他本质上是一个接口。
7.2、BeanPostProcessor分析
他是一个接口,要实现他的两个方法:
Object postProcessBeforeInitialization(Object bean,String beanName)
:他的作用是在Spring创建完对象后,在进行初始化方法之前,- 通过参数获取到Spring创建好的对象,执行
postProcessBeforeInitialization
方法进行加工,最终通过返回值返回这个加工好的对象给Spring。 Object postProcessAfterInitialization(Object bean,Stirng beanName)
:Spring执行完对象的初始化操作之后,运行postProcessAfterInitialization
方法进行加工,通过参数获取Spring创建好的对象,最终通过返回值返回给Spring。
在日常的开发中,我们很少去处理Spring的初始化操作,所以没有必要区分前后,所以一般只需要实现其中一个方法即可,且BeanPostProcessor会对Spring工厂中的所有对象进行加工。
7.3、代码实现
@Data @AllArgsConstructor @NoArgsConstructor public class Student { private Integer id; private String name; } 复制代码
public class MyBeanPoster implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // 要进行类型判断,如果不是Student类型的话直接返回,不然会报类型转换错误,因为Spring会把工厂中的所有对象进行加工处理 if (bean instanceof Student){ Student student = (Student) bean; student.setName("lisi"); } return bean; } } 复制代码
<bean id="student" class="com.beanpost.Student"> <property name="id" value="10"/> <property name="name" value="zs"/> </bean> <!--注册后置Bean--> <bean id="myBeanProcessor" class="com.beanpost.MyBeanPoster"/>