一、 springboot中使用mybatis
指定扫描mapper的包
这里只是简单的使用@MapperScan
注解,因为重点是分析Mapper
对象注入到spring
中,所以其他的配置,这里不介绍。
@MapperScan(value = { "com.card.mapper"})
@SpringBootApplication
public class TradeApplication {
public static void main(String[] args) {
System.setProperty("dubbo.application.logger","slf4j");
SpringApplication.run(TradeApplication.class,args);
}
}
按道理没有其他毛病的话,包com.card.mapper
下的Mapper
对象应该就会注入spring中。
二、注入源码分析
这里涉及到的类先声明下
MapperScannerRegistrar
:主要作用将@MapperScan
中信息,配置到ClassPathMapperScanner
中。
ClassPathMapperScanner
:担任扫描指定包下的Mapper
,以及将Mapper
注入到spring的功能。
MapperScannerRegistrar
a. 调用时机
这个类调用时机是启动SpringBoot
的xxxApplication
类。
原因
在xxxApplication
中使用@MapperScan
后,这个类(MapperScannerRegistrar
)的registerBeanDefinitions
方法会自动调用。这个机制的触发原因如下:
- 使用
@Import
将MapperScannerRegistrar
导入。
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
- 实现
ImportBeanDefinitionRegistrar
接口。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar
b. 源码
/***
**/
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取MapperScan 注解
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
// 创建路径扫描器,下面的一大段都是将MapperScan 中的属性设置到ClassPathMapperScanner
// 做的就是一个set操作
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
// 设置资源加载器,作用:扫描指定包下的class文件。
scanner.setResourceLoader(resourceLoader);
}
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 注册过滤器,作用:什么类型的Mapper将会留下来。
scanner.registerFilters();
// 扫描指定包
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}
ClassPathMapperScanner
先来个这个类的继承代码
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
熟悉spring
注入流程的同学应该对ClassPathBeanDefinitionScanner
这个类有印象,其实这个类就是将bean
注入spring
中的核心入口。ClassPathBeanDefinitionScanner
主要干了以下几件事。
- 扫描指定包下的
class
文件。 - 将包含
@Component
,@ManagedBean
,@Named
的类的元信息放入BeanDefinition
对象中 - 判断
BeanDefinition
中的类元信息,将有效的放入spring
中
/**
**/
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
private MapperFactoryBean> mapperFactoryBean = new MapperFactoryBean
纵然已经解释了super.doScan
方法,但是还是懵懂,我们看看这个方法。
//ClassPathBeanDefinitionScanner
protected Set doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet();
for (String basePackage : basePackages) {
//找到有效的BeanDefintion ,在这里其实就是Mapper
Set candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 获取beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//校验beanDefintion是否已在spring工厂内。
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 将对象注入spring工
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
在super.doScan
中最关键的还是findCandidateComponents
这个方法,只要搞懂这个方法一切都揭开了。
//ClassPathBeanDefinitionScanner
public Set findCandidateComponents(String basePackage) {
Set candidates = new LinkedHashSet();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 资源加载器,找出指定包下的文件
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
// 是否包含`@Component`,`@ManagedBean`,`@Named`
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
// 这个就是子类重写的方法,如果不重写,接口无法放入spring中。
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
总结
到这里mybatis
中Mapper注入spring的源码已经分析完了。其实这个解决的问题还是如何将一个接口放入spring中。