Spring入门(上)-工厂篇
文章目录
- Spring入门(上)-工厂篇
- 一:引言
- 1:EJB存在的问题
- 2:什么是Spring
- 3:设计模式和反射工厂
- 二:第一个spring程序
- 1:环境搭建
- 2:核心API - ApplicationContext
- 2.1:核心API和两大实现类
- 2.2:低级容器和高级容器
- 3:程序开发和使用
- 3.1:使用流程
- 3.2:常见方法
- 3.3:配置文件中的注意事项
- 4:底层实现原理【简易版本】
- 5:Spring和日志框架整合
- 三:注入Injection
- 1:注入概述
- 2:set注入
- 2.1:JDK内置类型
- 2.2:自定义类型
- 2.3:简化写法
- 3:构造器注入
- 3.1:使用方法
- 3.2:构造方法重载问题
- 4:注入总结
- 四:控制反转(IOC)和依赖注入(DI)
- 1:控制反转IOC
- 2:依赖注入DI
- 五:复杂对象的创建
- 1:什么是复杂对象
- 2:创建复杂对象的三种方式
- 2.1:FactoryBean接口
- 2.2:实例工厂
- 2.3:静态工厂
- 六:控制对象的创建次数
- 1:如何控制对象的创建次数
- 1.1:简单对象的创建次数
- 1.2:复杂对象的创建次数
- 2:为什么要控制对象的创建次数
- 七:对象的生命周期
- 1:创建阶段
- 2:初始化阶段
- 3:销毁阶段
- 4:总结一下(面试重点)
- 八:配置文件参数化
- 1:配置文件参数化
- 2:开发步骤
- 九:自定义类型转换器
- 1:类型转换器
- 2:自定义类型转换器
- 十:后置处理Bean
- 1:BeanPostProcessor概述和原理
- 2:开发步骤
一:引言
1:EJB存在的问题
运行环境苛刻 + 代码移植性性差 = 重量级框架
2:什么是Spring
spring = 轻量级的 + JavaEE解决方案的框架,没有引入新的技术,但是整合了众多的优秀设计模式
-
所谓轻量级,是针对EJB而言:对于运行环境是没有要求的【开源tomcat, jetty等】,同时代码移植性高【 不用实现额外的移植接口】
-
所谓JAVAEE的通用解决方案:是Spring的整合内容包含了JAVA分层的各个阶段:而不像struts2只整合Controller层,Mybatis只整合DAO层
3:设计模式和反射工厂
什么是设计模式
- 狭义:GOF提出的23种设计模式:创建型 + 结构型 + 行为型
- 广义:面向对象设计中,解决特定问题的经典代码
spring中常用的四个设计模式是:工厂模式【工厂 + 反射 = spring核心】,模板方法模式【可扩展性】,代理模式【AOP】,策略模式【子类的选择】
什么是工厂模式
通过工厂类创建对象,而不是通过显式的new()方式进行对象的创建,这样可以做到解耦合【解除代码之间的强关联关系】
因为一旦代码中出现强耦合,就会不利于代码的维护
UserService userSerivce = new UserServiceImpl(); // 强耦合
而如何解耦合呢,这时候就要想到对象的创建方式除了使用显式的new()创建还有什么方式了 -> 反射,所以,可以通过反射工厂来进行对象的创建实现解耦
public class MyBeanFactory {/*** 创建person对象* <p>* 对象的创建方式:* (1)显式创建:Person person = new Person()* (2) 隐式创建:反射 -> class.forName(全限定类型名) + newInstance()* </p>* @return person对象实例*/public static Person getPersonInstance() {Person person = null;try {Class clazz = Class.forName("com.cui.commonboot.suns_spring.entity.Person");person = (Person) clazz.newInstance();} catch (Exception e) {e.printStackTrace();}return person;}
}
但是上述方式显然还是有两个问题:
(1)如果有多个类要进行工厂创建,是不是要写很多个这样的getXXXInstance()?
(2)这里面的Class.forName("com.cui.commonboot.suns_spring.entity.Person");
并没有实现解耦,如果要改对应的类还是要到代码里去改
针对问题一:可以发现所有的类的工厂创建流程都是一样的,所以可以使用一个通用的方式进行创建【返回Object】
针对问题二:可以使用外部配置文件进行加载,然后使用java.util.Properties
获取外部配置文件信息,这样就可以做到解耦了
// ================== 通用反射工厂 =================
package com.cui.commonboot.suns_spring.config;import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;/*** <p>* 功能描述:my bean factory* </p>** @author cui haida* @date 2024/01/06/9:32*/
public class MyBeanFactory {// 加载配置// 将properties配置文件中的内容读取到Properties中private static Properties env = null;// 因为io比较耗时,所以放到静态代码块中static {try {// 加载配置文件InputStream in = MyBeanFactory.class.getResourceAsStream("/applicationContext.properties");// 装载到env中env.load(in);} catch (IOException e) {e.printStackTrace();}}/*** get bean from factory* @param name bean name* @return bean instance*/public static Object getBean(String name) {Object bean = null;try {// 获取指定的类String property = env.getProperty(name);Class clazz = Class.forName(property);bean = clazz.newInstance();} catch (Exception e) {e.printStackTrace();}return bean;}
}
# ==================== applicationContext.properties ===============
person=com.cui.commonboot.suns_spring.entity.Person
二:第一个spring程序
1:环境搭建
maven依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.4.RELEASE</version>
</dependency>
配置文件
Spring对于配置文件的放置位置和命名没有任何的要求,一般放在resource下
日后在应用spring框架时,需要进行配置文件的路径的设置
2:核心API - ApplicationContext
2.1:核心API和两大实现类
作用是:Spring提供的这个工厂可以用于对象的创建
好处:解耦合
特点:是一个接口,这样可以完美的屏蔽实现的差异
- web环境:XmlWebApplicationContext
- 非web环境:ClassPathXmlApplicationContext(main, Junit)
ApplicationContext是一个重量级资源【工厂的对象会占用大量的内存空间】
所以spring不会频繁的创建对象,一个应用只会创建一个工厂对象避免占用大量内存空间
同时ApplicationContext内部含有大量的独占锁,所以是线程安全的,支持多线程并发访问
2.2:低级容器和高级容器
在 Spring 中,有两种 IoC 容器:BeanFactory
和 ApplicationContext
。(ApplicationContext是BeanFactory的子类)
BeanFactory
:Spring 实例化、配置和管理对象的最基本接口。
public interface BeanFactory {/*** 用来引用一个实例,或把它和工厂产生的Bean区分开* 如果一个FactoryBean的名字为a,那么,&a会得到那个Factory*/String FACTORY_BEAN_PREFIX = "&";/** 四个不同形式的getBean方法,获取实例*/Object getBean(String name) throws BeansException;<T> T getBean(String name, Class<T> requiredType) throws BeansException;<T> T getBean(Class<T> requiredType) throws BeansException;Object getBean(String name, Object... args) throws BeansException;// 根据名称判断bean是否存在boolean containsBean(String name);// 是否为单实例Beanboolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否为原型(多实例)boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 名称、类型是否匹配boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;// 获取类型Class<?> getType(String name) throws NoSuchBeanDefinitionException;// 根据实例的名字获取实例的别名String[] getAliases(String name);
}
- ApplicationContext以更加面向框架的风格增强了BeanFactory,并提供了一些适用于企业应用程序的特性。
-
默认初始化所有的Singleton,也可以通过配置取消预初始化。
-
继承MessageSource,因此支持国际化。
-
资源访问,比如访问URL和文件。
-
事件传播特性,即支持aop特性。
-
同时加载多个配置文件。
-
以声明式方式启动并创建Spring容器。
-
3:程序开发和使用
3.1:使用流程
只有三个步骤:创建类型 -> 配置文件的配置【xxx.xml】-> 通过工厂类【ApplicationContext的两个实现类】,获得对象
<?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"><!--id:名字 - 唯一性class属性:配置全限定类名--><bean id="person" class="org.example.entity.Person"/></beans>
@org.junit.Testpublic void test() {// 通过工厂读取配置文件,因为这里是非web环境,所以是ClassPathXmlApplicationContext实现类工厂ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/bean.xml");// 通过工厂加载bean后获取对象实例,参数是配置文件中的id值Person person = (Person) ctx.getBean("person");System.out.println("person is: " + person);
}
3.2:常见方法
// getBean1: 通过工厂加载bean后获取对象实例,参数是配置文件中的id值
Person person = (Person) ctx.getBean("person");
// getBean2: 可以即指定id, 又指定返回值类型,这样可以不用强转
Person person0 = ctx.getBean("person", Person.class);
// getBean3:当前的配置文件中,如果只有一个类型是person的,可以只使用class作为参数,不指定id
Person person1 = ctx.getBean(Person.class);// 获取所有的bean的id值列表
String[] allBeanNames = ctx.getBeanDefinitionNames();
for (String beanName : allBeanNames) {System.out.println(beanName);
}// 获取指定类型的所有的bean的id值列表
String[] pointTypeAllBeanNames = ctx.getBeanNamesForType(Person.class);
for (String beanName : pointTypeAllBeanNames) {System.out.println(beanName);
}// 判断是否存在指定id值的bean, name属性不生效
boolean flag = ctx.containsBeanDefinition("person");
System.out.println("flag is: " + flag);// 判断是否存在指定id值的bean, name属性也生效
boolean flagWithName = ctx.containsBean("p");
System.out.println("flag is: " + flagWithName);
3.3:配置文件中的注意事项
只配置class属性
<bean class="org.example.entity.Person"></bean>
如果这个bean主需要使用一次,那么就可以省略id值,反之如果bean会使用多次或者会被其他bean引用需要设置id值
name属性:别名,可重复【逗号分割多个别名】
<bean id="person" name="p, p1, p2" class="org.example.entity.Person"/>
别名可以指定多个,但是id属性只能指定一个【你可以有多个小名,但是只能有一个大名】
ctx.containsBeanDefinition(String beanName)
传入别名的时候识别不了,ctx.containsBean(name)
可以识别
4:底层实现原理【简易版本】
这个是简易版本,不代表底层具体实现逻辑
🎉 spring工厂可以调用对象的私有构造方法创建对象
🎉 理论上程序的所有的对象都会交给spring工厂创建,但是实际上实体对象一般不会交给Spring管理创建,因为要操作数据库,所以一般会由持久层框架进行创建
5:Spring和日志框架整合
spring和日志框架进行整合,可以十分方便的在控制台输出运行过程中的重要的信息
<!-- spring整合日志 -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
<!-- 日志门面:将默认的logback和log4j2干掉,进而使得spring5可以整合支持log4j -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version>
</dependency>
然后引入log4j.properties
# 配置根
log4j.rootLogger = debug, console# 输出到控制台的配置
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
三:注入Injection
1:注入概述
所谓注入就是通过spring工厂和配置文件对创建的对象的成员变量赋值的操作
而常规的赋值操作是通过硬编码的方式,存在耦合
// 常规的变量赋值方法是通过set方法
Person person = (Person) ctx.getBean("person");
person.setId(1);
person.setName("cui");
而spring提供的注入方式可以消除这种硬编码,避免耦合
<bean id="person" name="p, p1, p2" class="org.example.entity.Person"><property name="id"><value>2</value></property><property name="name"><value>cui</value></property>
</bean>
2:set注入
针对于不同的类型的成员变量,在<property>
标签中需要嵌套其他的标签
2.1:JDK内置类型
8种基本类型 + String
-
<value>
-
<property name="id"><value>2</value> </property> <property name="name"><value>cui</value> </property>
数组
-
<list>
-<value>
, 里面也可以是其他类型的 -
<property name="emails"><list><value>111111@qq.com</value><value>123456@123.com</value></list> </property>
set集合
-
无序性,唯一性
-
<set>
-<value>/<ref>.....
-
<property name="emails"><set><value>111111@qq.com</value><value>123456@123.com</value></set> </property>
list集合
- 有序性,可重复
<list>
-<value>/<ref>.....
map
-
<map>
-<entry>
-<key>
&<value>
-
<property name="emails"><map><entry><key>张三</key><value>111111@qq.com</value></entry><entry><key>李四</key><value>111111@qq.com</value></entry></map> </property>
properties
-
是一种特殊的map[key = string, value = string]
-
<property name="p"><props><prop key="张三">value1</prop><prop key="李四">value2</prop></props> </property>
2.2:自定义类型
第一种方式
为成员变量提供get/set方法
配置文件中进行注入赋值【bean】
<bean id="userService" class="org.example.service.impl.UserServiceImpl"><property name="userDao"><bean class="org.example.Dao.impl.UserDaoImpl"/></property>
</bean>
但是这种方式会导致:配置文件代码冗余 & 被注入的对象会被多次创建,导致大大的浪费内存资源
第二种方式
<bean id="userDao" class="org.example.Dao.impl.UserDaoImpl"></bean><bean id="userService" class="org.example.service.impl.UserServiceImpl"><property name="userDao"><ref bean="userDao"/></property>
</bean>
2.3:简化写法
基于属性的简化
<bean id="student" class="springProject.Student"><!-- 通过property标签给各个成员变量赋值。name:成员变量名。value:数值类型的值。ref:引用类型的值。 --><property name="stuNo" value="2"></property><property name="userDao" ref="userDao"></property>
</bean>
p方式简化
需要引入p命名空间xmlns:p="http://www.springframework.org/schema/p"
。
然后在bean标签内使用p:成员变量名
这种方式来赋值
<!--
简单类型:p:属性名="属性值"
引用类型(除了String外):p:属性名-ref="引用的id"
注意多个 p赋值的时候 要有空格。 -->
<bean id="student" class="springProject.Student" p:userDao-ref="userDao" p:stuNo="123"></bean>
3:构造器注入
3.1:使用方法
Spring调用构造方法,通过配置文件,为成员变量赋值
- 使用这种方式的前提是需要bean类提供有参构造的方法
- 如果不写index,将使用默认顺序
<bean id="student" class="springProject.Student"><!-- index:构造方法中成员变量的顺序,从0开始。value、ref的含义同property。 --><constructor-arg value="3" index="0" ></constructor-arg><constructor-arg value="2" index="2" ></constructor-arg><constructor-arg value="zs" index="1"></constructor-arg>
</bean>
3.2:构造方法重载问题
- 参数个数不同的时候,可以通过控制
constructor-arg
标签个数进行区分 - 参数个数相同的时候,可以指定type属性,然后根据type属性进行区分
<bean id="person2" class="org.example.entity.Person"><constructor-arg type="int" value="15"/>
</bean><bean id="person3" class="org.example.entity.Person"><constructor-arg value="haha"/>
</bean>
4:注入总结
在应用中大多数用的都是set注入,因为构造注入有重载问题需要注意和解决,同时Spring底层大量应用了set注入
四:控制反转(IOC)和依赖注入(DI)
1:控制反转IOC
- IOC是Spring核心特性之一,另一个核心特性是AOP
- IOC(Inverse of Controll) -> 解耦合,底层是工厂设计模式
- IoC 意味着将你设计好的对象交给容器控制
谁控制谁,控制什么
传统 JavaSE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;
而 IoC 是有专门一个容器来创建这些对象,即由 IoC 容器来控制对象的创建;
- 谁控制谁?当然是 IoC 容器控制了对象;
- 控制什么?那就是主要控制了外部资源获取(对于成员变量赋值的控制权,不只是对象包括比如文件等)。
为何是反转,哪些方面反转了
传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;
- 为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
- 哪些方面反转了?依赖对象的获取被反转了。
2:依赖注入DI
DI(Dependency Injection) -> 依赖注入是 IoC 的最常见形式。
注入:通过Spring的工厂和配置文件,为对象(bean,组件)的成员变量进行复制
依赖注入:当一个类需要另一个类的时候,就意味着依赖,依赖出现依赖,就可以将另一个类作为本类的成员变量,最终通过Spring配置文件进行注入
五:复杂对象的创建
1:什么是复杂对象
所谓复杂对象,就是指不能直接通过new方法创建的对象
2:创建复杂对象的三种方式
2.1:FactoryBean接口
实现FactoryBean接口,然后Spring配置文件配置即可完成
在后续学习Spring整合其他框架的时候,底层会大量的运用到这个接口
FactoryBean介绍
FactroyBean接口共有三个需要实现的方法来辅助我们创建复杂的对象,三个方法分别是:
public interface FactoryBean<T> {// 用于书写创建复杂对象的代码,并将复杂对象作为方法的返回值返回@NullableT getObject() throws Exception;// 用于返回所创建的复杂的对象的class对象@NullableClass<?> getObjectType();// 如果该对象只需要创建一次,将返回true[单例为true]// 多例为false// 默认是单例default boolean isSingleton() {return true;}
}
<bean id="conn" class="com.xxx.xxx.ConnectionFactoryBean"></bean>
FactoryBean获取对象
虽然上面的Spring配置文件中创建复杂对象的配置方法和简单对象的配置方法一致,但是本质却有很大的不同
其他细节
- 如果就想获得FactoryBean类型的对象, 可以使用
getBean("&conn")
【&标记】,此时获得的就是ConnectionFactoryBean对象 - DI的体会和运用
<bean id="conn" class="com.xxx.xxx.ConnectionFactoryBean"><!-- 作为属性,DI --><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/cui?useSSL=false"/><property name="userName" value="root"/><property name="password" value="314159"/>
</bean>
实现原理【思想层面】
接口 + 反射,什么都能做 -> FactoryBean的核心原理就是接口回调
- 根据conn获得
<bean>
标签相关信息,并判断后面指定的全限定类名的类是不是属于FactoryBean的子类?- 如果是:进行下一步
- 如果不是:说明是简单对象,直接底层
new()
- 发现是子类,就要实现FactoryBean的
getObject()
方法,这个方法返回的内容就是ctx.getBean(xxx)
返回的内容
2.2:实例工厂
在有些老的项目中,有一些已经写好的复杂对象的创建方法没有整合到Spring容器中,此时就需要实例工厂和Spring的整合了
// 已经写好的复杂对象的创建方法,没有实现FactoryBean接口
public class ConnectionFactory {// 数据库连接public Connection getConnection() {Connection conn = null;try {Class.forName("com.mysql.jdbc.Driver");conn = DriverMannger.getConnection("jdbc:mysql://localhost:3306/cui?useSSL=false", "root", "314259");} catch (Exception e) {e.printStackTrace();}}
}
此时就可以通过配置文件将其加入到Spring容器的控制范围
<bean id="connFactory" class="com.xxx.xxx.ConnectionFactory"></bean><!-- factory-bean目的是注册connFactory为FactoryBean结构 -->
<!-- factory-method指明对标getObject的是那个方法 -->
<bean id="conn" factory-bean="connFactory" factory-method="getConnection"></bean>
2.3:静态工厂
静态工厂和实例工厂的唯一不同之处就是创建复杂对象的那个方法是静态的
// 已经写好的复杂对象的创建方法,没有实现FactoryBean接口
public class ConnectionFactory {// 静态的public static Connection getConnection() {Connection conn = null;try {Class.forName("com.mysql.jdbc.Driver");conn = DriverMannger.getConnection("jdbc:mysql://localhost:3306/cui?useSSL=false", "root", "314259");} catch (Exception e) {e.printStackTrace();}}
}
<!-- 类名.静态方法名() -->
<bean id="conn" class="com.xxx.xxx.ConnectionFactory" factory-method="getConnection"></bean>
六:控制对象的创建次数
1:如何控制对象的创建次数
1.1:简单对象的创建次数
<bean id="person" scope="singleton|prototype" class="xxx.Person"></bean>
singleton:只会创建一次简单对象【默认就是单例的】
prototype:每一次都会创建新的对象
1.2:复杂对象的创建次数
在FactoryBean中的isSingleton()
方法中指定
public class xxxFactoryBean implements FactoryBean {// 如果该对象只需要创建一次,将返回true[单例为true, 默认]// 多例为false默认是单例boolean isSingleton() {return true;}
}
2:为什么要控制对象的创建次数
因为要节省不必要的内存浪费
- 创建一次的对象常见的有:DAO,Service,Controller,SqlSessionFactory
- 每次都要新创建的对象常见的有:Connection,SqlSession,Session,Struts2 Action
七:对象的生命周期
对象的生命周期是指一个对象创建,使用到销毁的完整过程,熟悉对象的生命周期有利于更好的使用Spring管理创建对象
1:创建阶段
Spring工厂创建对象的时机取决于对象的创建次数:
- 如果是单例的,将在Spring工厂创建的同时,创建对象;
- 如果是多例的,将在获取对象的同时创建对象(
ctx.getBean(xxx)
)
🎉 如果想要在获取对象的时候创建单例对象,需要在<bean>
标签中加入lazy-init="true"
属性
2:初始化阶段
Spring工厂在创建完成对象[完成依赖注入]之后,调用对象的初始化方法,完成对应的初始化操作:
- 初始化方法的提供:由程序员根据需求提供初始化方法,最终完成初始化操作
- 初始化方法的调用:由Spring工厂进行调用
定义初始化方法有两种途径:
第一种方法是实现InitializingBean这个接口
这个接口提供了一个afterPropertiesSet()
方法,这个方法允许属性赋值之后做一些额外操作
这种方式耦合了Spring框架,需要实现InitializingBean接口
public class Product implements InitializingBean {public Product() {System.out.println("this is no args structure");}/*** 在这里可以做一些初始化操作 spring 会调用*/@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("Product.after properties set");}
}
第二种方法是配置文件中配置普通方法
public void myInit() {// 自己根据自己的需求,随便写初始化方法逻辑
}
<bean id="product" class="xxx.Product" init-method="myInit"></bean>
🎉 如果既实现了InitializingBean接口,又指定了init-method方法那么接口的顺序 > init-method方法的顺序
🎉 先进行注入才会执行初始化[先DI,再init]
🎉 初始化操作的主要是数据库,IO,网络流…
3:销毁阶段
Spring在调用ctx.close()
之后便会进行对象的销毁,而对象的销毁也是有两种方式
第一种方式是通过实现DisposableBean接口中的
destory()
方法
public class Product implements InitializingBean, DisposableBean {public Product() {System.out.println("this is no args structure");}/*** 在这里可以做一些初始化操作 spring 会调用*/@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("Product.after properties set");}@Overridepublic void destroy() throws Exception {System.out.println("this is destroy method");}
}
第二种方法是配置文件中配置普通方法为
destroy-method
属性
public myDestroy() throws Exception {// 自定义销毁方法
}
<bean id="product" class="xxx.Product" init-method="xxx" destory-method="myDestroy"></bean>
🎉 销毁方法只适用于单例模式的bean
🎉 所谓的销毁操作就是资源的释放操作io.close();connection.close()
4:总结一下(面试重点)
Spring Bean 生命周期简单概括为4个阶段:
- 实例化,创建一个Bean对象
- 填充属性,为属性赋值
- 初始化
- 如果实现了
xxxAware
接口,通过不同类型的Aware接口拿到Spring容器的资源 - 如果实现了BeanPostProcessor接口,则会回调该接口的
postProcessBeforeInitialzation
和postProcessAfterInitialization
方法 - 如果配置了
init-method
方法,则会执行init-method
配置的方法
- 如果实现了
- 销毁
- 容器关闭后,如果Bean实现了
DisposableBean
接口,则会回调该接口的destroy
方法 - 如果配置了
destroy-method
方法,则会执行destroy-method
配置的方法
- 容器关闭后,如果Bean实现了
八:配置文件参数化
1:配置文件参数化
所谓配置文件参数化就是:将Spring配置文件中需要经常修改的字符串信息转移到一个更小的配置文件中 -> 利于维护性
2:开发步骤
- 提供一个小的配置文件(.properties) -> 名字随便,放置位置随便
# 假设叫做db.properties,位于resource根路径下
jdbc.driverName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/cui?useSSL=false
jdbc.username = root
jdbc.password = 314159
- Spring配置文件和小的配置文件进行整合
<context:property-placeholder location="classpath:/db.propertied"/>
- 通过
${key}
获取对应的值
九:自定义类型转换器
1:类型转换器
所谓类型转换器就是Spring通过类型转换器将配置文件中的字符串类型的数据转换成为对象中对应类型的数据,进而完成注入工作
2:自定义类型转换器
当Spring内部没有提供特定类型的转换器的时候【例如String -> Date】,就需要在开发过程中自定义类型转换器
而如果需要自己实现类型转换器,就要实现Convert接口,然后在Spring配置文件中进行注册
package org.example.config;import org.springframework.core.convert.converter.Converter;import java.text.SimpleDateFormat;
import java.util.Date;/*** <p>* 功能描述:自定义类型转换器* Converter<原始类型, 自定义类型>* </p>** @author cui haida* @date 2024/01/06/17:21*/
public class MyDateConverter implements Converter<String, Date> {// converter方法作用:String -> Date// SimpleDateFormat sdf = new SimpleDateFormat(format);// sdf.parse(String) --> Date@Overridepublic Date convert(String s) {Date date = null;try {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");date = sdf.parse(s);} catch (Exception e) {e.printStackTrace();}return date;}
}
告知Spring框架,我们创建的是一个类型转换器,所以要在Spring中注册
<bean id="myDateConverter" class="xxx.MyDateConverter"></bean>
同时还要在Spring中声明用于注册类型转换器的服务类
<!-- 创建MyDateConverter对象 -->
<bean id="myDateConverter" class="org.example.config.MyDateConverter"/><!-- 用于注册类型转换器 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"><property name="converters"><!-- converters是一个set类型的 --><set><ref bean="myDateConverter"/> <!-- 注册刚才创建的MyDateConverter对象 --></set></property>
</bean>
🎉 Spring支持一种日期的类型转换器:2024/1/6
这种格式的,其他格式的都不支持,需要自定义
十:后置处理Bean
1:BeanPostProcessor概述和原理
BeanPostProcessor的作用就是对Spring工厂所创建的对象进行再加工操作,AOP的底层原理大量的就是用的这个
再看一遍生命周期这张图,回顾下BeanPostProcessor所处的位置
而对于BeanPostProcessor规定的接口中有两个要实现的方法
public interface BeanPostProcessor {/*** 作用:Spring创建完成对象,并进行注入之后【完成属性赋值和容器赋值】,可以运行Before方法进行加工* 获得Spring创建好的对象,通过方法的参数* 最终通过获取返回值交给Spring框架*/@Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}/*** 作用:Spring执行完成对象的初始化之后【init-method or InitalzingBean中的setAfterProperties】,可以运行After方法进行加工* 获得Spring创建好的对象,通过方法的参数* 最终通过获取返回值交给Spring框架*/@Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}
}
🎉 因为在实战中很少去处理Spring的初始化操作,所以没有必要去区分Before和After,此时二者只需要实现一个即可
⚠️ 一定要返回bean对象在before方法中
2:开发步骤
1:类要实现BeanPostProcessor接口
package org.example.beanpost;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;/*** <p>* 功能描述:* </p>** @author cui haida* @date 2024/01/07/8:19*/
public class MyBeanPostProcessor implements BeanPostProcessor {/*** 前置方法* @param bean bean* @param beanName bean id*/@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}/*** 后置方法* @param bean bean* @param beanName bean id*/@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {Category category = (Category) bean;category.setName("zhangsan");return category;}
}
2:Spring的配置文件中进行配置
<bean id="c" class="org.example.beanpost.Category"><property name="id" value="10"/><property name="name" value="cui"/>
</bean><bean id="myBeanPostProcessor" class="org.example.beanpost.MyBeanPostProcessor"/>
因为BeanPostProcessor会对工厂中所有的bean进行监控,所以在重写前置方法或者后置方法的时候可能要做特殊的处理
/*** 后置方法* @param bean bean* @param beanName bean id*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof Category) { // or beanName.equals("xxxbeanId")Category category = (Category) bean;category.setName("zhangsan");}return bean;
}