基于xml的Spring应用
# 1、SpringBean 的配置详解
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 的创建 |
# 1)Bean的基础配置
例如:配置UserDaoImpl由Spring容器负责管理
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
此时存储到Spring容器(singleObjects单例池)中的Bean的beanName是userDao,值是UserDaoImpl对象,可以根据beanName获取Bean实例
applicationContext.getBean("userDao");
如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
applicationContext.getBean("com.itheima.dao.impl.UserDaoImpl");
# 2)Bean的别名配置
可以为当前Bean指定多个别名,根据别名也可以获得Bean对象
<bean id="userDao" name="aaa,bbb" class="com.itheima.dao.impl.UserDaoImpl"/>
此时多个名称都可以获得UserDaoImpl实例对象
applicationContext.getBean("userDao");
applicationContext.getBean("aaa");
applicationContext.getBean("bbb");
# 3)Bean的范围配置
默认情况下,单纯的Spring环境Bean的作用范围有两个:Singleton和Prototype
singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
当scope设置为singleton时,获得两次对象打印结果是一样的
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton"/>
Object userDao = applicationContext.getBean("userDao");
Object userDao2 = applicationContext.getBean("userDao");
System.out.println(userDao); //com.itheima.dao.impl.UserDaoImpl@631330c
System.out.println(userDao2); //com.itheima.dao.impl.UserDaoImpl@631330c
通过断点调试,观察可以发现单例池中存在 userDao 实例

当scope设置为prototype时,获得两次对象打印结果是不一样的
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype"/>
Object userDao = applicationContext.getBean("userDao");
Object userDao2 = applicationContext.getBean("userDao");
System.out.println(userDao); //com.itheima.dao.impl.UserDaoImpl@4d50efb8
System.out.println(userDao2); //com.itheima.dao.impl.UserDaoImpl@7e2d773b
通过断点调试,观察可以发现单例池中不存在 userDao 实例,但是 userDao的信息已经被存储到beanDefinitionMap中了

# 4)Bean的延迟加载
当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" lazy-init="true"/>
# 5)Bean的初始化和销毁方法配置
Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
public class UserDaoImpl implements UserDao {
public UserDaoImpl() { System.out.println("UserDaoImpl创建了..."); }
public void init(){ System.out.println("初始化方法..."); }
public void destroy(){ System.out.println("销毁方法..."); }
}
扩展:除此之外,我们还可以通过实现 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...");
}
}
# 6)Bean的实例化配置
Spring的实例化方式主要如下两种:
构造方式实例化:底层通过构造方法对Bean进行实例化
工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化
构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的<bean>几乎都是无参构造该方式,此处不在赘述。下面讲解有参构造方法实例化Bean
//有参构造方法
public UserDaoImpl(String name){
}
有参构造在实例化Bean时,需要参数的注入,通过<constructor-arg>标签,嵌入在<bean>标签内部提供构造参数,如下:
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<constructor-arg name="name" value="haohao"/>
</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" factorymethod="getUserDao">
<constructor-arg name="name" value="haohao"/>
</bean>
PS:<constructor-arg>标签不仅仅是为构造方法传递参数,只要是为了实例化对象而传递的参数都可以通过<constructor-arg>标签完成,例如上面通过静态工厂方法实例化Bean所传递的参数也是要通过<constructor-arg>进行传递的
测试代码,直接通过ApplicationContext获得userDao即可
ApplicationContext applicationContext = new ClassPathxmlApplicationContext("applicationContext.xml");
Object userDao = applicationContext.getBean("userDao");
System.out.println(userDao);
断点调试,UserDaoImpl实例对象会存在于单例池中

实例工厂方法,也就是非静态工厂方法产生Bean实例,与静态工厂方式比较,该方式需要先有工厂对象,在用工厂对象去调用非静态方法,所以在进行配置时,要先配置工厂Bean,在配置目标Bean
<!-- 配置实例工厂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>
测试代码同上,直接通过ApplicationContext获得userDao即可,不在赘述
通过断点观察单例池singletonObjects,发现单例池中既有工厂Bean实例,也有目标Bean实例,且都是在Spring容器创建时,就完成了Bean的实例化

上面不管是静态工厂方式还是非静态工厂方式,都是自定义的工厂方法,Spring提供了FactoryBean的接口规范,FactoryBean接口定义如下:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = “factoryBeanObjectType”;
T getObject() throws Exception; //获得实例对象方法
Class<?> getObjectType(); //获得实例对象类型方法
default boolean isSingleton() {
return true;
}
}
定义工厂实现FactoryBean
public class UserDaoFactoryBean3 implements FactoryBean<UserDao> {
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
}
配置FactoryBean交由Spring管理即可
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean3"/>
通过Spring容器根据beanName可以正常获得UserDaoImpl
ApplicationContext applicationContext = new ClassPathxmlApplicationContext("applicationContext.xml");
Object userDao = applicationContext.getBean("userDao");
System.out.println(userDao);
通过断点观察发现Spring容器创建时,FactoryBean被实例化了,并存储到了单例池singletonObjects中,但是getObject() 方法尚未被执行,UserDaoImpl也没被实例化,当首次用到UserDaoImpl时,才调用getObject() ,此工厂方式产生的Bean实例不会存储到单例池singletonObjects中,会存储到 factoryBeanObjectCache 缓存池中,并且后期每次使用到userDao都从该缓存池中返回的是同一个userDao实例。

# 7)Bean的依赖注入配置
Bean的依赖注入有两种方式:
| 注入方式 | 配置方式 |
|---|---|
| 通过 Bean 的 set 方法注入 | <property name="userDao" ref="userDao"/> <property name="name" value="haohao"/> |
| 通过构造方法注入 | <constructor-arg name="name" ref="userDao"/> <constructor-arg name="name" value="haohao"/> |
其中,ref 是 reference 的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。value 用于注入普通属性值。
依赖注入的数据类型有如下三种:
普通数据类型,例如:String、int、boolean等,通过value属性指定。
引用数据类型,例如:UserDaoImpl、DataSource等,通过ref属性指定。
集合数据类型,例如:List、Map、Properties等。
注入 List<T> 集合 – 普通数据
void setStrList(List<String> strList){
strList.forEach(str->{
System.out.println(str);
});
}
<property name="strList">
<list>
<value>haohao</value>
<value>miaomiao</value>
</list>
</property>
注入 List<T> 集合 – 引用数据
public void setObjList(List<UserDao> objList){
objList.forEach(obj->{
System.out.println(obj);
});
}
<property name="objList">
<list>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
</list>
</property>
注入 List<T> 集合 – 引用数据
也可以直接引用容器中存在的Bean
<!--配置UserDao-->
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userDao2" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userDao3" class="com.itheima.dao.impl.UserDaoImpl"/>
<!--配置UserService-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="objList">
<list>
<ref bean="userDao"></ref>
<ref bean="userDao2"></ref>
<ref bean="userDao3"></ref>
</list>
</property>
</bean>
注入 Set<T> 集合
//注入泛型为字符串的Set集合
public void setValueSet(Set<String> valueSet){
valueSet.forEach(str->{
System.out.println(str);
});
}
//注入泛型为对象的Set集合
public void setObjSet(Set<UserDao> objSet){
objSet.forEach(obj->{
System.out.println(obj);
});
}
<!-- 注入泛型为字符串的Set集合 -->
<property name="valueSet">
<set>
<value>muzi</value>
<value>muran</value>
</set>
</property>
<!-- 注入泛型为对象的Set集合 -->
<property name="objSet">
<set>
<ref bean="userDao"></ref>
<ref bean="userDao2"></ref>
<ref bean="userDao3"></ref>
</set>
</property>
注入 Map<K,V> 集合
//注入值为字符串的Map集合
public void setValueMap(Map<String,String> valueMap){
valueMap.forEach((k,v)->{
System.out.println(k+"=="+v);
});
}
//注入值为对象的Map集合
public void setObjMap(Map<String,UserDao> objMap){
objMap.forEach((k,v)->{
System.out.println(k+"=="+v);
});
}
<!--注入值为字符串的Map集合-->
<property name="valueMap">
<map>
<entry key="aaa" value="AAA" />
<entry key="bbb" value="BBB" />
<entry key="ccc" value="CCC" />
</map>
</property>
<!--注入值为对象的Map集合-->
<property name="objMap">
<map>
<entry key="ud" value-ref="userDao"/>
<entry key="ud2" value-ref="userDao2"/>
<entry key="ud3" value-ref="userDao3"/>
</map>
</property>
注入 Properties 键值对
//注入Properties
public void setProperties(Properties properties){
properties.forEach((k,v)->{
System.out.println(k+"=="+v);
});
}
<property name="properties">
<props>
<prop key="xxx">XXX</prop>
<prop key="yyy">YYY</prop>
</props>
</property>
扩展:自动装配方式
如果被注入的属性类型是Bean引用的话,那么可以在<bean> 标签中使用 autowire 属性去配置自动注入方式,属性值有两个:
byName:通过属性名自动装配,即去匹配 setXxx 与 id="xxx"(name="xxx")是否一致;
byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" autowire="byType">
# 8)Spring的其他配置标签
Spring 的 xml 标签大体上分为两类,一种是默认标签,一种是自定义标签
默认标签:就是不用额外导入其他命名空间约束的标签,例如 <bean> 标签
自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如 <context:propertyplaceholder/> 标签
Spring的默认标签用到的是Spring的默认命名空间
<?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">
</beans>
该命名空间约束下的默认标签如下:
| 标签 | 作用 |
|---|---|
<beans> | 一般作为 xml 配置根标签,其他标签都是该标签的子标签 |
<bean> | Bean 的配置标签,上面已经详解了,此处不再阐述 |
<import> | 外部资源导入标签 |
<alias> | 指定 Bean 的别名标签,使用较少 |
<beans>标签,除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境
<!-- 配置测试环境下,需要加载的Bean实例 -->
<beans profile="test">
</beans>
<!-- 配置开发环境下,需要加载的Bean实例 -->
<beans profile="dev">
</beans>
可以使用以下两种方式指定被激活的环境:
- 使用命令行动态参数,虚拟机参数位置加载 -Dspring.profiles.active=test
- 使用代码的方式设置环境变量 System.setProperty("spring.profiles.active","test")
<import>标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过<import>标签导入到一个主配置文件中,项目加载主配置文件就连同<import> 导入的文件一并加载了
<!--导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"/>
<!--导入商品模块配置文件-->
<import resource="classpath:ProductModuleApplicationContext.xml"/>
<alias> 标签是为某个Bean添加别名,与在<bean> 标签上使用name属性添加别名的方式一样,我们为UserServiceImpl指定四个别名:aaa、bbb、xxx、yyy
<!--配置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扩展点方式之一,在《Spring整合其他框架》章节进行详细介绍
<!--默认标签-->
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<!--自定义标签-->
<context:property-placeholder/>
<mvc:annotation-driven/>
<dubbo:application name="application"/>
# 2、Spring 的get方法
| 方法定义 | 返回值和参数说明 |
|---|---|
Object getBean(String beanName) | 根据 beanName 从容器中获取 Bean 实例,要求容器中 Bean 唯一,返回值为 Object,需要强转 |
<T> T getBean(Class<T> type) | 根据 Class 类型从容器中获取 Bean 实例,要求容器中该类型唯一,返回值为类型实例,无需强转 |
<T> T getBean(String beanName, Class<T> type) | 根据 beanName 从容器中获取 Bean 实例,返回值为类型实例,无需强转 |
//根据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);
# 3、Spring 配置非自定义Bean
以上在 xml 中配置的Bean都是自己定义的,例如:UserDaoImpl,UserServiceImpl。但是,在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置
配置非自定义的Bean需要考虑如下两个问题:
被配置的Bean的实例化方式是什么?无参构造、有参构造、静态工厂方式还是实例工厂方式;
被配置的Bean是否需要注入必要属性。
# 1)配置 Druid 数据源交由Spring管理
导入Druid坐标
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
配置 DruidDataSource
<!--配置 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>
# 2)配置Connection交由Spring管理
Connection 的产生是通过DriverManager的静态方法getConnection获取的,所以我们要用静态工厂方式配置
<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>
# 3)配置日期对象交由Spring管理
产生一个指定日期格式的对象,原始代码按如下:
String currentTimeStr = "2023-08-27 07:20:00";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(currentTimeStr);
可以看成是实例工厂方式,使用Spring配置方式产生Date实例
<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>
# 4)配置MyBatis的SqlSessionFactory交由Spring管理
导入MyBatis的相关坐标:
<!--mybatis框架-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
MyBatis原始获得SqlSessionFactory的方式:
//加载mybatis核心配置文件,使用Spring静态工厂方式
InputStream in = Resources.getResourceAsStream(“mybatis-conifg.xml”);
//创建SqlSessionFactoryBuilder对象,使用Spring无参构造方式
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//调用SqlSessionFactoryBuilder的build方法,使用Spring实例工厂方式
SqlSessionFactory sqlSessionFactory = builder.build(in);
SqlSessionFactory交由Spring管理配置如下:
<!--静态工厂方式产生Bean实例-->
<bean id="inputStream" class="org.apache.ibatis.io.Resources" factory-method="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>
# 4、Bean 实例化的基本流程
Spring容器在进行初始化时,会将xml配置的<bean>的信息封装成一个BeanDefinition对象,所有的BeanDefinition存储到一个名为beanDefinitionMap的Map集合中去,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。
Bean信息定义对象-BeanDefinition
<bean id="" class="" name="" lazyinit="" scope="" init-method="" destroy-method="" factory-bean="" factory-method="" abstract=""
depends-on="" parent="">
<property name="" ref=""/>
<property name="" ref=""/>
<property name="" value=""/>
</bean>
⬇️信息封装
public interface BeanDefinition {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
/**
* 设置 Bean 的类名
*/
void setBeanClassName(@Nullable String var1);
/**
* 获取 Bean 的类名
*/
String getBeanClassName();
/**
* 设置作用域
*/
void setScope(@Nullable String var1);
/**
* 获取作用域
*/
String getScope();
/**
* 设置是否懒加载
*/
void setLazyInit(boolean var1);
/**
* 是否懒加载
*/
boolean isLazyInit();
/**
* 设置工厂 Bean 名称
*/
void setFactoryBeanName(@Nullable String var1);
/**
* 获取工厂 Bean 名称
*/
String getFactoryBeanName();
/**
* 设置工厂方法名称
*/
void setFactoryMethodName(@Nullable String var1);
/**
* 获取工厂方法名称
*/
String getFactoryMethodName();
/**
* 设置初始化方法名称
*/
void setInitMethodName(@Nullable String var1);
/**
* 获取初始化方法名称
*/
String getInitMethodName();
// ..... 省略部分属性和方法
}
DefaultListableBeanFactory对象内部维护着一个Map用于存储封装好的BeanDefinitionMap
public class DefaultListableBeanFactory extends ... implements ... {
//存储<bean>标签对应的BeanDefinition对象
//key:是Bean的beanName,value:是Bean定义对象BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap;
}
Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以**只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作**
Bean实例及单例池singletonObjects, beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中,维护着singletonObjects,源码如下:
public class DefaultSingletonBeanRegistry extends ... implements ... {
//存储Bean实例的单例池
////key:是Bean的beanName,value:是Bean的实例对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
}
- 加载xml配置文件,解析获取配置中的每个<bean>的信息,封装成一个个的BeanDefinition对象;
- 将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
- ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
- 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;
- 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

# 5、Spring的后处理器
Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态册
BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:
BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;
BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。
# 5.1、Bean工厂后处理器 – BeanFactoryPostProcessor
BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。
BeanFactoryPostProcessor 定义如下:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}
编写BeanFactoryPostProcessor
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
System.out.println("MyBeanFactoryPostProcessor执行了...");
}
}
配置BeanFactoryPostProcessor
<bean class="com.itheima.processor.MyBeanFactoryPostProcessor"/>
postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,拿到BeanFactory的引用,自然就可以对beanDefinitionMap中的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进行了修改操作,下面对BeanDefiition进行注册操作
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);
}
}
}
Spring 提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作
public class MyBeanFactoryPostProcessor2 implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
throws BeansException {
// 此方法可以留空,或者添加自定义逻辑
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry)
throws BeansException {
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
beanDefinitionRegistry.registerBeanDefinition("userDao2", beanDefinition);
}
}
# 5.11、使用Spring的BeanFactoryPostProcessor扩展点完成自定义注解扫描
- 自定义@MyComponent注解,使用在类上;
- 使用资料中提供好的包扫描器工具BaseClassScanUtils 完成指定包的类扫描;
- 自定义BeanFactoryPostProcessor完成注解@MyComponent的解析,解析后最终被Spring管理。
自定义@MyComponent注解,使用在类上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
//显示的指定Bean的beanName
String value() default "";
}
在类上使用@MyComponent
@MyComponent("otherBean")
public class OtherBean {
}
自定义BeanFactoryPostProcessor完成注解解析
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 指定要扫描的包
String basePackage = "com.itheima";
// 调用扫描工具扫描指定包及其子包下的 @MyComponent
Map<String, Class> myComponentClassMap = BaseClassScanUtils.scanMyComponentAnnotation(basePackage);
// 遍历 Map 集合,创建 BeanDefinition 对象进行注册
myComponentClassMap.forEach((beanName, clazz) -> {
try {
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName(clazz.getName());
registry.registerBeanDefinition(beanName, beanDefinition);
} catch (Exception e) {
e.printStackTrace();
}
});
}

# 5.2、Bean后处理器 – BeanPostProcessor
Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。
BeanPostProcessor的接口定义如下:
public interface BeanPostProcessor {
@Nullable
//在属性注入完毕,init初始化方法执行之前被回调
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
//在初始化方法执行之后,被添加到单例池singletonObjects之前被回调
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
自定义MyBeanPostProcessor,完成快速入门测试
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 初始化之前调用
*
* @param bean 当前被实例化的 Bean
* @param beanName 当前 Bean 实例在容器中的名称
* @return 当前 Bean 实例对象
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor 的 before 方法...");
return bean;
}
/**
* 初始化之后调用
*
* @param bean 当前被实例化的 Bean
* @param beanName 当前 Bean 实例在容器中的名称
* @return 当前 Bean 实例对象
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor 的 after 方法...");
return bean;
}
}
配置MyBeanPostProcessor
<bean class="com.itheima.processors.MyBeanPostProcessor"></bean>
测试控制台打印结果如下:
UserDaoImpl创建了...
UserDaoImpl属性填充...
BeanPostProcessor的before方法...
UserDaoImpl初始化方法执行...
BeanPostProcessor的after方法...
# 5.2.1、对Bean方法进行执行时间日志增强
要求如下:
Bean的方法执行之前控制台打印当前时间;
Bean的方法执行之后控制台打印当前时间。
分析:
对方法进行增强主要就是代理设计模式和包装设计模式;
由于Bean方法不确定,所以使用动态代理在运行期间执行增强操作;
在Bean实例创建完毕后,进入到单例池之前,使用Proxy代替真是的目标Bean
编写BeanPostProcessor,增强逻辑编写在 after方法中
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 对Bean进行动态代理,返回的是Proxy代理对象
Object proxyBean = Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(Object proxy, Method method, Object[] args) -> {
long start = System.currentTimeMillis();
System.out.println("开始时间:" + new Date(start));
// 执行目标方法
Object result = method.invoke(bean, args);
long end = System.currentTimeMillis();
System.out.println("结束时间:" + new Date(end));
return result;
}
);
// 返回代理对象
return proxyBean;
}

# 6、Spring Bean的生命周期
Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的;
Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
由于Bean的初始化阶段的步骤比较复杂,所以着重研究Bean的初始化阶段
Spring Bean的初始化过程涉及如下几个过程:
Bean实例的属性填充
Aware接口属性注入
BeanPostProcessor的before()方法回调
InitializingBean接口的初始化方法回调
自定义初始化方法init回调
BeanPostProcessor的after()方法回调
PS:通过代码验证上述初始化顺序… …
# 6.1、Bean实例属性填充
BeanDefinition 中有对当前Bean实体的注入信息通过属性propertyValues进行了存储,例如UserService的属性信息如下:


Spring在进行属性注入时,会分为如下几种情况:
注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;
注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题,下面会详细阐述解决方案。
PS:通过代码验证上述第二第三种属性填充… …
多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环引用"

public class UserServiceImpl implements UserService{
public void setUserDao(UserDao userDao) {}
}
public class UserDaoImpl implements UserDao{
public void setUserService(UserService userService){}
}
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
<property name="userService" ref="userService"/>
</bean>
代码验证后,分析出UserService与UserDao实例化与初始化的顺序如下:

Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:
public class DefaultSingletonBeanRegistry ... {
//1、最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//2、早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
//3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下
UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
UserService 注入UserDao;
UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
# 6.2、Aware接口属性注入
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。
| 接口名 | 回调方法 | 注入对象 | 使用场景 | 生效条件 |
|---|---|---|---|---|
| ServletContextAware | setServletContext(ServletContext context) | 当前 Web 容器的 ServletContext | 需要获取 Web 环境中的全局上下文对象,如访问服务器路径、配置等 | 仅在 Web 环境中(如 Spring MVC)才生效 |
| BeanFactoryAware | setBeanFactory(BeanFactory beanFactory) | 当前 Bean 所在的 BeanFactory 容器 | 获取当前容器,可以做一些编程式的 Bean 操作,如判断某 Bean 是否存在等 | 通用,任何 Spring 应用都适用 |
| BeanNameAware | setBeanName(String beanName) | 当前 Bean 的名称 | 想要知道当前这个 Bean 在容器中的名字 | 通用 |
| ApplicationContextAware | setApplicationContext(ApplicationContext context) | Spring 应用上下文对象(比 BeanFactory 更强大) | 想要获取其他 Bean、资源、事件发布等高级功能 | 通用 |
# 7、Spring IoC 整体流程总结

# 8、Spring xml方式整合第三方框架
xml整合第三方框架有两种整合方案:
不需要自定义名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如:MyBatis;
需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容,例如:Dubbo。
# 8.1、Spring整合MyBatis
之前已经在Spring中简单的配置了SqlSessionFactory,但是这不是正规的整合方式,MyBatis提供了mybatis-spring.jar专门用于两大框架的整合。
Spring整合MyBatis的步骤如下:
导入MyBatis整合Spring的相关坐标;(请见资料中的pom.xml)
编写Mapper和Mapper.xml;
配置SqlSessionFactoryBean和MapperScannerConfigurer;
编写测试代码
配置SqlSessionFactoryBean和MapperScannerConfigurer:
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置SqlSessionFactoryBean-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Mapper包扫描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.dao"></property>
</bean>
编写Mapper和Mapper.xml
public interface UserMapper {
List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.UserMapper">
<select id="findAll" resultType="com.itheima.pojo.User">
select * from tb_user
</select>
</mapper>
编写测试代码
ClassPathxmlApplicationContext applicationContext = new ClassPathxmlApplicationContext("applicationContext.xml");
UserMapper userMapper = applicationContext.getBean(UserMapper.class);
List<User> all = userMapper.findAll();
System.out.println(all);
# 8.2、原理剖析
整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象,SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
SqlSessionFactoryBean:需要进行配置,用于提供SqlSessionFactory;
MapperScannerConfigurer:需要进行配置,用于扫描指定mapper注册BeanDefinition;
MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法;
ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。
配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean实现了FactoryBean和InitializingBean两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法
SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean{
public void afterPropertiesSet() throws Exception {
//创建SqlSessionFactory对象
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
public SqlSessionFactory getObject() throws Exception {
return this.sqlSessionFactory;
}
}
配置MapperScannerConfigurer作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor和InitializingBean两个接口,会在postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean
class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean{
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
}
class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
} else {
this.processBeanDefinitions(beanDefinitions);
}
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
//设置Mapper的beanClass是org.mybatis.spring.mapper.MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.setAutowireMode(2); //设置MapperBeanFactory 进行自动注入
}
}
PS:autowireMode取值:1是根据名称自动装配,2是根据类型自动装配
class ClassPathBeanDefinitionScanner{
public int scan(String... basePackages) {
this.doScan(basePackages);
}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//将扫描到的类注册到beanDefinitionMap中,此时beanClass是当前类全限定名
this.registerBeanDefinition(definitionHolder, this.registry);
return beanDefinitions;
}
}
UserMapper userMapper = applicationContext.getBean(UserMapper.class);
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
}
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
}
Spring 整合其他组件时就不像MyBatis这么简单了,例如Dubbo框架在于Spring进行整合时,要使用Dubbo提供的命名空间的扩展方式,自定义了一些Dubbo的标签
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 配置应用名称 -->
<dubbo:application name="dubbo1-consumer"/>
<!-- 配置注册中心地址 -->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!-- 扫描 dubbo 的注解 -->
<dubbo:annotation package="com.itheima.controller"/>
<!-- 消费者配置 -->
<dubbo:consumer check="false" timeout="1000" retries="0"/>
</beans>
为了降低我们此处的学习成本,不在引入Dubbo第三方框架了,以Spring的 context 命名空间去进行讲解,该方式也是命名空间扩展方式。
需求:加载外部properties文件,将键值对存储在Spring容器中
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
引入context命名空间,在使用context命名空间的标签,使用SpEL表达式在xml或注解中根据key获得value
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加载外部配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
</beans>
其实,加载的properties文件中的属性最终通过Spring解析后会被存储到了Spring容器的environment中去,不仅自己定义的属性会进行存储,Spring也会把环境相关的一些属性进行存储


原理剖析解析过程,只能从源头ClassPathXmlApplicationContext入手,经历复杂的源码追踪,找到如下两个点:
1)在创建DefaultNamespaceHandlerResolver时,为处理器映射地址handlerMappingsLocation属性赋值,并加载命名空间处理器到Map<String, Object> handlerMappings 中去
this.handlerMappingsLocation = "META-INF/spring.handlers";



第一点完成后,Map集合handlerMappings就被填充了很多XxxNamespaceHandler,继续往下追代码
2)在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中,发现如下逻辑:
//如果是默认命名空间
if (delegate.isDefaultNamespace(ele)) {
this.parseDefaultElement(ele, delegate);
//否则是自定义命名空间
} else {
delegate.parseCustomElement(ele);
}
如果是默认命名空间,则执行parseDefaultElement方法
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
this.doRegisterBeanDefinitions(ele);
}
}
如果是自定义命名空间,则执行parseCustomElement方法
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
//解析命名空间
String namespaceUri = this.getNamespaceURI(ele);
//获得命名空间解析器
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
//解析执行的标签
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
在执行resovle方法时,就是从Map<String, Object> handlerMappings中根据命名空间名称获得对应的处理器对象,此处是ContextNamespaceHandler,最终执行NamespaceHandler的parse方法
ContextNamespaceHandler源码如下,间接实现了NamespaceHandler接口,初始化方法init会被自动调用。由于context命名空间下有多个标签,所以每个标签又单独注册了对应的解析器,注册到了其父类NamespaceHandlerSupport的Map<String, BeanDefinitionParser> parsers中去了
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
public ContextNamespaceHandler() {
}
@Override
public void init() {
this.registerBeanDefinitionParser("property-placeholder",
new PropertyPlaceholderBeanDefinitionParser());
this.registerBeanDefinitionParser("property-override",
new PropertyOverrideBeanDefinitionParser());
this.registerBeanDefinitionParser("annotation-config",
new AnnotationConfigBeanDefinitionParser());
this.registerBeanDefinitionParser("component-scan",
new ComponentScanBeanDefinitionParser());
this.registerBeanDefinitionParser("load-time-weaver",
new LoadTimeWeaverBeanDefinitionParser());
this.registerBeanDefinitionParser("spring-configured",
new SpringConfiguredBeanDefinitionParser());
this.registerBeanDefinitionParser("mbean-export",
new MBeanExportBeanDefinitionParser());
this.registerBeanDefinitionParser("mbean-server",
new MBeanServerBeanDefinitionParser());
}
}
通过上述分析,我们清楚的了解了外部命名空间标签的执行流程,如下:
将自定义标签的约束 与 物理约束文件与网络约束名称的约束 以键值对形式存储到一个spring.schemas文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;
将自定义命名空间的名称 与 自定义命名空间的处理器映射关系 以键值对形式存在到一个叫spring.handlers文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;
准备好NamespaceHandler,如果命名空间只有一个标签,那么直接在parse方法中进行解析即可,一般解析结果就是注册该标签对应的BeanDefinition。如果命名空间里有多个标签,那么可以在init方法中为每个标签都注册一个BeanDefinitionParser,在执行NamespaceHandler的parse方法时在分流给不同的BeanDefinitionParser进行解析(重写doParse方法即可)。
# 8.3、案例
设想自己是一名架构师,进行某一个框架与Spring的集成开发,效果是通过一个指示标签,向Spring容器中自动注入一个BeanPostProcessor
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/xmlSchema-instance"
xmlns:haohao="http://www.itheima.com/haohao"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.itheima.com/haohao
http://www.itheima.com/haohao/haohao-annotation.xsd">
<haohao:annotation-driven/>
</beans>
步骤分析:
确定命名空间名称、schema虚拟路径、标签名称;
编写schema约束文件haohao-annotation.xsd
在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser
编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注册HaohaoBeanPostProcessor
编写HaohaoBeanPostProcessor
=====================以上五步是框架开发者写的,以下是框架使用者写的==========================
在applicationContext.xml配置文件中引入命名空间
在applicationContext.xml配置文件中使用自定义的标签
编写schema约束文件haohao-annotation.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
xmlns="http://www.itheima.com/haohao"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.itheima.com/haohao">
<xsd:element name="annotation-driven" />
</xsd:schema>
在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers

spring.schemas
http\://www.itheima.com/haohao/haohao-annotation.xsd =
com/itheima/haohao/config/haohao-annotation.xsd
spring.handlers
http\://www.itheima.com/haohao =
com.itheima.handler.HaohaoNamespaceHandler
编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser
public class HaohaoNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
this.registerBeanDefinitionParser("annotation-driven",new HaohaoBeanDefinitionParser());
}
}
编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注册HaohaoBeanPostProcessor
public class HaohaoBeanDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 创建 HaohaoBeanPostProcessor 的 BeanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(HaohaoBeanPostProcessor.class);
// 注册 HaohaoBeanPostProcessor
parserContext.getRegistry().registerBeanDefinition("haohaoBeanPostProcessor", beanDefinition);
return beanDefinition;
}
}
编写HaohaoBeanPostProcessor
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class HaohaoBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("自动注入 HaohaoBeanPostProcessor 成功");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 可选实现
return bean;
}
}