文章目录
- 1 BeanFactory与ApplicationContext的关系
- 2 Spring基础环境下,常用的三个ApplicationContext
- 3 Spring开发中Bean的配置
- 4 Bean的初始化和销毁方法配置
- 5 Bean的实例化配置
- 6 Bean的依赖注入之自动装配
- 7 Spring 的 xml 标签(默认、自定义)
- 8 Spring 的get方法
- 9 配置非自定义Bean
- 10 Bean 实例化的基本流程
- 11 Spring的后处理器
1 BeanFactory与ApplicationContext的关系
- BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为Spring容器;
- ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装;
- Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。
- Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好。
2 Spring基础环境下,常用的三个ApplicationContext
实现类 | 功能描述 |
---|---|
ClassPathXmlApplicationContext | 加载类路径下的xml配置的ApplicationContext |
FileSystemXmlApplicationContext | 加载磁盘路径下的xml配置的ApplicationContext |
AnnotationConfigApplicationContext | 加载注解配置类的ApplicationContext |
3 Spring开发中Bean的配置
Bean的常用配置
Xml配置方式 | 功能描述 |
---|---|
<bean id=“” class=“”> | Bean的id和全限定名配置 |
<bean name=“”> | 通过name设置Bean的别名,通过别名也能直接获取到Bean实例 |
<bean scope=“”> | Bean的作用范围,BeanFactory作为容器时取值singleton和prototype |
<bean lazy-init=“”> | Bean的实例化时机,是否延迟加载。BeanFactory作为容器时无效 |
<bean init-method=“”> | Bean实例化后自动执行的初始化方法,method指定方法名 |
<bean destroy-method=“”> | Bean实例销毁前的方法,method指定方法名 |
<bean autowire=“byType”> | 设置自动注入模式,常用的有按照类型byType,按照名字byName |
<bean factory-bean=“” factory-method=“”/> | 指定哪个工厂Bean的哪个方法完成Bean的创建 |
beanName
例如:
配置UserDaolmpl由Spring容器负责管理
<bean id="testDaoService" class="com.hyl.service.TestDaoService"/>
此时存储到Spring容器(singleObjects单例池Map
)中的Bean的beanName是testDaoService,值是TestDaoService对象,可以根据beanName获取Bean实例
applicationContext.getBean ( "testDaoService");
如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
applicationContext.getBean ( "com.hyl.service.TestDaoService");
如果起别名,在没有配置id的时候,默认第一个别名为beanName;
如果配置的有id,同时也起了别名,这时候别名对应的还是id名,beanName还是id。
默认情况下,单纯的Spring环境Bean的作用范围有两个: Singleton和Prototype
- singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
- prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
Bean的延迟加载
当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的。
4 Bean的初始化和销毁方法配置
- 我们可以直接在bean里面配置对应的映射方法。
init-method="方法名1" destroy-method="方法名2"
- 我们还可以通过实现InitializingBean 接口,完成一些Bean的初始化操作,如下:
public class UserDaoImpl implements UserDao,InitializingBean {
public UserDaoImpl() {
System.out.println ( "UserDaoImpl创建了...");
}
public void init() {
System.out.println("初始化方法...");
}
public void destroy () {
System.out.println("销毁方法...");
}
//执行时机早于init-method配置的方法
public void afterPropertiesSet ( ) throws Exception {
System. out.println ( "InitializingBean . . . " )
}
}
5 Bean的实例化配置
Spring的实例化方式主要如下两种:
构造方式实例化:底层通过构造方法对Bean进行实例化
工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化
构造方式实例化Bean:
分为无参构造方法实例化和有参构造方法实例化,Spring中配置的几乎都是无参构;有参构造在实例化Bean时,需要参数的注入,通过<constructor-arg>标签,嵌入在<bean>标签内部提供构造参数
工厂方式实例化Bean,可分为如下三种:
静态工厂方法实例化Bean
实例工厂方法实例化Bean
实现FactoryBean规范延迟实例化Bean
静态工厂方法实例化Bean,其实就是定义一个工厂类,提供一个静态方法用于生产Bean实例,在将该工厂类及其静态方法配置给Spring即可。
//工厂类
public class UserDaoFactoryBean {
//非静态工厂方法
public static UserDao getUserDao(String name){
//可以在此编写一些其他逻辑代码
return new UserDaoImpl();
}
}
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean" factory-method="getUserDao">
<constructor-arg name="name" value="haohao"/>
</bean>
UserDaoImpl实例对象会存在于单例池中。
实例工厂方法,也就是非静态工厂方法产生Bean实例,与静态工厂方式比较,该方式需要先有工厂对象,在用工厂对象去调用非静态方法,所以在进行配置时,要先配置工厂Bean,在配置目标Bean。
//工厂类
public class UserDaoFactoryBean2 {
//非静态工厂方法
public UserDao getUserDao(String name){
//可以在此编写一些其他逻辑代码
return new UserDaoImpl();
}
}
<!-- 配置实例工厂Bean -->
<bean id="userDaoFactoryBean2" class="com.itheima.factory.UserDaoFactoryBean2"/>
<!-- 配置实例工厂Bean的哪个方法作为工厂方法 -->
<bean id="userDao" factory-bean="userDaoFactoryBean2" factory-method="getUserDao">
<constructor-arg name="name" value="haohao"/>
</bean>
在Spring容器创建时,就完成了Bean的实例化,单例池中既有工厂Bean实例,也有目标Bean实例.
注:
<constructor-arg>标签不仅仅是为构造方法传递参数,只要是为了实例化对象而传递的参数都可以通过<constructor-arg>标签完成,例如上面通过工厂方法实例化Bean所传递的参数也是要通过<constructor-arg>进行传递的
Spring提供的FactoryBean接口规范(源码)
package org.springframework.beans.factory;
public interface FactoryBean<T> {
//获得实例对象方法
T getObject() throws Exception;
//获得实例对象类型方法
Class<?> getObjectType();
boolean isSingleton();
}
实现过程
定义工厂实现FactoryBean
public class UserDaoFactoryBean3 implements FactoryBean<UserDao> {
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
}
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean3"/>
ps:
通过断点观察发现Spring容器创建时,FactoryBean被实例化了,并存储到了单例池singletonObjects中,但是getObject() 方法尚未被执行,UserDaoImpl也没被实例化,当首次用到UserDaoImpl时,才调用getObject() ,此工厂方式产生的Bean实例不会存储到单例池singletonObjects中,会存储到 factoryBeanObjectCache 缓存池中,并且后期每次使用到userDao都从该缓存池中返回的是同一个userDao实例。
6 Bean的依赖注入之自动装配
如果被注入的属性类型是Bean引用的话,那么可以在 标签中使用 autowire 属性去配置自动注入方式,属性值有两个:
byName:通过属性名自动装配,即去匹配 setXxx 与 id=“xxx”(name=“xxx”)是否一致;
<bean id="testDaoService" class="com.hyl.service.TestDaoService" autowire="byName"/>
<bean id="testDao" class="com.hyl.mapper.impl.TestDaoImpl"/>
public class TestDaoService {
private TestDao testDao;
public void setTestDao(TestDao testDao) {
System.out.println("注入bean"+testDao);
}
}
byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
<bean id="testDaoService" class="com.hyl.service.TestDaoService" autowire="byType"/>
<bean class="com.hyl.mapper.impl.TestDaoImpl"/>
public class TestDaoService {
private TestDao testDao;
public void setTestDao(TestDao testDao) {
System.out.println("注入bean"+testDao);
}
}
7 Spring 的 xml 标签(默认、自定义)
默认标签:
就是不用额外导入其他命名空间约束的标签,例如 <bean> 标签。
默认标签如下:
标签 | 作用 |
---|---|
<beans> | 一般作为 xml 配置根标签,其他标签都是该标签的子标签 |
<bean> | Bean的配置标签 |
<import> | 外部资源导入标签 |
<alias> | 指定Bean的别名标签,使用较少 |
<beans>标签
,除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境
<!--公开bean-->
<bean id =" " class=" " ></bean>
<!-- 配置测试环境下,需要加载的Bean实例 -->
<beans profile="test">
<bean id =" " class=" " ></bean>
...
</beans>
<!-- 配置开发环境下,需要加载的Bean实例 -->
<beans profile="dev">
<bean id =" " class=" " ></bean>
...
</beans>
/*
test和dev中的bean等一类配置是私有的,当激活某个环境时,创建对应的相关bean,
而除去特殊环境的,其他bean等配置,是公有的,就是激不激活其他环境,都会创建
*/
可以使用以下两种方式指定被激活的环境:
使用命令行动态参数,虚拟机参数位置加载 -Dspring.profiles.active=test
使用代码的方式设置环境变量 System.setProperty(“spring.profiles.active”,“test”)
<import>标签
,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过标签导入到一个主配置文件中,项目加载主配置文件就连同<import> 导入的文件一并加载了
<!--导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"/>
<!--导入商品模块配置文件-->
<import resource="classpath:ProductModuleApplicationContext.xml"/>
<alias> 标签
,是为某个Bean添加别名,与在<bean> 标签上使用name属性添加别名的方式一样。
<!--配置UserService-->
<bean id="userService" name="aaa,bbb" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!--指定别名-->
<alias name="userService" alias="xxx"/>
<alias name="userService" alias="yyy"/>
在beanFactory中维护着一个名为aliasMap的Map<String,String>集合,存储别名和beanName
之间的映射关系
自定义标签:
Spring的自定义标签需要引入外部的命名空间,并为外部的命名空间指定前缀,使用 <前缀:标签> 形式的标签,称之为自定义标签,自定义标签的解析流程也是 Spring xml扩展点方式之一。
//引入命名空间 context
xmlns:context="http://www.springframework.org/schema/context
//指定约束规范路径
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
//使用举例:
<context:component-scan base-package="com.hyl"/>
<context:property-placeholder location="db.properties"/>
8 Spring 的get方法
方法定义 | 返回值和参数 |
---|---|
Object getBean (String beanName) | 根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Object,需要强转 |
T getBean (Class type) | 根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例,无需强转 |
T getBean (String beanName,Class type) | 根据beanName从容器中获得Bean实例,返回值为Class类型实例,无需强转 |
//根据beanName获取容器中的Bean实例,需要手动强转
UserService userService = (UserService) applicationContext.getBean("userService");
//根据Bean类型去容器中匹配对应的Bean实例,如存在多个匹配Bean则报错
UserService userService2 = applicationContext.getBean(UserService.class);
//根据beanName获取容器中的Bean实例,指定Bean的Type类型
UserService userService3 = applicationContext.getBean("userService", UserService.class)
9 配置非自定义Bean
配置非自定义的Bean需要考虑如下两个问题:
被配置的Bean的实例化方式是什么?无参构造、有参构造、静态工厂方式还是实例工厂方式;
被配置的Bean是否需要注入必要属性。
举例:
配置 Druid 数据源交由Spring管理(无参构造,需要注入属性)
/*********************************************************************/
DruidDataSource source=new DruidDataSource();
source.setDriverClassName();
source.setUrl();
source.setUsername();
source.setPassword();
/*********************************************************************/
<!--配置 DruidDataSource数据源-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<!--配置必要属性-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
配置Connection交由Spring管理
Connection 的产生是通过DriverManager的静态方法getConnection获取的,所以我们要用静态工厂方式配置
/*********************************************************************/
Class.forName("com.mysql.jdbc.Driver");
DriverManager.getConnection("url","username","password");
/*********************************************************************/
<bean class="java.lang.Class" factory-method="forName">
<constructor-arg name="className" value="com.mysql.jdbc.Driver"/>
</bean>
<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection" scope="prototype">
<constructor-arg name="url" value="jdbc:mysql:///mybatis"/>
<constructor-arg name="user" value="root"/>
<constructor-arg name="password" value="root"/>
</bean>
产生一个指定日期格式的对象
可以看成是实例工厂方式,使用Spring配置方式产生Date实例
/*********************************************************************/
String currentTimeStr = "2023-08-27 07:20:00";
//有参构造
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(currentTimeStr);
/*********************************************************************/
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
<constructor-arg name="source" value="2023-08-27 07:20:00"/>
</bean>
配置MyBatis的SqlSessionFactory交由Spring管理
/*********************************************************************/
//加载mybatis核心配置文件,使用Spring静态工厂方式
InputStream in = Resources.getResourceAsStream(“mybatis-conifg.xml”);
//创建SqlSessionFactoryBuilder对象,使用Spring无参构造方式
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//调用SqlSessionFactoryBuilder的build方法,使用Spring实例工厂方式
SqlSessionFactory sqlSessionFactory = builder.build(in);
/*********************************************************************/
<!--静态工厂方式产生Bean实例-->
<bean id=“inputStream” class=“org.apache.ibatis.io.Resources” factorymethod=“getResourceAsStream”>
<constructor-arg name=“resource” value=“mybatis-config.xml/>
</bean>
<!--无参构造方式产生Bean实例-->
<bean id="sqlSessionFactoryBuilder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"/>
<!--实例工厂方式产生Bean实例-->
<bean id="sqlSessionFactory" factory-bean="sqlSessionFactoryBuilder" factory-method="build">
<constructor-arg name="inputStream" ref="inputStream"/>
</bean>
10 Bean 实例化的基本流程
- 加载xml配置文件,解析获取配置中的每个<bean>的信息,封装成一个个的BeanDefinition对象;
- 将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
- ApplicationContext底层遍历beanDefinitionMap,使用反射创建Bean实例对象;
- 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。
Bean信息定义对象-BeanDefinition
<bean id="" class="" name="" lazy-init="" scope="" init-method="" destroy-method="" factory-bean=""
factory-method="" abstract="" depends-on="" parent="">
<property name="" ref=""/>
<property name="" ref=""/>
<property name="" value=""/>
</bean>
DefaultListableBeanFactory对象内部维护着一个Map用于存储封装好的BeanDefinitionMap
Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作。
Bean实例及单例池singletonObjects
beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例singletonObjects中去,在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中,维护着singletonObjects。
Bean 实例化的基本流程图
11 Spring的后处理器
Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。
Spring主要有两种后处理器:
BeanFactoryPostProcessor
:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;
BeanPostProcessor
:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。
BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。(操作bean定义)
package org.springframework.beans.factory.config;
import org.springframework.beans.BeansException;
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,拿到BeanFactory的引用,自然就可以对beanDefinitionMap中的BeanDefinition进行操作了.
修改BeanDefinition
对UserDaoImpl的BeanDefinition进行修改操作
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
BeanDefinition userDaoBD = beanFactory.getBeanDefinition(“userDao”);//获得UserDao定义对象
userDaoBD.setBeanClassName("com.itheima.dao.impl.UserDaoImpl2"); //修改class
//userDaoBD.setInitMethodName(methodName); //修改初始化方法
//userDaoBD.setLazyInit(true); //修改是否懒加载
//... 省略其他的设置方式 ...
}
}
注册BeanDefinition
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
throws BeansException {
//强转成子类DefaultListableBeanFactory
if(configurableListableBeanFactory instanceof DefaultListableBeanFactory){
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
//进行注册操作
beanFactory.registerBeanDefinition("userDao2",beanDefinition);
}
}
}
BeanDefinitionRegistryPostProcessor
是BeanFactoryPostProcessor的子接口专门用于注册BeanDefinition操作
package org.springframework.beans.factory.support;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}
同上注册UserDaoImpl2
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
beanDefinitionRegistry.registerBeanDefinition("userDao2",beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
BeanFactoryPostProcessor 在SpringBean的实例化过程中的体现
BeanPostProcessor也是一个接口规范,Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。
package org.springframework.beans.factory.config;
import org.springframework.beans.BeansException;
public interface BeanPostProcessor {
/**
*
* @param var1 是当前被实例化的Bean
* @param var2 是当前Bean实例在容器中的名称
* @return 当前Bean实例对象
* @throws BeansException
*/
//在属性注入完毕,init初始化方法执行之前被回调
Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;
在初始化方法执行之后,被添加到单例池singletonObjects之前被回调
Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;
}
顺序:
对象创建了…
对象属性填充…
BeanPostProcessor的before方法…
对象初始化方法执行…
BeanPostProcessor的after方法…