手写Spring框架day5
今天我们了解一些Spring有关扫描深入的知识点。
Spring扫描前的准备工作
Spring的beanName生成
在之前第一天的文章里面,我们聊到了一些beanName的生成原则,主要是类名首字母小写(如果第二字母没有大写),或者直接类名(第二字母大写),遵循驼峰命名规范。
但是其实我们是可以自定义命名的规则的。
1 |
|
那如果我们没有定义呢?Spring在@Component中nameGenerator默认值是一个BeanNameGenerator.class。他会从注解中拿出nameGenerator,然后和BeanNameGenerator.class做比较,如果不等就是你有自己的beanName生成器,相等就使用默认的生成器。
注意
BeanNameGenerator.class是默认值,他只是一个接口,不是默认生成器!默认的生成器是一个AnnotationBeanNameGenerator的类,实现了BeanNameGenerator。
那这个生成器内部是怎么实现的呢?他会去先判断你是否是通过@Component定义出来的bean;然后会去拿@Component里面value的属性,如果你有设置值,就直接使用;如果没有,先拿到beanClassName,然后处理成bean的类名;接着就是我们当时写的,根据Introspector.decapitalize(className)去生成bean的名字。
这是里面的内部实现,Spring在扫描开始的时候,只是往扫描器里面放入了beanName的生成器,在扫描到beanDefinition的时候才会使用。
ScopeProxyMode的作用
我们在定义一个bean的时候,我们有可能会指定一下这个bean需要在什么时候去创建。比如:
1 |
|
这样,我们这个OrderService的bean就只会在请求来的时候才会创建。但是如果我在其他地方非懒加载式的注入了这个bean,如果放任不管,肯定是会报错的吧。所以,Spring就提供了和@Lazy一样的方式,允许我们先放一个假的动态代理对象进去。
那么这个动态代理是要用JDK还是CGLIB呢?我们就可以在@Scope里面去指定当前bean类需要使用哪种,或者在配置类的@ComponentScan里面统一配置,这就是ScopeProxyMode。如果没有指定的话,Spring就会默认不创建代理对象,就会产生问题。
设置类路径下扫描组件匹配的资源文件模式
Spring通过scanner.setResourcePattern(“…”)设置了在扫描包路径时,只加载符合该模式的class文件(或资源文件)。他的默认值是”**/*.class”,也就是扫描指定包及其子包下所有的.class文件,然后对这些类进行注解检查。我们可以通过@ComponentScan去配置。
ExcludeFilter
ExcludeFilter,就是排除过滤器,用来排除bean的。比如你不想要某个类成为bean,你就可以配置把他排除,使得Spring不会有这个类的bean:
1 | //数组形式 |
其中,type指定你要根据什么去进行过滤,如ASSIGNABLE_TYPE就是直接指定类,后面的class就直接指定是哪个类;ANNOTATION就是指定一个注解,有标记这个注解的类就不会产生bean,后面的class指定是哪个注解类;ASPECTJ,指定切面表达式;REGEX,指定正则表达式等等。
当你指定后,Spring就会把这个内容set到scanner里面。
在扫描路径解析完毕后(下面会讲),Spring会自己添加一个排除的过滤器,排除的是什么呢?就是各种配置类,配置类是无需注册为bean的。
lazyInit
我们还可以在@Component里面指定你扫描的这个包是否全部都是懒加载的bean,相当于一个整体配置:
1 |
这样Spring就会把这个内容set到scanner里面默认生成beanDefinition的策略,从非懒加载改为懒加载。
扫描路径解析
我们通常@Component里面会指定一个value,其实就是basePackages,如果我们使用这种方式标记扫描路径,就会自动解析出扫描的是指定的哪些包。
但是如果我们不通过这个方式,还可以有别的方式。如basePackageClasses,我们可以指定类;Spring会解析出扫描的是这些类所在的包。
如果我们都不指定呢?那Spring会直接根据你加了@Component的注解的类,也就是配置类的包来进行扫描。
就像Spring boot,为什么我们加上@SpringbootApplication后,什么都没有指定,就会扫描这个启动类所在的所有包及其子包呢?就是因为这个注解里面有@Component注解,声明了这个启动类是一个配置类。
开始进行扫描
扫描的入口方法是doScan,在之前mybatis和Spring整合的时候应该我们都有见过。
扫描是按照包的顺序进行的,也就是一个个for循环去扫描指定的包,然后生成一个个的set
去重
Spring会对这些beanDefinition进行处理;前面的我们先不看,主要是处理一些特殊的注解如@DependsOn等等,我们看到处理的最后,Spring会做一件事:去重。有时候,包路径可能会产生包含的关系,可能一个bean就会被扫描到两次,这个时候就需要去重。只有确保没有重复,才会注册到容器中。
Spring如果遇到重复的bean,还会检查是否兼容。如果不兼容,会直接抛出异常,容器初始化也就结束了。那什么是不兼容呢?
不兼容就是指一个bean的名字却对应两个类型。比如,我的UserServicebean的名字是userService,但是如果我在OrderService里面的@Component里面再定义这个bean的名字是userService,就会出现bean的名字重复了,这个时候就是重大错误,需要直接抛出异常。
否则就是兼容,但仍需要返回false说明这个bean已经重复了。
findCandidateComponents(String basePackage)中如何扫描 —— ASM技术
我们有了路径,应该要如何去读取类的元数据呢?
在之前我们手写Spring的时候,我们是利用的反射机制,去读取每一个类,然后获取有没有bean的声明注解,但是这在现在的Spring里面会导致一个问题:如果包下有很多很多类,难道要一个个都读入吗?我们知道JVM的策略永远是当类被使用的时候才会被加载,现在这样做不是直接违反了原则嘛。所以Spring就使用了自己封装的ASM技术,把类抽象化为资源,然后通过流式读取成一个资源元数据的reader,接着再执行判断。这样就不会产生加载过多类的问题了。
这是最常见、最普通的做法。但是Spring不止使用这种最普通的方法,他还提供了一个优化的方法,也就是我们自己手动加索引,告诉Spring我有哪些bean。
我们可以在META-INF包下建立一个Spring.components文件,里面声明所有我们的bean。但是注意:一旦你声明了,Spring就不会再去看其他的声明方式的内容了,比如你的@ComponentScan里面配置的路径失效了,Spring只会看这个文件的内容。所以这个是一个Spring提供的优化的方式。
两重判断 —— 第一重 Exclude和Include
现在我们拿到了类资源元数据的一个reader,我们就要开始判断这个类是否是一个bean了。
Spring里面设定了两重判断,第一重就是ExcludeFilter和IncludeFilter判断。ExcludeFilter之前我们讲过,在scanner设置里面就已经set好了。那IncludeFilter是什么呢?
其实,这个IncludeFilter是紧跟在ExcludeFilter后面设置好的。我们先来看看第一重判断的逻辑是什么,就知道IncludeFilter是什么用了:
如果这个类被任何一个ExcludeFilter拦截,他就不可能是bean,返回false;
如果这个类没有被任何一个ExcludeFilter拦截,他就有可能是一个bean,然后进行IncludeFilter拦截判断。
如果一个类至少被一个IncludeFilter拦截,那他就有可能是一个bean,进行下一轮判断。
如果一个类没有被一个IncludeFilter拦截,那他就不可能是一个bean,返回false。
到这里,我们就应该知道了:IncludeFilter和ExcludeFilter相反,就是用来规定bean应该有哪些特质来进行拦截的。
那Spring在当时set scanner的时候是怎么配置这个IncludeFilter的呢?
Spring自己的一套IncludeFilter里面包含这些内容:**@Component,@ManagedBean,@Named**。只要有一个就行,但是后面两个基本用不到,所以基本上也就是扫描@Component的bean了。
这两个条件判断通过后,就会进入下一轮判断。下一轮判断是判断我们@Condition注解定义的内容是否有符合,如:
1 |
|
如果你的条件match失败,这个bean也就不能生成了,如果match成功,bean就可以进入下一重判断。
第二重 —— 类的性质判断
现在这个bean的第一重条件检验通过了,现在就是对这个类性质的判断:是否独立类,是否接口,是否抽象类,是否有lookup注解(下面会讲)。
判断的逻辑是:
如果你的类是独立类,并且不是抽象类和接口,或者你是抽象类,但是里面的方法有@Lookup注解,就可以是一个bean。
那什么是独立类呢?
不继承自其他类除Object;
不作为非静态内部类(inner class)或嵌套类存在;(静态内部类是可以单独实例化的!)
可以单独编译、实例化和使用;
与其他类之间没有强耦合关系。
好,那我们再看看什么是@Lookup注解。
lookup注解本来是用来解决单例里面需要多例bean的需求。比如,一个单例bean注入了一个多例bean,但是在运行环境,我们再怎么运行这个多例bean都是一样的,因为被注入bean的是单例bean。这个lookup注解就有用了,他的功能是:加在方法上的时候,不执行这个方法,而是返回一个这个方法返回值类型的bean。其实返回的就是一个动态代理,但只要一直调用这个加上了lookup注解的方法,就可以实现单例bean得到多个不一样的多例bean了。
Spring允许抽象类里面有这个注解的时候去创建bean。
到这里,Spring手写部分就先告一段落了,如果后续有补充,将会写在后面。
- 标题: 手写Spring框架day5
- 作者: ylnxwlp
- 创建于 : 2025-10-05 22:51:18
- 更新于 : 2025-10-06 22:22:16
- 链接: https://www.swissroll.today/2025/10/05/手写Spring框架day5/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。