第五讲-常用的BeanFactory后处理器
第五讲 BeanFactory后处理器
BeanFactory后处理器的作用:为BeanFactory提供扩展
编写如下价格Bean:
package com.cherry.chapter1.a05;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com.cherry.chapter1.a05.component")
public class Config {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("19915238312");
return dataSource;
}
}
package com.cherry.chapter1.a05.component;
import com.cherry.chapter1.a05.A05Application;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class Bean2 {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
public Bean2(){
log.debug("我被 spring 容器管理了...");
}
}
编写主方法:
package com.cherry.chapter1.a05;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.GenericApplicationContext;
public class A05Application {
private static final Logger log = LoggerFactory.getLogger(A05Application.class);
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config",Config.class);
context.refresh();
for (String name:context.getBeanDefinitionNames()){
System.out.println(name);
}
context.close();
}
}
运行结果如下:
config
我们发现,只有我们手动添加的Config这个bean被容器管理了,@Configuration, @ComponentScan注解并没有没注入到容器中,且组件扫描也没有生效,@Bean也不例外。
接下来我们添加以一些对@ComponentScan,@Bean注解解析的BeanFactory后处理器:
context.registerBean(ConfigurationClassPostProcessor.class);
运行结果如下:
21:22:05.679 [main] DEBUG com.cherry.chapter1.a05.component.Bean2 - 我被 spring 容器管理了...
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
bean2
sqlSessionFactoryBean
dataSource
我们发现这些Bean都被IOC容器管理了!
ConfigurationClassPostProcessor后处理器可以支持对@ComponentScan,@Bean, @Import, @ImportResource注解的解析支持。
除了ConfigurationClassPostProcessor后处理器,还有一个常见的后处理器,这个处处理器是和MyBatis整合的处理器,例如下面所示:
context.registerBean(MapperScannerConfigurer.class, bd->{ // @MapperScanner
bd.getPropertyValues().add("basePackage","com.cherry.chapter1.a05.mapper");
});
package com.cherry.chapter1.a05.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface Mapper1 {
}
@Mapper
public interface Mapper2 {
}
运行结果如下:
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
org.mybatis.spring.mapper.MapperScannerConfigurer
bean2
sqlSessionFactoryBean
dataSource
mapper1
mapper2
BeanFactory的模拟实现
1. 组件扫描功能 @ComponentScan
首先编写几个Bean:
@Component
public class Bean2 {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
public Bean2(){
log.debug("我被 spring 容器管理了...");
}
}
@Component
public class Bean3 {
private static final Logger log = LoggerFactory.getLogger(Bean3.class);
public Bean3(){
log.debug("我被 spring 容器管理了...");
}
}
public class Bean4 {
private static final Logger log = LoggerFactory.getLogger(Bean4.class);
public Bean4(){
log.debug("我被 spring 容器管理了...");
}
}
@Configuration
@ComponentScan(basePackages = "com.cherry.chapter1.a05.component")
public class Config {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("19915238312");
return dataSource;
}
}
我们可以通过Spring提供的工具类查找哪些类上有@ComponentScan注解,并获取对应的包名
// 使用 Spring提供的工具类查找类中是否包含ComponentScan注解
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
if (componentScan!=null){
// 找到了,获取注解上的属性:拿到包名
for (String p : componentScan.basePackages()) {
System.out.println(p);
}
}
运行如下:
com.cherry.chapter1.a05.component
接着将包名转为路径格式!Spring是按照文件路径去看Class类上是否包含@ComponentScan注解,而不是通过反射的方式判断类上是否包含@ComponentScan注解!
也就是说:
com.cherry.chapter1.a05.component -> classpath*:com/cherry/chapter1/a05component/**/*.class
// 将包名转为完整的路径名:
// com.cherry.chapter1.a05.component -> classpath*:com/cherry/chapter1/a05component/**/*.class
String path = "classpath*:"+p.replace('.','/')+"/**/*.class";
System.out.println(path);
紧接着使用Spring提供的工具类来判断类上是否包含@Component注解
// CachingMetadataReaderFactory 是用于读取类上信息的工具类
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
// 拼接完之后加载文件
Resource[] resources = context.getResources(path);
for (Resource r: resources){
System.out.println(r);
// 查看这些类上有没有加上@Component注解了
// reader 保存了这个类上的信息
MetadataReader reader = factory.getMetadataReader(r);
// 判断该类上是否包含@Component注解
System.out.println("类名:"+reader.getClassMetadata().getClassName());
if (reader.getAnnotationMetadata().hasAnnotation(Component.class.getName())) {
System.out.println("该类包含了@Component注解");
} else {
System.out.println("该类没有包含@Component注解");
}
}
运行如下:
classpath*:com/cherry/chapter1/a05/component/**/*.class
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean2.class]
类名:com.cherry.chapter1.a05.component.Bean2
该类包含了@Component注解
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean3.class]
类名:com.cherry.chapter1.a05.component.Bean3
该类包含了@Component注解
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean4.class]
类名:com.cherry.chapter1.a05.component.Bean4
该类没有包含@Component注解
如果我们在类上使用的是@Compnonent注解的派生类,例如@Controller,那么会扫描到吗?
我们在Bean4.java文件中加上@Controller注解重新测试一下:
@Controller
public class Bean4 {
private static final Logger log = LoggerFactory.getLogger(Bean4.class);
public Bean4(){
log.debug("我被 spring 容器管理了...");
}
}
classpath*:com/cherry/chapter1/a05/component/**/*.class
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean2.class]
类名:com.cherry.chapter1.a05.component.Bean2
该类包含了@Component注解
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean3.class]
类名:com.cherry.chapter1.a05.component.Bean3
该类包含了@Component注解
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean4.class]
类名:com.cherry.chapter1.a05.component.Bean4
该类没有包含@Component注解
但是很可惜,并没有被扫描掉,也就是说,@Component的派生注解并不会被扫描到。这时就不应该使用hasAnnotation()
方法来判断了,而是使用另一个方法hasMetaAnnotation()
if (reader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())) {
System.out.println("该类包含了@Component的派生注解");
} else {
System.out.println("该类没有包含@Component的派生注解");
}
classpath*:com/cherry/chapter1/a05/component/**/*.class
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean2.class]
类名:com.cherry.chapter1.a05.component.Bean2
该类包含了@Component注解
该类没有包含@Component的派生注解
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean3.class]
类名:com.cherry.chapter1.a05.component.Bean3
该类包含了@Component注解
该类没有包含@Component的派生注解
file [D:\javaProject\spring-analyse\chapter1\target\classes\com\cherry\chapter1\a05\component\Bean4.class]
类名:com.cherry.chapter1.a05.component.Bean4
该类没有包含@Component注解
该类包含了@Component的派生注解
我们发现此时识别到了@Component的派生注解!
接下来我们根据我们已经找出的包含@Component以及派生注解的类封装BeanDefinition对象并加入到容器中:
// 如果直接加了@Component注解或者间接加了@Component注解(派生注解)
if (reader.getAnnotationMetadata().hasAnnotation(Component.class.getName())
|| reader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())) {
// 将该类封装为BeanDefinition对象
BeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(reader.getClassMetadata().getClassName())
.getBeanDefinition();
// 使用Spring提供的工具为该beanDefinition生成一个名字
BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory());
// 将该BeanDefinition对象加入到BeanFactory中
context.getDefaultListableBeanFactory().registerBeanDefinition(beanName, beanDefinition);
}
测试运行一下:
18:42:14.088 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@4d591d15
18:42:14.178 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
18:42:14.208 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
18:42:14.208 [main] DEBUG com.cherry.chapter1.a05.component.Bean2 - 我被 spring 容器管理了...
18:42:14.209 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
18:42:14.209 [main] DEBUG com.cherry.chapter1.a05.component.Bean3 - 我被 spring 容器管理了...
18:42:14.209 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean4'
18:42:14.209 [main] DEBUG com.cherry.chapter1.a05.component.Bean4 - 我被 spring 容器管理了...
18:42:14.278 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@4d591d15, started on Tue Jul 23 18:42:14 CST 2024
我们发现,被@Component以及派生的注解被Spring容器托管了!
总结:
这里我们总结一下Spring关于组件扫描器(包扫描)的实现思路:
- 首先通过配置类拿到@ComponentScan注解中的basePackage中的包信息
- 将包信息转为完整的路径信息
- 通过完整的路径信息逐一判断类上是否有@Component以及派生类的注解
- 如果有,则将该类封装为BeanDefinietion对象,并加入到BeanFactory容器中
最后呢,我们将完整的代码封装为独立的BeanFactoryPostProcessor:
package com.cherry.chapter1.a05.component;
import com.cherry.chapter1.a05.Config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.stereotype.Component;
import java.io.IOException;
public class ComponentScanPostProcessor implements BeanFactoryPostProcessor {
@Override // 在 context.refresh()方法调用时调用
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
try {
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
if (componentScan != null) {
// 找到了,获取注解上的属性:拿到包名
for (String p : componentScan.basePackages()) {
// System.out.println(p);
// 将包名转为完整的路径名:
// com.cherry.chapter1.a05.component -> classpath*:com/cherry/chapter1/a05component/**/*.class
String path = "classpath*:" + p.replace('.', '/') + "/**/*.class";
System.out.println(path);
// CachingMetadataReaderFactory 是用于读取类上信息的工具类
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
// 拼接完之后加载文件
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
for (Resource r : resources) {
// System.out.println(r);
// 查看这些类上有没有加上@Component注解了
// reader 保存了这个类上的信息
MetadataReader reader = factory.getMetadataReader(r);
// 判断该类上是否包含@Component注解
// System.out.println("类名:" + reader.getClassMetadata().getClassName());
// if (reader.getAnnotationMetadata().hasAnnotation(Component.class.getName())) {
// System.out.println("该类包含了@Component注解");
// } else {
// System.out.println("该类没有包含@Component注解");
// }
//
// if (reader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())) {
// System.out.println("该类包含了@Component的派生注解");
// } else {
// System.out.println("该类没有包含@Component的派生注解");
// }
// 如果直接加了@Component注解或者间接加了@Component注解(派生注解)
if (reader.getAnnotationMetadata().hasAnnotation(Component.class.getName())
|| reader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())) {
// 将该类封装为BeanDefinition对象
BeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(reader.getClassMetadata().getClassName())
.getBeanDefinition();
// 使用Spring提供的工具为该beanDefinition生成一个名字
BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
if (configurableListableBeanFactory instanceof DefaultListableBeanFactory) {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
String beanName = beanNameGenerator.generateBeanName(beanDefinition, beanFactory);
// 将该BeanDefinition对象加入到BeanFactory中
beanFactory.registerBeanDefinition(beanName, beanDefinition);
}
}
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
最后在容器中添加我们写的组件扫描后处理器就可以完成组件扫描的功能:
context.registerBean(ComponentScanPostProcessor.class);
运行结果如下:
========================
19:00:27.569 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@3b22cdd0
19:00:27.664 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.cherry.chapter1.a05.component.ComponentScanPostProcessor'
classpath*:com/cherry/chapter1/a05/component/**/*.class
19:00:27.975 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
19:00:27.976 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
19:00:27.976 [main] DEBUG com.cherry.chapter1.a05.component.Bean2 - 我被 spring 容器管理了...
19:00:27.977 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
19:00:27.977 [main] DEBUG com.cherry.chapter1.a05.component.Bean3 - 我被 spring 容器管理了...
19:00:27.978 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean4'
19:00:27.978 [main] DEBUG com.cherry.chapter1.a05.component.Bean4 - 我被 spring 容器管理了...
19:00:28.047 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@3b22cdd0, started on Tue Jul 23 19:00:27 CST 2024
Process finished with exit code 0
OK,我们写的组件扫描工具生效了!
2. 对@Bean注解的解析
也就是将@Bean标注的方法封装为BeanDefinition封装到容器中。
首先看一下我们写的配置类:
package com.cherry.chapter1.a05;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@ComponentScan(basePackages = "com.cherry.chapter1.a05.component")
public class Config {
@Bean
public Bean1 bean1(){
return new Bean1();
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("19915238312");
return dataSource;
}
}
使用Spring提供的工具判断哪些类上加了@Bean注解
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/cherry/chapter1/a05/Config.class"));
// 寻找哪些方法上标注了@Bean注解
Set<MethodMetadata> methods =
reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method:methods){
System.out.println(method);
}
运行如下:
com.cherry.chapter1.a05.Config.bean1()
com.cherry.chapter1.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
com.cherry.chapter1.a05.Config.dataSource()
根据方法信息生成对应的BeanDefinition,并加入到BeanFactory中:
// 根据方法信息生成对应的BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 设置工厂方法
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
// 指定自动装配模式,否则会出错,原因是SqlSessionFactory中要包含一个参数
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
AbstractBeanDefinition bd = builder.getBeanDefinition();
// 加入到BeanFactory中
context.getDefaultListableBeanFactory().registerBeanDefinition(method.getMethodName(), bd);
测试如下:
bean1
sqlSessionFactoryBean
dataSource
bean2
bean3
bean4
不过dataSource方法上的@Bean注解中的额initMethod
方法没有被解析,接下来我们解析一下:
// 获取Bean注解上的属性
Map<String, Object> annotationAttributes = method.getAnnotationAttributes(Bean.class.getName());
String initMethod = (String) annotationAttributes.get("initMethod");
if (initMethod.length() >0){
builder.setInitMethodName(initMethod); //执行init上的初始化方法
}
运行结果如下:
19:27:36.444 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
同样的,对于@Bean注解上的其它方法分析步骤和上面的init方法分析思路类似。
最后,我们将该方法封装为一个解析@Bean注解的BeanFactoryPostProcessor:
package com.cherry.chapter1.a05;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
public class AtBeanPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) throws BeansException {
try {
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/cherry/chapter1/a05/Config.class"));
// 寻找哪些方法上标注了@Bean注解
Set<MethodMetadata> methods =
reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method:methods){
System.out.println(method);
// 获取Bean注解上的属性
Map<String, Object> annotationAttributes = method.getAnnotationAttributes(Bean.class.getName());
String initMethod = (String) annotationAttributes.get("initMethod");
// 根据方法信息生成对应的BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 设置工厂方法
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
// 指定自动装配模式,否则会出错,原因是SqlSessionFactory中要包含一个参数
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
if (initMethod.length() >0){
builder.setInitMethodName(initMethod); //执行init上的初始化方法
}
AbstractBeanDefinition bd = builder.getBeanDefinition();
if (bf instanceof DefaultListableBeanFactory){
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)bf;
// 加入到BeanFactory中
beanFactory.registerBeanDefinition(method.getMethodName(), bd);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
最后将我们写的对@Bean注解解析的BeanFactory后处理器加入到容器中:
context.registerBean(AtBeanPostProcessor.class);
3. 对MyBatis框架中@Mapper注解的解析分析
我们都知道,@Mapper注解写在接口上,而Spring容器并不能直接管理接口,那么接口是怎么变为可管理的对象的呢?这里呢,其实是Spring为MyBatis提供了一个MapperFactoryBean
,也就是Mapper工厂, Spring托管的是生产出来的Mapper对象,而不仅仅是接口。下面代码就是使用MapperFactoryBean创建Mapper对象
@Bean
public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory){
MapperFactoryBean<Mapper1> factoryBean = new MapperFactoryBean<>(Mapper1.class);
// 将该mapper对象加载到SqlSessionFactory中管理
factoryBean.setSqlSessionFactory(sqlSessionFactory);
return factoryBean;
}
@Bean
public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory){
MapperFactoryBean<Mapper2> factoryBean = new MapperFactoryBean<>(Mapper2.class);
// 将该mapper对象加载到SqlSessionFactory中管理
factoryBean.setSqlSessionFactory(sqlSessionFactory);
return factoryBean;
}
我们在容器中查看是否存在mapper1和mapper2对象
config
com.cherry.chapter1.a05.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource
mapper1
mapper2
我们发现mapper1和mapper2添加到Spring容器中了!
接下来我们手动模拟实现@Mapper注解的扫描实现
package com.cherry.chapter1.a05;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import java.io.IOException;
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:com/cherry/chapter1/a05/mapper/**/*.class");
CachingMetadataReaderFactory cachingFactory = new CachingMetadataReaderFactory();
for (Resource r: resources){
MetadataReader reader = cachingFactory.getMetadataReader(r);
// 读取类的元信息
ClassMetadata classMetadata = reader.getClassMetadata();
if (classMetadata.isInterface()) {
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
// 接口上注明了@Mapper接口(当然这里默认有@Mapper注解,也可以自行判断)
// 定义BeanDefinitionBuilder
AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
.addConstructorArgValue(classMetadata.getInterfaceNames()) //设置SqlSessionFactory
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) // 设置自动装配
.getBeanDefinition();
AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
String beanName = generator.generateBeanName(bd2, beanFactory);
beanFactory.registerBeanDefinition(beanName,bd);
}
}
// 判断
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构