…忽略不必要代码…
//向这个bd里面注入一个 basePackage 属性,未来可以通过属性注入的方式注入到 MapperScannerConfigurer 的属性中
builder.addPropertyValue(“basePackage”, StringUtils.collectionToCommaDelimitedString(basePackages));
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
…忽略不必要代码…
}
这一段代码最终的逻辑简单来说就是构建了一个自定义扫描器MapperScannerConfigurer
然后注册到Bean工厂中,他也就是前面术语项中说的BeanDefinitionRegistryPostProcessor
的实现类,Spring声明周期中,会自动回调postProcessBeanDefinitionRegistry()
方法,进行一系列的操作。我们下一步就是进入到MapperScannerConfigurer
中看一下他做了哪些操作!
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
/**
- 自定义扫描器
- @param registry 注册到bean工厂的工具类
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//构建一个自定义的扫描器 他是 ClassPathBeanDefinitionScanner 的子类
// 可以扫描项目下的class文件转换成BeanDefinition
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
…忽略不必要代码…
//这一步是很重要的,他是注册了一系列的过滤器,使得Spring在扫描到Mapper接口的时候不被过滤掉
scanner.registerFilters();
//开始执行扫描程序 传入对应要扫描的包路径
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
这一段代码主要是在Spring回调这个方法后,这个方法会构建一个ClassPathMapperScanner
扫描器,他是前面术语项中说到的ClassPathBeanDefinitionScanner
的子类实现,然后调用 ClassPathMapperScanner
的scan
方法,将扫描到的类转换成对应的BeanDefinition
注册到容器中,正常来说我们应该关注的是scan方法,但是但是,我们在看scan之前,应该重点的关注一下registerFilters
方法,我们大可看一下他做了哪些操作!然后再去看scan方法!
/**
- 配置父扫描程序以搜索正确的界面。它可以搜索所有接口或仅搜索那些
- 扩展了markerInterface或/和那些用notificationClass注释的标记
*/
public void registerFilters() {
boolean acceptAllInterfaces = true;
// 如果指定指定注解标注的Mapper
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// 指定接口的Mapper接口
if (this.markerInterface != null) {
addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
@Override
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
//默认的添加所有的Mapper接口为MyBatis类
if (acceptAllInterfaces) {
// 默认包含所有类的过滤器
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// 排除package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith(“package-info”);
});
}
为什么要先看这个呢?因为对于Spring而言,他对一个BeanDefinition
有着很严格的校验,当扫描的类不符合预定的一些条件的时候,Spring就会把它丢弃掉,不会管理这个类,我们这个方法就是为了,让Spring在扫描到那些接口的时候,添加一些自定义的过滤器,使Spring能够识别我们预定的这些接口,然后转换成BeanDefinition
!
自定义的过滤器添加完毕后,我们就进入到scan方法去!
/**
- 在指定的基本程序包中执行扫描。
- @param basePackages 包以检查带注释的类
- @return 注册的bean的数量
*/
public int scan(String… basePackages) {
//获取现有的总数 bd
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//开始扫描逻辑
doScan(basePackages);
…忽略不必要代码…
//统计本次扫描新增加的BeanDefinition数量 使用总共的数量 - 原本的数量
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
这一步没的说,他会统计一下本次新加的一个bd的数量,我们进入到scan
方法
/**
- 调用父级搜索,该搜索将搜索并注册所有候选者。然后注册的对象处理以将它们设置为MapperFactoryBeans
- @param basePackages 要扫描的包路径
- @return 对应的BeanDefinition的包装类
*/
@Override
public Set doScan(String… basePackages) {
//调用父类的扫描逻辑,转换为 BeanDefinitionHolder
Set beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
…忽略不必要代码…
} else {
//为这些接口的逻辑设置beanClass
processBeanDefinitions(beanDefinitions);
}
//返回这些设置好的包装类
return beanDefinitions;
}
无可厚非,我们肯定先进入到super.doScan(basePackages)
方法!
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan 源码解读
/**
- 在指定的基本软件包中执行扫描,
- 返回注册的bean定义。
- 此方法不会注册注释配置处理器而是将其留给调用方。
- @param basePackages 包以检查带注释的类
- @return 为工具注册目的而已注册的一组bean(决不{@code null})
*/
protected Set doScan(String… basePackages) {
…忽略不必要代码…
Set beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
//查找候选组件主要是查找spring的bean 完成扫描的 这个是将传入的包路径下的类(符合条件的) 转换成对应的bd
Set candidates = findCandidateComponents(basePackage);
…忽略不必要代码…
}
//返回本次经过全部流程扫描的bean
return beanDefinitions;
}
这个代码篇幅原因我忽略了不少,具体源码注释如下:
当然,我们最需要关注的就是 findCandidateComponents(basePackage)
方法,他是真正的扫描逻辑,真正的将一个class行对象变为BeanDefinition
不想复制了,直接截图,理所应当的进入到了scanCandidateComponents
方法:
/**
- 这个就是扫描 过滤 转换 class成bd的地方
- @param basePackage 包路径
- @return 转换成功的bd
*/
private Set scanCandidateComponents(String basePackage) {
Set candidates = new LinkedHashSet<>();
try {
//拼装一个扫描的路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + ‘/’ + this.resourcePattern;
//这一步做了递归拿到所有的类,这一步读取了配置类里面配置的路径文件
//然后通过包名以及io手段将包名替换成文件夹的全路径,通过递归拿到里面所有的类文件
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
…忽略不必要代码…
//这里开始将对应的类资源文件转换成对应的bd
for (Resource resource : resources) {
…忽略不必要代码…
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
//这一步是扫描判断过滤器的
//可以通过 addIncludeFilter 添加一些匹配规则
//这个就是我们前面添加到的过滤器,不然的话在这里就不会生效
//也不会添加到容器中
if (isCandidateComponent(metadataReader)) {
//构建一个扫描bean的定义
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
//设置源
sbd.setResource(resource);
sbd.setSource(resource);
//这一步是判断这个是不是 接口等 可以由子类复写
//这个判断也很重要,下面一张图会详细解释
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
//确定是一个候选组件的话就把这个放到候选组件的集合里面
candidates.add(sbd);
}
…忽略不必要代码…
}
…忽略不必要代码…
}
…忽略不必要代码…
}
…忽略不必要代码…
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(“I/O failure during classpath scanning”, ex);
}
//返回 筛选转换的候选bean
return candidates;
}
上述代码片段中,第二段判断isCandidateComponent(sbd)
,只有它通过的时候,才会被加载到候选组件中,在Spring原本的逻辑中,他是不会被加载进来的,但是,因为MyBatis重写了这段逻辑,所以,他才会被加载,重写逻辑如下:
至此,我们的接口被扫描出来,并转换成了 BeanDefinition
,我们逐步返回到最终的调用逻辑org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
中:
我们将上一步扫描到的 BeanDefinitionHolder
使用箭头所指的方法设置了一些属性,什么属性呢?
/**
- 给扫描到的处理器设置一些自定义的属性
- @param beanDefinitions 对应接口的 beanDefinition
*/
private void processBeanDefinitions(Set beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
…忽略不必要代码…
// 映射器接口是Bean的原始类但是,bean的实际类是MapperFactoryBean
//这里传入的是对应接口的全限定名,未来注入到 mapperFactoryBean中后,会被自动的转换成class
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
//设置对应的class,细心点你会发现,他注入的属性并不是对应的接口,而是一个 MapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBeanClass);
…忽略不必要代码…
}
}
这一段逻辑特别重要,为什么呢?因为要知道我们扫描出来的bd都是接口类型的,在java中,接口是不能被实例化的,想要让Spring管理这些Mapper接口,那么Spring所实例化的必须是一个具体的类,所以,这里就注入了一个MapperFactoryBean
,他是FactoryBean
类型的对象,Spring后续在实例化这个Mapper接口的时候,会通过FactoryBean
实例化!我们进入到MapperFactoryBean
中查看对象!
在看这个之前,我们需要了解FactoryBean
的最基础的知识,就是Spring在创建对象的时候,如果发现这个对象是一个FactoryBean
类型的数据,那么会调用getObject
方法,获取对应的对象,所以,我们只需要关注org.mybatis.spring.mapper.MapperFactoryBean#getObject
方法,就可以看出Spring究竟是如何把一个接口变为具体的Mapper操作实现类的!
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
/**
- 通过注入额 mapperInterface全限定名,自动转换为class对象
*/
private Class mapperInterface;
…忽略不必要代码…
/**
- spring会回调这个方法获取最终的对象
- @return 要创建的对象
- @throws Exception 异常
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://img-blog.csdnimg.cn/img_convert/4347a23bf0f0612e4ad401802863a3fb.jpeg)
总结
至此,文章终于到了尾声。总结一下,我们谈论了简历制作过程中需要注意的以下三个部分,并分别给出了一些建议:
- 技术能力:先写岗位所需能力,再写加分能力,不要写无关能力;
- 项目经历:只写明星项目,描述遵循 STAR 法则;
- 简历印象:简历遵循三大原则:清晰,简短,必要,要有的放矢,不要海投;
以及最后为大家准备的福利时间:简历模板+Java面试题+热门技术系列教程视频
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
放矢,不要海投;
以及最后为大家准备的福利时间:简历模板+Java面试题+热门技术系列教程视频
[外链图片转存中…(img-5hjeOKpx-1713754923288)]
[外链图片转存中…(img-gr1VYCo7-1713754923288)]
[外链图片转存中…(img-WpC8hgYn-1713754923289)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!