通过AbstractApplicationContext#refresh()提供了在加载完配置文件后的启动过程。以SpringBoot的Web环境来看启动过程(文末有spring容器的启动过程及区别)
1prepareRefresh():由于在此时还没有加载servlet容器,所以servletContext=null,servletConfig=null,还没有进行初始化propertySource,添加了几个ApplicationListeners。
2obtainFreshBeanFactory():创建BeanFactory,即DefaultListableBeanFactory
3prepareBeanFactory():对DefaultListableBeanFactory进行属性填充。如:添加类加载器,添加BeanPostProcessor等等。
4postProcessBeanFactory():在上面prepareBeanFactory()公共的之后,提供子类的特殊处理,注册特殊的后处理器,此处为ServletWebServerApplicationContext的具体实现。
5invokeBeanFactoryPostProcessors():主要是扫描包,注册成BeanDefinition,对类配置的信息解析成BeanDefinition的属性。然后执行实现了BeanFactoryPostProcessor接口的bean的postProcessBeanFactory()方法。
对于BeanDefinitionRegistryPostProcessor类型的BeanFactoryPostProcessor,执行postProcessBeanDefinitionRegistry
ConfigurationWarningsApplicationContextInitializer类型的processor,检查包名是否异常
获取类型为BeanDefinitionRegistryPostProcessor并且是PriorityOrdered优先排序类型的beanDefinition
上面获取postProcessorNames的具体实现如下
调用刚获取到的currentRegistryProcessor列表中的bean的postProcessBeanDefinitionRegistry()方法
获取到启动类
对启动类开始进行解析,包装成SourceClass,开始解析
获取到需要扫描的包信息,开始进行解析
解析是否有includeFilters,excludeFilters,lazyInit等属性,解析basePackage,basePackageClasses配置,如果没有,则根据启动类的包名获取,最终获得需要扫描的包。
解析包下所有beanDefinition,遍历并逐个解析。 如果实现了AnnotatedBeanDefinition接口,解析是否有lazy,Primary,DependsOn,Role,Description等注解并对beanDefinition进行相应属性设置。 检查是否在beanDefinitionMap中,如果不存在则将beanDefinition注册到beanDefinitionMap中。
对获取到的包下的beanDefinition进行遍历处理。处理是否import,ImportResource,Bean注解,处理实现的接口。
处理完BeanDefinitionRegistryPostProcessor类型的bean之后,处理之前注册的处理器,前两个处理器没做任何处理,第三个处理器ConfigurationClassPostProcessor,对目前加载的所有的beanDefinition进行处理。如果是启动类,生成代理类并替换原来的beanClass
获取实现了BeanFactoryPostProcessor接口的类,去除了上面已经解析过的internalConfigurationAnnotationProcessor,根据是否排序进行分类,然后按照PriorityOrdered,Ordered,nonOrdered以此执行。
6registerBeanPostProcessors():获取实现了BeanPostProcessor的bean,并根据是否实现order接口进行排序,并按顺序注册到beanPostProcessors中。
7initMessageSource():
8initApplicationEventMulticaster():注册事件广播器
9onRefresh():获取ServletWebServerFactory(tomcatServletWebServerFactory),创建TomcatWebServer,然后initServletPropertySources
创建TomcatWebServer之后获取需要初始化的servlet,filter
将servlet添加到servletContext中
将filter添加到servletContext中
10registerListeners():在事件广播器中注册监听器,注册监听器name,如果earlyApplicationEvents存在,则广播事件。
11finishBeanFactoryInitialization():实例化bean
先进行实例化之前的 *** 作,再实例化bean。
如果有实现InstantiationAwareBeanPostProcessor, 则执行postProcessBeforeInstantiation
根据实例化策略进行实例化(这里是cglib)包装成BeanWrapper
实例化之后执行后处理
执行postProcessProperties
属性设置,在AutowiredAnnotationBeanPostProcessor中进行依赖注入
是否实现BeanNameAware,BeanClassLoaderAware,BeanFactoryAware等接口
init之前
调用自定义的init方法,在InitDestroyAnnotationBeanPostProcessor中处理
如果实现了InitializingBean,执行afterPropertiesSet()方法
然后执行init之后的方法
12finishRefresh():主要做的事情是启动tomcat,并发布事件
逐个listener去匹配事件
以下为spring环境启动与web环境启动的相同和不同:
1prepareRefresh():区别在于initPropertySources()的实现不同,spring上下文为AnnotationConfigApplicationContext,web上下文AnnotationConfigServletWebServerApplicationContext
2obtainFreshBeanFactory():相同
3prepareBeanFactory():相同
4postProcessBeanFactory():spring使用的AbstractApplicationContext的默认处理,没有提供实现,web提供了实现注册了特殊的处理器
5invokeBeanFactoryPostProcessors():相同
6registerBeanPostProcessors():相同
7initMessageSource():相同
8initApplicationEventMulticaster():相同
9onRefresh():spring什么都没做,使用的AbstractApplicationContext的默认实现,web提供了创建web容器的过程
10registerListeners():相同
11finishBeanFactoryInitialization():相同
12finishRefresh():spring主要做的事情是发布事件,web容器多了一步启动web容器
综上:web环境和spring环境启动容器流程基本相同,spring默认使用AbstractApplicationContext的实现,web多了properties解析,注册特殊处理器,创建webServer和启动webServer的过程。
SpringBoot项目中或者 Spring项目中配置 <context:component-scan base-package="comexampledemo" />
,那么在IOC 容器初始化阶段(调用beanFactoryPostProcessor阶段) 就会采用ClassPathBeanDefinitionScanner进行扫描包下 所有类,并将符合过滤条件的类注册到IOC 容器内。Mybatis 的Mapper注册器(ClassPathMapperScanner) 是同过继承ClassPathBeanDefinitionScanner,并且自定义了过滤器规则来实现的。具体的 调用过程并不会在这里说明,只是想在这里描述ClassPathBeanDefinitionScanner是如何 扫描 和 注册BeanDefinition的。
ClassPathBeanDefinitionScanner作用就是将指定包下的类通过一定规则过滤后 将Class 信息包装成 BeanDefinition 的形式注册到IOC容器中。
过滤器用来过滤 从指定包下面查找到的 Class ,如果能通过过滤器,那么这个class 就会被转换成BeanDefinition 注册到容器。
如果在实例化ClassPathBeanDefinitionScanner时,没有说明要使用用户自定义的过滤器的话,那么就会采用下面的默认的过滤器规则。
注册了 @Component 过滤器到 includeFiters ,相当于 同时注册了所有被 @Component 注释的注解,包括 @Service , @Repository , @Controller ,同时也支持java EE6 的 javaxannotationManagedBean 和 JSR-330 的 @Named 注解。
实际执行包扫描,进行封装的函数是findCandidateComponents,findCandidateComponents定义在父类中。ClassPathBeanDefinitionScanner的主要功能实现都在这个函数中。
通过自定义的扫描器,扫描指定包下所有被@MyBean 注释的类。
通过对ClassPathBeanDefinitionScanner的分析,终于揭开了Spring 的类扫描的神秘面纱,其实,就是对指定路径下的 所有class 文件进行逐一排查,对符合条件的 class ,封装成 BeanDefinition注册到IOC 容器。
理解ClassPathBeanDefinitionScanner的工作原理,可以帮助理解Spring IOC 容器的初始化过程。
同时对理解MyBatis 的 Mapper 扫描 也是有很大的帮助。
因为 MyBatis 的MapperScannerConfigurer的底层实现也是一个ClassPathBeanDefinitionScanner的子类。就像我们自定义扫描器那样,自定定义了 过滤器的过滤规则。
先看一下测试用例
很遗憾加载了ServiceA,为什么会出现这种情况,我们下面分析一下
扫描的实现类为ClassPathBeanDefinitionScanner,Spring中所有的秒扫方法实现都是委托到这个类来处理的,我们直接看最原始的扫描结果可以明显看到这里虽然同包同名但是file的路径是不同的,所以在扫描处理这个阶段是可以区分的,但是实际上变为ScannedGenericBeanDefinition这个对象的时候,它的基类AbstractBeanDefinition的equals方法经过自身的重写,并没有去考虑实际Resource的不同也就是存在同包同名的Class这里也只会注册到容器一个对象(同包同名后面的会替换掉前面的)
可是虽然同包同名,但是我们通过@Profile进行了区分,这里应该是会把那个不满足条件的Class跳过才对,也就是应该加载了对的那个Class这里确实是加载的对的那一个,那么为什么加载了对了那个BeanDefinition我们的注解却没有生效呢这里实际上只解析了指定路径扫出来的结果,并没有处理配置类的递归解析,我们继续往下看看配置类的注解如何解析的
这里看到一旦扫描完成直接判断扫描到的BeanDefiition是否含有配置类@Configuration注解,如果有的话递归解析,但是parse方法并没有直接使用这个BeanDefiition已经解析出的注解而是自己从新加载
可以看到这里加载了错误的元信息回去,但是实际上这里解析不会出问题的,因为 processConfigurationClass方法会判断 @Conditional注解的条件
到这里都没有载入错误的信息,那到底这些错误的注解实在哪里解析的继续往下看,追踪到所有解析的入口在 ConfigurationClassPostProcessor的processConfigBeanDefinitions方法
先看一下主要的相关逻辑去除了部分其他逻辑细节
在第一次扫描完之后原来外层还会再判断一次配置类是否加载,这里可是从BeanDefiition取出来正确的元信息了,那么会加载正确吗
下面就找到了最终加载错误的元凶了
可以看到最后还是通过正确的BeanDefinition跳过验证,后面通过缓存加载了错误的类元信息,导致后面解析类元信息解析错误
尽量避免同包,每个项目的package都应该有自己独立的顶层package这样能避免同包同名导致的错误相同包内如果同名会提示错误的,所以同项目同包目录下不担心同名问题
示例源码: github源码地址
1创建一个配置类,在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <context:component-scan>。
2使用 ApplicationContext 的 getBeanDefinitionNames() 方法获取已经注册到容器中的 bean 的名称。
运行效果:
除了 spring 本身注册的一些 bean 之外,可以看到最后一行,已经将 BeanConfig 这个类注册进容器中了。
3指定要扫描的包(使用@ComponentScan 的 valule 属性来配置),创建一个controller 包,并在该包下新建一个 AppController 类。
在类上加了@Controller注解,说明该类是一个 Component。在 BeanConfig 类中修改:
在 @ComponentScan 注解中指定了要扫描的包。
运行效果:
AppController 已经被注册进容器了。
4excludeFilters 和 includeFilters 的使用
使用 excludeFilters 来按照规则排除某些包的扫描。
excludeFilters 的参数是一个 Filter[] 数组,然后指定 FilterType 的类型为 ANNOTATION,也就是通过注解来过滤,最后的 value 则是Controller 注解类。配置之后,在 spring 扫描的时候,就会跳过 iomieux 包下,所有被 @Controller 注解标注的类。
使用 includeFilters 来按照规则只包含某些包的扫描。
在创建一个 service 的包,并创建一个 AppService 类,再加上一个 @Service 注解。
修改 BeanCofig 类:
运行效果:
配置里面,应该是只包含 @Controller 注解的类才会被注册到容器中,为什么 @Service 注解的类也被注册了呢?这里涉及到 @ComponentScan 的一个 useDefaultFilters 属性的用法,该属性默认值为 true,也就是说 spring 默认会自动发现被 @Component、@Repository、@Service 和 @Controller 标注的类,并注册进容器中。要达到只包含某些包的扫描效果,就必须将这个默认行为给禁用掉(在 @ComponentScan 中将 useDefaultFilters 设为 false 即可)。
运行效果:
5添加多种扫描规则
1、如果使用的 jdk8,则可以直接添加多个 @ComponentScan 来添加多个扫描规则,但是在配置类中要加上 @Configuration 注解,否则无效。
2、也可以使用 @ComponentScans 来添加多个 @ComponentScan,从而实现添加多个扫描规则。同样,也需要加上 @Configuration 注解,否则无效。
6添加自定义过滤规则
在前面使用过 @Filter 注解,里面的 type 属性是一个 FilterType 的枚举类型:
使用 CUSTOM 类型,就可以实现自定义过滤规则。
1、 首先创建一个实现 TypeFilter 接口的 CustomTypeFilter 类,并实现其 match 方法。
这里简单对扫描到的类名进行判断,如果类名包含”Co“的就符合条件,也就会注入到容器中。
2、对 BeanConfig 进行修改,指定过滤类型为 Custom 类型,并指定 value 为 CustomTypeFilterclass。
运行效果:
以上就是关于Spring容器启动流程全部的内容,包括:Spring容器启动流程、Spring 的类扫描器分析 - ClassPathBeanDefinitionScanner、Spring同包同名的配置类处理方式等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)