模拟解析
我们模拟是MapperScannerConfigurer,作用是将带有@Mapper注解的接口变为BeanDefinition交由spring管理
spring管理的都是一个对象,那么接口是怎么变为对应的对象呢,这里解释一下,来看下面代码(spring底层也是这样添加,缺点是不能批量添加mapper接口):
我们就是以这个MapperFactoryBean工厂对象生产出对应mapper接口的对象,最终我们用的也是这个工厂生产的对象,不仅仅是接口。
这里的例子我们生产mapper1类型的:
- 创建Mapper1.class的工厂对象(mapper不能独立构造,最终会用到sqlSessionFactory)
- 设置factory.setSqlSessionFactory(sqlSessionFactory);
配置号这个Bean之后可以自己打印调试一下,查看是否被注入进来;
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
结果如下说明正确:
这样就是将mapper1注册到BeanFactory的一个完整的过程;
步骤如下
接下来就真正开始模拟实现
1.扫描mapper包,拿到元数据,找到包下的interface接口
前面组件扫描模拟说过了这些,其代码的意思为,(1,2行)找到对应包获得资源,(4,5,6行)之后遍历获得元数据,(7行)根据元数据找出为接口的目标资源。
2.生成MapperFactoryBean(也就是最开始讲的那个部分)
- 定义BeanDefinition,管理类型为MapperFactoryBean.class类型
- 设置构造方法参数(mapper1.class),通过设置构造方法参数值设置
- 设置sqlSessionFactory装配,通过设置自动装配模式(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)来设置
上面几步,对应着这里的几步,只是设置的方法不同
代码如下:
设置一下名字,最终注册到容器中
实现代码:
设置名字有点奇怪,它右生成了一个bd2,这个bd2只是为了生成名字
最后运行一下查看结果(成功的话会吧我们配置的mapper加入到容器中)
这里需要将sqlSessionFactory先注入到容器中,否则会报错,我注册的是自己模拟的注入@Bean的后处理器( context.registerBean(AtBeanPostProcessor.class);),大家改为如下即可
context.registerBean(ConfigurationClassPostProcessor.class); // 可以解析 @ComponentScan @Bean @Import @ImportResoure
代码
MapperPostProcessor
package com.itheima.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 {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
Resource[] resources = resolver.getResources("classpath:com/itheima/a05/mapper/**/*.class");
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
for (Resource resource : resources) {
MetadataReader reader = factory.getMetadataReader(resource);
ClassMetadata classMetadata = reader.getClassMetadata();
if (classMetadata.isInterface()) {
AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
.addConstructorArgValue(classMetadata.getClassName())
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
.getBeanDefinition();
// 加了这段是为了根据接口类型不同生成不同的名字,如果不加的话都为 MapperFactoryBean.class 名字就会一样,这样还会导致后一个生成的,mapper2把前一个覆盖
AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
String name = generator.generateBeanName(bd2, beanFactory);
beanFactory.registerBeanDefinition(name, bd);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
A05
package com.itheima.a05;
import com.itheima.a05.mapper.Mapper1;
import com.itheima.a05.mapper.Mapper2;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
/*
BeanFactory 后处理器的作用
*/
// 研究 @Bean 标注后被创建流程
public class A05 {
private static final Logger log = LoggerFactory.getLogger(A05.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// BeanFactory 后处理器
// context.registerBean(ConfigurationClassPostProcessor.class); // 可以解析 @ComponentScan @Bean @Import @ImportResoure
// context.registerBean(MapperScannerConfigurer.class, (BeanDefinition bd) -> {
// bd.getPropertyValues().add("basePackage", "com.itheima.a05.mapper");
// } ); // @MapperScanner 里面也是用这个类
context.registerBean(AtBeanPostProcessor.class);
context.registerBean(MapperPostProcessor.class);
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// ⬇️销毁容器
context.close();
/*
学到了什么
a. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
b. 这些扩展功能由不同的 BeanFactory 后处理器来完成, 其实主要就是补充了一些 bean 定义
*/
}
}
mapper1
package com.itheima.a05.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface Mapper1 {
}
收获
- Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
- Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名