Salmon的全栈知识 Salmon的全栈知识
首页
  • JavaSE
  • JavaWeb
  • Spring生态
  • JUC
  • JVM
  • Netty
  • Java各版本特性
  • 23种设计模式
  • Maven
  • Java常用框架
  • Dubbo
  • OpenFeign
  • Nacos
  • Zookeeper
  • Sentinel
  • Seata
  • SpringCloud Gateway
  • Apollo
  • Eureka
  • Go基础
  • Gin
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
    • ElasticSearch
  • 消息中间件

    • RabbitMQ
    • RocketMQ
    • Kafka
    • ActiveMQ
    • MQTT
    • NATS
  • 网关中间件

    • Nginx
  • Linux
  • Docker
  • Git
  • K8s
  • Solidity
  • Java
  • 计算机网络
  • 操作系统
GitHub (opens new window)
首页
  • JavaSE
  • JavaWeb
  • Spring生态
  • JUC
  • JVM
  • Netty
  • Java各版本特性
  • 23种设计模式
  • Maven
  • Java常用框架
  • Dubbo
  • OpenFeign
  • Nacos
  • Zookeeper
  • Sentinel
  • Seata
  • SpringCloud Gateway
  • Apollo
  • Eureka
  • Go基础
  • Gin
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
    • ElasticSearch
  • 消息中间件

    • RabbitMQ
    • RocketMQ
    • Kafka
    • ActiveMQ
    • MQTT
    • NATS
  • 网关中间件

    • Nginx
  • Linux
  • Docker
  • Git
  • K8s
  • Solidity
  • Java
  • 计算机网络
  • 操作系统
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Spring框架

    • 传统Javaweb开发的困惑
    • IoC、DI和AOP思想提出
    • Spring框架的诞生
    • 基于xml的Spring应用
      • 1、SpringBean 的配置详解
        • 1)Bean的基础配置
        • 2)Bean的别名配置
        • 3)Bean的范围配置
        • 4)Bean的延迟加载
        • 5)Bean的初始化和销毁方法配置
        • 6)Bean的实例化配置
        • 7)Bean的依赖注入配置
        • 8)Spring的其他配置标签
      • 2、Spring 的get方法
      • 3、Spring 配置非自定义Bean
        • 1)配置 Druid 数据源交由Spring管理
        • 2)配置Connection交由Spring管理
        • 3)配置日期对象交由Spring管理
        • 4)配置MyBatis的SqlSessionFactory交由Spring管理
      • 4、Bean 实例化的基本流程
      • 5、Spring的后处理器
        • 5.1、Bean工厂后处理器 – BeanFactoryPostProcessor
        • 5.11、使用Spring的BeanFactoryPostProcessor扩展点完成自定义注解扫描
        • 5.2、Bean后处理器 – BeanPostProcessor
        • 5.2.1、对Bean方法进行执行时间日志增强
      • 6、Spring Bean的生命周期
        • 6.1、Bean实例属性填充
        • 6.2、Aware接口属性注入
      • 7、Spring IoC 整体流程总结
      • 8、Spring xml方式整合第三方框架
        • 8.1、Spring整合MyBatis
        • 8.2、原理剖析
        • 8.3、案例
    • 基于注解的Spring应用
    • AOP 简介
    • 基于xml配置的AOP
    • 基于注解配置的AOP
    • 基于AOP的声明式事务控制
  • SpringMVC框架

    • Spring整合web环境
    • web层MVC框架思想与设计思路
    • SpringMVC简介
    • SpringMVC的请求处理
    • SpringMVC的响应处理
    • SpringMVC的拦截器
    • SpringMVC的全注解开发
    • SpringMVC的组件原理剖析
    • SpringMVC的异常处理机制
  • 《Spring生态》笔记
  • Spring框架
Salmon
2025-07-23
目录

基于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 实例

image-20250724004622491

当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中了

image-20250724004728262

# 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实例对象会存在于单例池中

image-20250724005322394

实例工厂方法,也就是非静态工厂方法产生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的实例化

image-20250724005454100

上面不管是静态工厂方式还是非静态工厂方式,都是自定义的工厂方法,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实例。

image-20250724005756544

# 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之间的映射关系

image-20250724011017976

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实例返回。

image-20250724100223807

# 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();
        }
    });
}

image-20250724101600129

# 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;
}

image-20250724102329731

# 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的属性信息如下:

image-20250724143152351

image-20250724143157238

Spring在进行属性注入时,会分为如下几种情况:

  • 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;

  • 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;

  • 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题,下面会详细阐述解决方案。

PS:通过代码验证上述第二第三种属性填充… …

多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环引用"

image-20250724143337560

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实例化与初始化的顺序如下:

image-20250724143420547

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 整体流程总结

image-20250724144558100

# 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也会把环境相关的一些属性进行存储

image-20250724150337906

image-20250724150343047

原理剖析解析过程,只能从源头ClassPathXmlApplicationContext入手,经历复杂的源码追踪,找到如下两个点:

1)在创建DefaultNamespaceHandlerResolver时,为处理器映射地址handlerMappingsLocation属性赋值,并加载命名空间处理器到Map<String, Object> handlerMappings 中去

this.handlerMappingsLocation = "META-INF/spring.handlers";

image-20250724150425137

image-20250724150430408

image-20250724150435064

第一点完成后,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>

步骤分析:

  1. 确定命名空间名称、schema虚拟路径、标签名称;

  2. 编写schema约束文件haohao-annotation.xsd

  3. 在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers

  4. 编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser

  5. 编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注册HaohaoBeanPostProcessor

  6. 编写HaohaoBeanPostProcessor

=====================以上五步是框架开发者写的,以下是框架使用者写的==========================

  1. 在applicationContext.xml配置文件中引入命名空间

  2. 在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

image-20250724152201210

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;
    }
}
上次更新: 2025/07/25, 07:39:11
Spring框架的诞生
基于注解的Spring应用

← Spring框架的诞生 基于注解的Spring应用→

Theme by Vdoing | Copyright © 2022-2025 Salmon's Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式