Spring启动流程(一)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring启动流程(一)相关的知识,希望对你有一定的参考价值。

参考技术A

以java-config形式编写一个测试demo,新建一个 AnnotationConfigApplicationContext ,如果是XML形式使用 ClassPathXmlApplicationContext

两者都继承了 AbstractApplicationContext 类,详细看下面的层次图。

注意:在new AnnotationConfigApplicationContext()时如果未指定参数,会报运行时异常: org.springframework.context.annotation.AnnotationConfigApplicationContext@6ebc05a6 has not been refreshed yet

AnnotationConfigApplicationContext的有参构造执行了3个方法,分别是自己的无参构造、register()、refresh();

在描述前先从网上找了一个总体流程图方便了解一下大致流程,理清思路。

在执行 AnnotationConfigApplicationContext 的无参构造方法前会调用父类 GenericApplicationContext 的无参构造方法;

GenericApplicationContext中实例化一个 DefaultListableBeanFactory ,也就是说bean工厂实际上是应用上下文的一个属性;

从上面的类层次图可以看到:应用上下文和bean工厂又同时实现了BeanFactory接口。

前面讲到我们为了解IOC使用了Spring提供的AnnotationConfigApplicationContext作为入口展开,那Spring怎么对加了特定注解(如 @Service、@Repository)的类进行读取转化成 BeanDefinition 对象呢?

又如何对指定的包目录进行扫描查找 bean 对象呢?

所以我们需要new一个注解配置读取器和一个路径扫描器。

AnnotatedBeanDefinitionReader中执行了AnnotationConfigUtils中的registerAnnotationConfigProcessors(this.registry)方法,会向容器注册Sprign内置的处理器。

registerAnnotationConfigProcessors方法中通过 new RootBeanDefinition(XX.class) 新建一个RootBeanDefinition(BeanDefinition的一个实现),然后调用registerPostProcessor将内置bean对应的BeanDefinition保存到bean工厂中;

这里需要说明的是:我们刚刚一直在谈到注册bean,实际上就是将内置bean对应的beanDefinition保存到bean工厂中。那为什么要保存beanDefinition呢?因为Spring是跟据beanDefinition中对bean的描述,来实例化对象的,就算自己定义的bean也是要被解析成一个beanDefinition并注册的。

其中最主要的组件便是 ConfigurationClassPostProcessor 和 AutowiredAnnotationBeanPostProcessor ,前者是一个 beanFactory 后置处理器,用来完成 bean 的扫描与注入工作,后者是一个 bean 后置处理器,用来完成 @AutoWired 自动注入。

这个步骤主要是用来解析用户传入的 Spring 配置类,解析成一个 BeanDefinition 然后注册到容器中,主要源码如下:

通过生成AnnotatedGenericBeanDefinition,然后解析给BeanDefinition的其他属性赋值,然后将BeanDefinition和beanName封装成一个BeanDefinitionHolder对象注册到bean工厂中(就是将beanName与baenDefinition封装到Map中,将beanName放到list中。Map与list都是bean工厂DefaultListableBeanFactory所维护的属性),和前面内置bean的注册相同。

执行到这一步,register方法到此就结束了,通过断点观察BeanFactory中的beanDefinitionMap属性可以看出: this()和this.register(componentClasses)方法中就是将内置bean和我们传的配置bean的beanDefinition进行了注册,还没处理标记了@Component等注解的自定义bean。

Spring源码剖析-基于注解的IOC启动流程

前言

在上一篇文章我们分析了一下基于注解的IOC启动流程的第一种方式,根据指定的BeanClass启动,这篇文章我们分析另外一种方式,扫描一个包路径来启动。

IOC案例

我这里还是使用 AnnotationConfigApplicationContext 写一个简单的IOC案例

第一步:创建一个类

package cn.xx
//通过扫描方式注册Bean
@Component
public class OtherBean {
}

第二步:使用 AnnotationConfigApplicationContext 扫描一个包

public class MyBeanTest {

    @Test
    public void testMyBean(){
        //通过容器工厂:AnnotationConfigApplicationContext 加载一个包中的Bean
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("cn.xxx");
        //通过容器工厂获取bean
        OtherBean bean= applicationContext.getBean(OtherBean .class);
        //打印bean
        System.out.println(bean);
    }
}

与之前不一样的是,这次的OtherBean这个类上面贴了 @Component注解,对于AnnotationConfigApplicationContext我们传入了该所在的包,我们希望它可以自动扫描到该注解所在的类,然后自动注册到Spring容器中。

AnnotationConfigApplicationContext

我们直接定位到这个构造器中,

	/**
	 * Create a new AnnotationConfigApplicationContext, scanning for bean definitions
	 * in the given packages and automatically refreshing the context.
	 * @param basePackages the packages to check for annotated classes
	 */
	 //创建一个新的 AnnotationConfigApplicationContext,扫描给定包中的 bean 定义并自动刷新上下文。
	public AnnotationConfigApplicationContext(String... basePackages) {
		//创建 AnnotatedBeanDefinitionReader Bean注册器 和   ClassPathBeanDefinitionScanner Bean扫描器
		this();
		//扫描给定的包
		scan(basePackages);
		//刷新容器
		refresh();
	}

	public void scan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		this.scanner.scan(basePackages);
	}

这三个方法和上篇文章里面说到的差不多,只是register(annotatedClasses) 根据class注册Bean 方法变成了 scan(basePackages); 扫描一个包下的Bean.该方法使用了 ClassPathBeanDefinitionScanner 来扫描, 跟一下scan方法

//在指定的基本包内执行扫描。
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

这里返回了注册的Bean的个数,通过doScan方法实现包的扫描和Bean的注册

ClassPathBeanDefinitionScanner

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		//用来装注册的Bean
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		//循环扫描多个包
		for (String basePackage : basePackages) {
			//获取符合条件的Bean,调用 ClassPathScanningCandidateComponentProvider
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			//循环处理扫描到的BeanDefinition
			for (BeanDefinition candidate : candidates) {
				//解析Scope元注解
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				//把scope属性设置到BeanDefinition中,默认单利
				candidate.setScope(scopeMetadata.getScopeName());
				//生成Bean的名字
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					//设置Bean的自动注入装配属性等
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					//解析Bean的通用注解,lazy,parimary等
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				//判断容器中是否已经包含该Bean,不兼容就报错
				if (checkCandidate(beanName, candidate)) {
					//把BeanDefinition封装成BeanDefinitionHolder
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					//根据Bean的作用域,生成代理 , 默认不会产生
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					//走 BeanDefinitionReaderUtils.registerBeanDefinition 方法注册Bean
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		//返回注册的所有Bean
		return beanDefinitions;
	}

该逻辑大概做了这些事情

  • 循环所有的包路径,从包下面扫描到满足条件的Bean,封装成BeanDefinition返回
  • 通过scopeMetadataResolver解析每个 Bean的scope设置到BeanDefinition中
  • 通过beanNameGenerator生成Bean的名字
  • 通过 AnnotationConfigUtils 解析Bean的公共注解,如lazy,primary等
  • 判断Bean时候已经在容器中,不兼容就报错冲突
  • 调用BeanDefinitionReaderUtils.registerBeanDefinition ,通过BeanDefinitionRegistry 把BeanDefinition注册到DefaultListableBeanFactory的Map中

扫描包:ClassPathScanningCandidateComponentProvider

bean的解析和注册流程和上一篇文章都一样,只是增加了包的扫描过程,我们跟一下ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<>();
		try {
			//拼接扫描路径: classpath/包路径/*.class ,扫描classpath下的所有class文件
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + this.resourcePattern;
			//使用 ResourcePatternResolver 把 class 字节码文件转成Resource[]
			Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				//resource可读
				if (resource.isReadable()) {
					try {
						//获取元数据解析器
						MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
						//是否是满足条件的Bean,
						//确定给定的类是否不匹配任何排除过滤器excludeFilters,并且匹配至少一个包含过滤器
						if (isCandidateComponent(metadataReader)) {
							//把满足条件的bean封装成ScannedGenericBeanDefinition对象,装到set中返回
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							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);
		}
		return candidates;
	}

	//根据MetadataReader判断Bean是否满足条件
	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
		//是否被排除
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
		//是否包含在includeFilters
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}

该方法中大概做了如下事情

  • 该方法中将 classpath 作为基础路径,把传入的包名进行拼接,如:classpath/包/*.class 作为扫描路径,即:包下面的所有class文件,
  • 然后通过ResourcePatternResolver 把该路径下的class文件 转成Resource[]
  • 通过 getMetadataReaderFactory 获取Resource的 MetadataReader 注解解析器,根据MetadataReader判断Bean是否满足条件
  • 确定给定的类是否不匹配任何排除过滤器excludeFilters,并且匹配至少一个包含过滤器
  • 如果满足注册条件就把Bean封装成 ScannedGenericBeanDefinition
  • 然后再次检查ScannedGenericBeanDefinition bean 定义是否有资格作为候选
  • 最后把所有扫描到的BeanDefinition转到set集合返回

总结

最后来做个小结,AnnotationConfigApplicationContext(package) 指定包扫描路径的方式和直接指定类名的方式多了一个包的扫描过程,Bean的解析和注册流程都是一样的。

  • 先是调用ClassPathScanningCandidateComponentProvider扫描给定的包下的class文件,满足条件的就装载成BeanDefinition返回
  • 然后在ClassPathBeanDefinitionScanner主流程中解析Bean的相关注解,Scope,lazy等
  • 最后通过BeanDefinitionRegistry把BeanDefinition注册到容器中。

IOC的启动流程就到这里把,喜欢的话请给个好评哦,你的肯定是我最大的动力!!!

以上是关于Spring启动流程(一)的主要内容,如果未能解决你的问题,请参考以下文章

Spring启动流程

Spring启动核心流程

Linux如何启动流程?Linux启动流程详解

Spring启动流程

spring boot怎么启动

Spring源码剖析-基于注解的IOC启动流程