# Spring 的 get 方法

方法定义返回值参数
Object getBean(String beanName)ObjectbeanName
T getBean(Class type)Class typetype
T getBean(String beanName, Class type)Class typebeanName , type
  • 第一行的方法定义是 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 类型实例,无需强转。

1
2
3
4
5
6
7
//根据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);

# Spring 配置非自定义 Bean

以上在 xml 中配置的 Bean 都是自己定义的,例如:UserDaoImpl,UserServiceImpl。但是,在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方 jar 包中的,那么,这些 Bean 要想让 Spring 进行管理,也需要对其进行配置。

配置非自定义的 Bean 需要考虑如下两个问题:

  • 被配置的 Bean 的实例化方式是什么?无参构造、有参构造、静态工厂方式还是实例工厂方式;
  • 被配置的 Bean 是否需要注入必要属性。

1)配置 Druid 数据源交由 Spring 管理

配置 DruidDataSource

1
2
3
4
5
6
7
8
<!--配置 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 获取的,所以我们要用静态工厂方式配置

1
2
3
4
5
6
7
8
9
10
<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 管理

产生一个指定日期格式的对象,原始代码按如下:

1
2
3
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 实例

1
2
3
4
5
6
7
<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 的相关坐标:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--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 的方式:

1
2
3
4
5
6
//加载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 管理配置如下:

1
2
3
4
5
6
7
8
9
10
11
<!--静态工厂方式产生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>

# Bean 实例化的基本流程

Spring 容器在进行初始化时,会将 xml 配置的 <bean> 的信息封装成一个 BeanDefinition 对象,所有的 BeanDefinition 存储到一个名为 beanDefinitionMap 的 Map 集合中去,Spring 框架在对该 Map 进行遍历,使用反射创建 Bean 实例对象,创建好的 Bean 对象存储在一个名为 singletonObjects 的 Map 集合中,当调用 getBean 方法时则最终从该 Map 集合中取出 Bean 实例对象返回。

DefaultListableBeanFactory 对象内部维护着一个 Map 用于存储封装好的 BeanDefinitionMap

1
2
3
4
5
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,源码如下:

1
2
3
4
5
public class DefaultSingletonBeanRegistry extends ... implements ... {
//存储Bean实例的单例池
////key:是Bean的beanName,value:是Bean的实例对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
}

Bean 实例化的基本流程

  • 加载 xml 配置文件,解析获取配置中的每个 <bean> 的信息,封装成一个个的 BeanDefinition 对象;
  • 将 BeanDefinition 存储在一个名为 beanDefinitionMap 的 Map<String,BeanDefinition> 中;
  • ApplicationContext 底层遍历 beanDefinitionMap,创建 Bean 实例对象;
  • 创建好的 Bean 实例对象,被存储到一个名为 singletonObjects 的 Map<String,Object> 中;
  • 当执行 applicationContext.getBean (beanName) 时,从 singletonObjects 去匹配 Bean 实例返回。

image-20240621022208516

# Spring 的后处理器

Spring 的后处理器是 Spring 对外开发的重要扩展点,允许我们介入到 Bean 的整个实例化流程中来,以达到动态注册 BeanDefinition,动态修改 BeanDefinition,以及动态修改 Bean 的作用。

Spring 主要有两种后处理器:

  • BeanFactoryPostProcessor:Bean 工厂后处理器,在 BeanDefinitionMap 填充完毕,Bean 实例化之前执行;
  • BeanPostProcessor:Bean 后处理器,一般在 Bean 实例化之后,填充到单例池 singletonObjects 之前执行。

# Bean 工厂后处理器 – BeanFactoryPostProcessor

BeanFactoryPostProcessor 是一个接口规范,实现了该接口的类只要交由 Spring 容器管理的话,那么 Spring 就会回调该接口的方法,用于对 BeanDefinition 注册和修改的功能。

BeanFactoryPostProcessor 定义如下:

1
2
3
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

编写 BeanFactoryPostProcessor

1
2
3
4
5
6
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
System.out.println("MyBeanFactoryPostProcessor执行了...");
}
}

配置 BeanFactoryPostProcessor

1
<bean class="com.xxx.xxx.MyBeanFactoryPostProcessor"/>

postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,拿到 BeanFactory 的引用,自然就可以

对 beanDefinitionMap 中的 BeanDefinition 进行操作了 ,例如对 UserDaoImpl 的 BeanDefinition 进行修改操作

1
2
3
4
5
6
7
8
9
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 进行注册操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 操作

1
2
3
4
5
6
7
8
9
10
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);
}
}

image-20240621022719095

# Bean 后处理器 – BeanPostProcessor

Bean 被实例化后,到最终缓存到名为 singletonObjects 单例池之前,中间会经过 Bean 的初始化过程,例如:属性的填充、初始方法 init 的执行等,其中有一个对外进行扩展的点 BeanPostProcessor,我们称为 Bean 后处理。跟上面的 Bean 工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的 BeanPostProcessor,会在流程节点上被 Spring 自动调用。

BeanPostProcessor 的接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

image-20240621161955434

# 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 () 方法回调

# Bean 实例属性填充

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

  • 注入普通属性,String、int 或存储基本类型的集合时,直接通过 set 方法的反射设置进去;
  • 注入单向对象引用属性时,从容器中 getBean 获取后通过 set 方法反射设置进去,如果容器中没有,则先创建被注入对象 Bean 实例(完成整个生命周期)后,在进行注入操作;
  • 注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题,下面会详细阐述解决方案。

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

image-20240621164735541

1
2
3
4
5
6
public class UserServiceImpl implements UserService{
public void setUserDao(UserDao userDao) {}
}
public class UserDaoImpl implements UserDao{
public void setUserService(UserService userService){}
}

1
2
3
4
5
6
<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-20240621165048484

Spring 提供了三级缓存存储 完整 Bean 实例 和 半成品 Bean 实例 ,用于解决循环引用问题

在 DefaultListableBeanFactory 的上四级父类 DefaultSingletonBeanRegistry 中提供如下三个 Map:

1
2
3
4
5
6
7
8
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,存储到一级缓存,删除二三级缓存。

# 常用的 Aware 接口

Aware 接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能 API 不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似 Aware 的接口,让框架给我们注入该对象。

Aware 接口回调方法作用
ServletContextAwaresetServletContext(ServletContext context)Spring 框架回调方法,注入 ServletContext 对象。仅在 web 环境下生效。
BeanFactoryAwaresetBeanFactory(BeanFactory factory)Spring 框架回调方法,注入 BeanFactory 对象。
BeanNameAwaresetBeanName(String beanName)Spring 框架回调方法,注入当前 Bean 在容器中的 beanName
ApplicationContextAwaresetApplicationContext(ApplicationContext applicationContext)Spring 框架回调方法,注入 ApplicationContext 对象。

# Spring IoC 整体流程总结

image-20240621172440661

# Spring xml 方式整合第三方框架

xml 整合第三方框架有两种整合方案:

  • 不需要自定义名空间,不需要使用 Spring 的配置文件配置第三方框架本身内容,例如:MyBatis;
  • 需要引入第三方框架命名空间,需要使用 Spring 的配置文件配置第三方框架本身内容,例如:Dubbo。

Spring 整合 MyBatis,之前已经在 Spring 中简单的配置了 SqlSessionFactory,但是这不是正规的整合方式,

MyBatis 提供了 mybatis-spring.jar 专门用于两大框架的整合。

Spring 整合 MyBatis 的步骤如下:

  • 导入 MyBatis 整合 Spring 的相关坐标;(请见资料中的 pom.xml)
  • 编写 Mapper 和 Mapper.xml;
  • 配置 SqlSessionFactoryBean 和 MapperScannerConfigurer;
  • 编写测试代码

配置 SqlSessionFactoryBean 和 MapperScannerConfigurer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--配置数据源-->
<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

1
2
3
public interface UserMapper {
List<User> findAll();
}

1
2
3
4
5
6
7
8
9
<?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>

编写测试代码

1
2
3
4
5
ClassPathxmlApplicationContext applicationContext =
new ClassPathxmlApplicationContext("applicationContext.xml");
UserMapper userMapper = applicationContext.getBean(UserMapper.class);
List<User> all = userMapper.findAll();
System.out.println(all);

Spring 整合 MyBatis 的原理剖析

整合包里提供了一个 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 () 方法

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean{
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 是根据类型自动装配

1
2
3
4
5
6
7
8
9
10
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;
}
}

1
UserMapper userMapper = applicationContext.getBean(UserMapper.class);

1
2
3
4
5
6
7
8
9
10
11
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 的标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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 容器中

1
2
3
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

引入 context 命名空间,在使用 context 命名空间的标签,使用 SpEL 表达式在 xml 或注解中根据 key 获得 value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<beans>

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

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

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

image-20240621211534198

第一点完成后,Map 集合 handlerMappings 就被填充了很多 XxxNamespaceHandler,继续往下追代码

2)在 DefaultBeanDefinitionDocumentReader 的 parseBeanDefinitions 方法中,发现如下逻辑:

1
2
3
4
5
6
7
//如果是默认命名空间
if (delegate.isDefaultNamespace(ele)) {
this.parseDefaultElement(ele, delegate);
//否则是自定义命名空间
} else {
delegate.parseCustomElement(ele);
}

如果是默认命名空间,则执行 parseDefaultElement 方法

1
2
3
4
5
6
7
8
9
10
11
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 方法

1
2
3
4
5
6
7
8
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 中去了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
public ContextNamespaceHandler() {
}
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 方法即可)。

设想自己是一名架构师,进行某一个框架与 Spring 的集成开发,效果是通过一个指示标签,向 Spring 容器中自动注入一个 BeanPostProcessor

1
2
3
4
5
6
7
8
9
<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

1
2
3
4
5
6
<?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:element>
</xsd:schema>

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

image-20240621213627866

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

1
2
3
4
5
6
public class HaohaoNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
this.registerBeanDefinitionParser("annotation-driven",new HaohaoBeanDefinitionParser());
}
}

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

1
2
3
4
5
6
7
8
9
public class HaohaoBeanDefinitionParser implements BeanDefinitionParser {
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

1
2
3
4
5
6
7
8
public class HaohaoBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws
BeansException {
System.out.println("自动注入HaohaoBeanPostProcessor成功");
return bean;
}
}

# 基于注解的 Spring 应用

基本 Bean 注解,主要是使用注解的方式替代原有 xml 的 <bean> 标签及其标签属性的配置

1
2
<bean id="" name="" class="" scope="" lazy-init="" init-method="" destroy-method="" 
abstract="" autowire="" factory-bean="" factory-method=""></bean>

使用 @Component 注解替代 <bean> 标签

可以通过 @Component 注解的 value 属性指定当前 Bean 实例的 beanName,也可以省略不写,不写的情况下为当前类名首字母小写

1
2
3
4
5
6
7
8
//获取方式:applicationContext.getBean("userDao");
@Component("userDao")
public class UserDaoImpl implements UserDao {
}
//获取方式:applicationContext.getBean("userDaoImpl");
@Component
public class UserDaoImpl implements UserDao {
}

使用注解对需要被 Spring 实例化的 Bean 进行标注,但是需要告诉 Spring 去哪找这些 Bean,要配置组件扫描路径

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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:context="http://www.springframework.org/schema/context"
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
">
<!-- 告知Spring框架去itheima包及其子包下去扫描使用了注解的类 -->
<context:component-scan base-package="com.itheima"/>
</beans>

@Component 就单纯一个 value 属性,那么 xml 配置 <bean> 时那些属性怎么进行配置呢?Spring 是通过注解方式去配置的之前 <bean> 标签中的那些属性,例如:@Scope

XML 配置注解描述
<bean scope="">@Scope在类上或使用了 @Bean 标注的方法上,标注 Bean 的作用范围,取值为 singletonprototype
<bean lazy-init="">@Lazy在类上或使用了 @Bean 标注的方法上,标注 Bean 是否延迟加载,取值为 truefalse
<bean init-method="">@PostConstruct在方法上使用,标注 Bean 的实例化后执行的方法。
<bean destroy-method="">@PreDestroy在方法上使用,标注 Bean 的销毁前执行的方法。

由于 JavaEE 开发是分层的,为了每层 Bean 标识的注解语义化更加明确,@Component 又衍生出如下三个注解:

描述
@Repository在 DAO (Data Access Object) 层类上使用,标识这是一个数据访问组件。
@Service在 Service 层类上使用,标识这是一个业务服务组件。
@Controller在 Web 层类上使用,标识这是一个 Spring MVC 控制器组件。

# Bean 依赖注入注解开发

Spring 主要提供如下注解,用于在 Bean 内部进行属性注入的:

注解描述
@Value使用在字段或方法上,用于注入普通数据 (如字符串、数字等)。
@Autowired使用在字段或方法上,用于根据类型(byType)自动注入引用数据。
@Qualifier使用在字段或方法上,结合 @Autowired 使用,根据名称注入 Bean。
@Resource使用在字段或方法上,根据类型或名称进行注入 Bean。

当容器中同一类型的 Bean 实例有多个时,会尝试自动根据名字进行匹配:

1
2
3
4
5
//匹配当前Bean
@Repository("userDao")
public class UserDaoImpl implements UserDao{}
@Repository("userDao2")
public class UserDaoImpl2 implements UserDao{}

当容器中同一类型的 Bean 实例有多个时,且名字与被注入 Bean 名称不匹配时会报错

@Qualifier 配合 @Autowired 可以完成根据名称注入 Bean 实例,使用 @Qualifier 指定名称

1
2
3
4
5
6
7
8
9
@Autowired
@Qualifier("userDao2")
private UserDao userDao;

@Autowired
@Qualifier("userDao2")
public void setUserDao(UserDao userDao){
System.out.println(userDao);
}

@Resource 注解既可以根据类型注入,也可以根据名称注入,无参就是根据类型注入,有参数就是根据名称注入

1
2
3
4
5
6
7
@Resource
private UserDao userDao;

@Resource(name = "userDao2")
public void setUserDao(UserDao userDao){
System.out.println(userDao);
}

PS:@Resource 注解存在与 javax.annotation 包中,Spring 对其进行了解析

# 非自定义 Bean 注解开发

非自定义 Bean 不能像自定义 Bean 一样使用 @Component 进行管理,非自定义 Bean 要通过工厂的方式进行实例化,使用 @Bean 标注方法即可,@Bean 的属性为 beanName,如不指定为当前工厂方法名称

1
2
3
4
5
6
7
8
9
10
//将方法返回值Bean实例以@Bean注解指定的名称存储到Spring容器中
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}

PS:工厂方法所在类必须要被 Spring 管理

如果 @Bean 工厂方法需要参数的话,则有如下几种注入方式:

  • 使用 @Autowired 根据类型自动进行 Bean 的匹配,@Autowired 可以省略 ;
  • 使用 @Qualifier 根据名称进行 Bean 的匹配;
  • 使用 @Value 根据名称进行普通数据类型匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Bean
@Autowired //根据类型匹配参数
public Object objectDemo01(UserDao userDao){
System.out.println(userDao);
return new Object();
}
@Bean
public Object objectDemo02(@Qualifier("userDao") UserDao userDao,
@Value("${jdbc.username}") String username){
System.out.println(userDao);
System.out.println(username);
return new Object();
}

# Bean 配置类的注解开发

@Component 等注解替代了 <bean> 标签,但是像 <import><context:componentScan> 等非 <bean> 标签怎样去使用注解替代呢?

1
2
3
4
5
6
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 组件扫描 -->
<context:component-scan base-package="com.itheima"/>
<!-- 引入其他xml文件 -->
<import resource="classpath:beans.xml"/>

@Configuration 注解标识的类为配置类,替代原有 xml 配置文件,该注解第一个作用是标识该类是一个配置类,第

二个作用是具备 @Component 作用

1
2
@Configuration
public class ApplicationContextConfig {}

@ComponentScan 组件扫描配置,替代原有 xml 文件中的 <context:component-scan base-package=""/>

1
2
3
@Configuration
@ComponentScan({"com.itheima.service","com.itheima.dao"})
public class ApplicationContextConfig {}

base-package 的配置方式:

  • 指定一个或多个包名:扫描指定包及其子包下使用注解的类
  • 不配置包名:扫描当前 @componentScan 注解配置类所在包及其子包下的类

@PropertySource 注解用于加载外部 properties 资源配置,替代原有 xml 中的 <context:property

placeholder location=“”/> 配置

1
2
3
4
@Configuration
@ComponentScan
@PropertySource({"classpath:jdbc.properties","classpath:xxx.properties"})
public class ApplicationContextConfig {}

@Import 用于加载其他配置类,替代原有 xml 中的 <import resource="classpath:beans.xml"/> 配置

1
2
3
4
5
@Configuration
@ComponentScan
@PropertySource("classpath:jdbc.properties")
@Import(OtherConfig.class)
public class ApplicationContextConfig {}

# Spring 配置其他注解

扩展:@Primary 注解用于标注相同类型的 Bean 优先被使用权,@Primary 是 Spring3.0 引入的,与 @Component 和 @Bean 一起使用,标注该 Bean 的优先级更高,则在通过类型获取 Bean 或通过 @Autowired 根据类型进行注入时,会选用优先级更高的

1
2
3
4
5
@Repository("userDao")
public class UserDaoImpl implements UserDao{}
@Repository("userDao2")
@Primary
public class UserDaoImpl2 implements UserDao{}

扩展:@Profile 注解的作用同于 xml 配置时学习 profile 属性,是进行环境切换使用的

注解 @Profile 标注在类或方法上,标注当前产生的 Bean 从属于哪个环境,只有激活了当前环境,被标注的 Bean 才能被注册到 Spring 容器里,不指定环境的 Bean,任何环境下都能注册到 Spring 容器里

# Spring 注解的解析原理

image-20240621223308353

# Spring 注解方式整合第三方框架

第三方框架整合,依然使用 MyBatis 作为整合对象,之前我们已经使用 xml 方式整合了 MyBatis,现在使用注解方式无非就是将 xml 标签替换为注解,将 xml 配置文件替换为配置类而已

原有 xml 方式整合配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--配置数据源-->
<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>

使用 @Bean 将 DataSource 和 SqlSessionFactoryBean 存储到 Spring 容器中,而 MapperScannerConfigurer 使用注解 @MapperScan 进行指明需要扫描的 Mapper 在哪个包下,使用注解整合 MyBatis 配置方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@ComponentScan("com.itheima")
@MapperScan("com.itheima.mapper")
public class ApplicationContextConfig {
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
//省略部分代码
return dataSource;}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}}

注解方式,Spring 整合 MyBatis 的原理,关键在于 @MapperScan,@MapperScan 不是 Spring 提供的注解,是 MyBatis 为了整合 Spring,在整合包 org.mybatis.spring.annotation 中提供的注解,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends Annotation> annotationClass() default Annotation.class;
// ... ...
}

重点关注一下 @Import ({MapperScannerRegistrar.class}),当 @MapperScan 被扫描加载时,会解析 @Import 注解,从而加载指定的类,此处就是加载了 MapperScannerRegistrar

MapperScannerRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,Spring 会自动调用 registerBeanDefinitions 方法,该方法中又注册 MapperScannerConfigurer 类,而 MapperScannerConfigurer 类作用是扫描 Mapper,向容器中注册 Mapper 对应的 MapperFactoryBean,前面讲过,此处不在赘述了:

1
2
3
4
5
6
7
8
9
10
11
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, 
ResourceLoaderAware {
//默认执行registerBeanDefinitions方法
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
//... 省略其他代码 ...
//注册BeanDefinition
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
}

Spring 与 MyBatis 注解方式整合有个重要的技术点就是 @Import,第三方框架与 Spring 整合 xml 方式很多是凭借自定义标签完成的,而第三方框架与 Spring 整合注解方式很多是靠 @Import 注解完成的。

@Import 可以导入如下三种类:

  • 普通的配置类
  • 实现 ImportSelector 接口的类
  • 实现 ImportBeanDefinitionRegistrar 接口的类

@Import 导入实现了 ImportSelector 接口的类

1
2
3
4
5
@Configuration
@ComponentScan("com.itheima")
@Import({MyImportSelector.class})
public class ApplicationContextConfig {
}

1
2
3
4
5
6
7
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//返回要进行注册的Bean的全限定名数组
return new String[]{User2.class.getName()};
}
}

ImportSelector 接口 selectImports 方法的参数 AnnotationMetadata 代表注解的媒体数据,可以获得当前注解修饰的类的元信息,例如:获得组件扫描的包名

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//获得指定类型注解的全部信息
Map<String, Object> annotationAttributes =
annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName());
//获得全部信息中basePackages信息
String[] basePackages = (String[]) annotationAttributes.get("basePackages");
//打印结果是com.itheima
System.out.println(basePackages[0]);
return new String[]{User2.class.getName()};
}
}

@Import 导入实现 ImportBeanDefinitionRegistrar 接口的类,实现了该接口的类的 registerBeanDefinitions 方法会被自动调用,在该方法内可以注册 BeanDefinition

1
2
3
4
5
6
7
8
9
10
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
//使用给定的BeanDefinitionRegistry参数,手动注册BeanDefinition
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.pojo.User2");
registry.registerBeanDefinition("user2",beanDefinition);
}
}