一次Spring Configuration配置问题分析及对应的扫描加载过程
近日,系统中遇到了一个令人”费解“的问题:我们有一个jar包其中包含一个基础的@Configuration类,经查找确认一直没有添加到spring.factories中,但系统一直运行的非常正常。当同事把该jar包复用到另一工具程序中后,启动提示缺少该Configuration类型。问题来了,同一jar包为什么在不同的应用里出现两种不一致的现象? 没有添加到spring.factories又为什么一直可以使用?
带着这个疑问,我们对Configuration的扫描加载机制进行了跟踪,才得以定位到原因。先上一段关键的代码,再解释流程及原因。
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); }
scanner.setResourcePattern(componentScan.getString("resourcePattern")); for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } boolean lazyInit = componentScan.getBoolean("lazyInit"); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true); } Set<String> basePackages = new LinkedHashSet<>(); String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); return scanner.doScan(StringUtils.toStringArray(basePackages)); }
上面代码是类ComponentScanAnnotationParser中获取BeanDefinitionHolder的逻辑,其中第44、45行是向basePackages扫描路径中添加了启动类所在的package。回过头来看我们的问题,我们启动类中未配置任何basePackages,但问题jar包中的Configuration类型与启动类的package一致,所以也被扫描了进来。反过来工具的启动类package与我们的Configuration不一致,所以才出现问题。
问题说清楚了,再来梳理下Configuration的扫描、解析顺序,转载自:
https://blog.csdn.net/Xp545945/article/details/101437143
https://blog.csdn.net/Xp545945/article/details/101550359
https://my.oschina.net/u/4344750/blog/3811028 (非常棒的一篇文章,务必读一下)
Spring是一个IOC容器,只要配置了bean之后在程序的其他地方可以自动注入实例,现在使用最多是注解配置,那么配置的那些 @Configuration、@EnableAutoConfiguration等注解是如何工作的呢?
Spring是通过BeanFactory保存Bean的配置,那么BeanFactory如何创建?
SpringApplication启动时在其run方法中会创建一个ApplicationContext的子类,以Servlet项目为例,会创建AnnotationConfigServletWebServerApplicationContext实例。在创建AnnotationConfigServletWebServerApplicationContext实例时会通过父类GenericApplicationContext创建一个DefaultListableBeanFactory,这时的bean factory没有什么作用,后面会对这个bean factory进行修改。
如何发现有注解的类以及如何处理各种注解?
既然配置了注解,那么一定需要注解处理器来处理这些注解,所有首先加载的类应该是注解处理器。上面一个问题中已经知道会初始化一个 AnnotationConfigServletWebServerApplicationContext 实例,它有两个重要属性 AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner。 AnnotatedBeanDefinitionReader 初始化时会通过 AnnotationConfigUtils 注入6个注解处理器,其中就包括处理@Configuration 注解的 ConfigurationClassPostProcessor 。
注解处理器加载完成之后,就需要加载被注解的类。首先被加载的类是启动类,也就是通过run方法的参数传入的类型。通过 SpringApplication 的 prepareContext 方法将启动类添加到 bean factory 中。
经过前面两步 bean factory 中已经包含了注解处理器和带注解的启动类,在 AbstractApplicationContext 的 refresh 的过程中最终会调用 PostProcessorRegistrationDelegate 的 invokeBeanFactoryPostProcessors 方法,调用 BeanFactoryPostProcessor 的实现类(这里会循环调用,解析注解过程中产生的 BeanFactoryPostProcessor)。 ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 间接实现了 BeanFactoryPostProcessor 接口,所以从调用 ConfigurationClassPostProcessor 开始,Bean 的注册工作就正式启动了。
前面的问题中只加载了启动类,其他有注解的类如何加载?
首先可以肯定第一个处理的一定是启动类,在处理启动类的过程中,会解析启动类上的注解,这些注解通常都是用来声明需要加载哪些其他的类。例如 @ComponentScan 声明了需要加载的包, @Import 声明需要加载的一个或一组类。
ConfigurationClassPostProcessors 是处理配置类的处理器,前面已经知道它是在初始化 AnnotationConfigApplicationContext 时添加到 BeanFactory 中,并且作为一个 BeanDenifitionRegistryPostProcessor 在 AbstractApplicationContext 的 refresh 方法中被调用。接下来继续分析 ConfigurationClassPostProcessor 如何处理配置类,目前理解的主要流程如下:
- 在 bean factory 中查找已经添加的配置类 (在以 Servlet 项目为例子时,只有启动类需要解析)
- 对上一步的configClass进行排序。
- 利用 ConfigurationClassParser 解析配置类(解析该配置类上的其他注解)。
- 调用 doProcessConfigurationClass 方法解析配置类。
- 如果成员类也是配置类,则先解析成员类。
- 处理配置类上的显示或隐式的 @PropertySources 和 @PropertySource 注解。
- 处理配置类上显示或隐式的 @ComponentScans 和 @ComponentScan 注解,解析与其相关的配置类。
- 处理 @Import 注解,查找配置类上显示和隐式的 @Import 注解,保存注解的 value 值。
- 处理 @ImportResource 注解。
- 为配置类添加带 @Bean 注解的方法。
- 查找接口中带 @Bean 注解的方法。
- 如果父类也是配置类,则解析父类。
- 处理 DeferredImportSelector
- 将上一步解析出来的新配置类通过 loadBeanDefinitions 添加到 bean factory 中。
- 如果 bean factory 中有还没有解析的配置类,重复第3步。
有 @Configuration、@Import 、 @Component、@ComponentScan 以及 @ImportResource 这几个注解的类都是配置类,都会对其进行上面的处理。除了 @Configuration 注解只标识为配置类之外,另外几个注解都有各自的作用,在配置类处理过程中会根据这些注解的参数进行处理。