精尽 Spring Boot 源码分析 —— BeanDefinitionLoader
1. 概述
本文,我们来补充 《精尽 Spring Boot 源码分析 —— SpringApplication》 文章,并未详细解析的 BeanDefinitionLoader 。在 SpringApplication 中,我们可以看到 #load(ApplicationContext context, Object[] sources) 方法中,是如下一段代码:
|
下面,我们来一起揭开它的面纱~
2. BeanDefinitionLoader
org.springframework.boot.BeanDefinitionLoader ,BeanDefinition 加载器(Loader),负责 Spring Boot 中,读取 BeanDefinition 。其类上的注释如下:
|
2.1 构造方法
|
<1>处,设置sources属性。它来自方法参数Object... sources,来自SpringApplication#getAllSources()方法,代码如下:// BeanDefinitionLoader.java/*** 主要的 Java Config 类的数组*/ private Set<Class<?>> primarySources;private Set<String> sources = new LinkedHashSet<>();/*** Return an immutable set of all the sources that will be added to an* ApplicationContext when {@link #run(String...)} is called. This method combines any* primary sources specified in the constructor with any additional ones that have* been {@link #setSources(Set) explicitly set}.* @return an immutable set of all sources*/ public Set<Object> getAllSources() {Set<Object> allSources = new LinkedHashSet<>();if (!CollectionUtils.isEmpty(this.primarySources)) {allSources.addAll(this.primarySources);}if (!CollectionUtils.isEmpty(this.sources)) {allSources.addAll(this.sources);}return Collections.unmodifiableSet(allSources); }- 默认情况下,返回的结果是
Spring#run(Class<?> primarySource, String... args)方法的Class<?> primarySource的方法参数。例如说:MVCApplication 。
- 默认情况下,返回的结果是
<2.1>处,创建 AnnotatedBeanDefinitionReader 对象,设置给annotatedReader属性。<2.2>处,创建 XmlBeanDefinitionReader 对象,设置给xmlReader属性。<2.3>处,创建 GroovyBeanDefinitionReader 对象,设置给groovyReader属性。其中,#isGroovyPresent()方法,判断是否可以使用 Groovy 。代码如下:// BeanDefinitionLoader.javaprivate boolean isGroovyPresent() {return ClassUtils.isPresent("groovy.lang.MetaClass", null); }<2.4>处,创建 ClassPathBeanDefinitionScanner 对象,并设置给scanner属性。其中,ClassExcludeFilter 是 BeanDefinitionLoader 的内部静态类,继承 AbstractTypeHierarchyTraversingFilter 抽象类,用于排除对sources的扫描。代码如下:// BeanDefinitionLoader.java/*** Simple {@link TypeFilter} used to ensure that specified {@link Class} sources are* not accidentally re-added during scanning.*/ private static class ClassExcludeFilter extends AbstractTypeHierarchyTraversingFilter {private final Set<String> classNames = new HashSet<>();ClassExcludeFilter(Object... sources) {super(false, false);for (Object source : sources) {if (source instanceof Class<?>) {this.classNames.add(((Class<?>) source).getName());}}}@Overrideprotected boolean matchClassName(String className) {return this.classNames.contains(className);}}- 如果不排除,则会出现重复读取 BeanDefinition 的情况。
2.2 setBeanNameGenerator
#setBeanNameGenerator(BeanNameGenerator beanNameGenerator) 方法,代码如下:
|
2.3 setResourceLoader
#setResourceLoader(ResourceLoader resourceLoader) 方法,代码如下:
|
2.4 setEnvironment
#setEnvironment(ConfigurableEnvironment environment) 方法,代码如下:
|
2.5 load
#load() 方法,执行 BeanDefinition 加载。代码如下:
|
- 针对不同
source类型,执行不同的加载逻辑。 <1>处,如果是 Class 类型,则调用#load(Class<?> source)方法,使用 AnnotatedBeanDefinitionReader 执行加载。详细解析,见 「2.5.1 load(Class<?> source)」 。<2>处,如果是 Resource 类型,则调用#load(Resource source)方法,使用 XmlBeanDefinitionReader 执行加载。详细解析,见 「2.5.2 load(Resource source)」 。<3>处,如果是 Package 类型,则调用#load(Package source)方法,使用 ClassPathBeanDefinitionScanner 执行加载。详细解析,见 「2.5.3 load(Package source)」 。<4>处,如果是 CharSequence 类型,则调用#load(CharSequence source)方法,各种尝试去加载。例如说source为"classpath:/applicationContext.xml"。详细解析,见 「2.5.4 load(CharSequence source)」 。<5>处,无法处理的类型,抛出 IllegalArgumentException 异常。
2.5.1 load(Class<?> source)
#load(Class<?> source) 方法,使用 AnnotatedBeanDefinitionReader 执行加载。代码如下:
|
<1>处,调用#isComponent(Class<?> type)方法,判断是否为 Component 。代码如下:// BeanDefinitionLoader.javaprivate boolean isComponent(Class<?> type) {// This has to be a bit of a guess. The only way to be sure that this type is// eligible is to make a bean definition out of it and try to instantiate it.// 如果有 @Component 注解,则返回 trueif (AnnotationUtils.findAnnotation(type, Component.class) != null) {return true;}// Nested anonymous classes are not eligible for registration, nor are groovy// closures// 暂时忽略if (type.getName().matches(".*\\$_.*closure.*") || type.isAnonymousClass()|| type.getConstructors() == null || type.getConstructors().length == 0) {return false;}return true; }- 因为 Configuration 类,上面有
@Configuration注解,而@Configuration上,自带@Component注解,所以该方法返回true。
- 因为 Configuration 类,上面有
<2>处,调用AnnotatedBeanDefinitionReader#register(Class<?>... annotatedClasses)方法,执行注册。
2.5.2 load(Resource source)
#load(Resource source) 方法,使用 XmlBeanDefinitionReader 执行加载。代码如下:
|
- 调用
XmlBeanDefinitionReader#loadBeanDefinitions(Resource resource)方法,从 XML 中加载 BeanDefinition 。
2.5.3 load(Package source)
#load(Package source) 方法,使用 ClassPathBeanDefinitionScanner 执行加载。代码如下:
|
2.5.4 load(CharSequence source)
#load(CharSequence source) 方法,各种尝试去加载。代码如下:
按照
source是 Class > Resource > Package 的顺序,尝试加载。
|
<1>处,解析source。因为,有可能里面带有占位符。<2>处,将source转换成 Class ,然后执行 「2.5.1 load(Class<?> source)」 的流程。<3>处,尝试按照 Resource 进行加载。<3.1>处,调用#findResources(String source)方法,获得source对应的 Resource 数组。代码如下:// BeanDefinitionLoader.javaprivate Resource[] findResources(String source) {// 创建 ResourceLoader 对象ResourceLoader loader = (this.resourceLoader != null) ? this.resourceLoader : new PathMatchingResourcePatternResolver();try {// 获得 Resource 数组if (loader instanceof ResourcePatternResolver) {return ((ResourcePatternResolver) loader).getResources(source);}// 获得 Resource 对象return new Resource[] { loader.getResource(source) };} catch (IOException ex) {throw new IllegalStateException("Error reading source '" + source + "'");} }<3.2>处,遍历resources数组,调用#isLoadCandidate(Resource resource)方法,判断是否为符合条件的 Resource 。代码如下:// BeanDefinitionLoader.javaprivate boolean isLoadCandidate(Resource resource) {// 不存在,则返回 falseif (resource == null || !resource.exists()) {return false;}// 判断 resource 是 ClassPathResource 类,不是一个 packageif (resource instanceof ClassPathResource) {// A simple package without a '.' may accidentally get loaded as an XML// document if we're not careful. The result of getInputStream() will be// a file list of the package content. We double check here that it's not// actually a package.String path = ((ClassPathResource) resource).getPath();if (path.indexOf('.') == -1) {try {return Package.getPackage(path) == null;} catch (Exception ex) {// Ignore}}}// 返回 true ,符合条件return true; }<3.3>处,执行 「2.5.2 load(Resource source)」 的流程。<3.4>处,有加载到,则认为成功,返回。<4>处,尝试按照 Package 进行加载。<4.1>处,调用#findPackage(CharSequence source)方法,获得 Package 对象。代码如下:// BeanDefinitionLoader.javaprivate Package findPackage(CharSequence source) {// <X> 获得 source 对应的 Package 。如果存在,则返回Package pkg = Package.getPackage(source.toString());if (pkg != null) {return pkg;}try {// Attempt to find a class in this package// 创建 ResourcePatternResolver 对象ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader());// 尝试加载 source 目录下的 class 们Resource[] resources = resolver.getResources(ClassUtils.convertClassNameToResourcePath(source.toString()) + "/*.class");// 遍历 resources 数组for (Resource resource : resources) {// 获得类名String className = StringUtils.stripFilenameExtension(resource.getFilename());// 按照 Class 进行加载 BeanDefinitionload(Class.forName(source.toString() + "." + className));break;}} catch (Exception ex) {// swallow exception and continue}// 返回 Packagereturn Package.getPackage(source.toString()); }- 虽然逻辑比较复杂,我们只需要看看
<X>处的前半部分的逻辑即可。
- 虽然逻辑比较复杂,我们只需要看看
<4.2>处,执行 「2.5.3 load(Package source)」 的流程。<5>处,无法处理,抛出 IllegalArgumentException 异常。
666. 彩蛋
简单小文一篇。如果胖友不了解 Spring BeanDefinition ,可以补充看看 《【死磕 Spring】—— IoC 之加载 BeanDefinition》 文章。
如果想要测试 SpringFactoriesLoader 的各种情况,可以调试 BeanDefinitionLoaderTests 提供的单元测试。
参考和推荐如下文章:
- 一个努力的码农 《spring boot 源码解析8-SpringApplication#run第8步》
- oldflame-Jm 《Spring boot源码分析-BeanDefinitionLoader(7)》