Spring源码剖析(三)-component-scan和注解

前言

前一章分析了Spring的整个启动流程,但是只是讲了基于XML的配置方式,基于注解和基于Java类的配置方式并没有讲解,本章就介绍一下这部分内容,同时也补充一些上章没有讲到的细节。

component-scan

上一章说到哪些类需要注册到IoC容器中,由容器负责创建,在基于XML的配置方式中我们通过<bean>标签来定义需要由IoC容器管理的bean。但在有很多bean需要容器管理的场景,比如100个bean,难道要在XML中定义100个<bean>标签,这显然不合理。
这时候就需要使用component-scan了,我们扫描一个或多个包,将每个包中配置了注解的类注册到IoC容器中。

<context:component-scan base-package="org.example.anno"></context:component-scan>

上面的例子就是扫描org.example.anno的所有类,我们来看下这是如何实现的。
在上一章的obtainFreshBeanFactory章节里,我们说到在这一步里会生成一个beanFactory,同时解析XML,加载beanDefinition。在BeanDefinitionParserDelegate类的parseCustomElement方法中解析自定义元素。

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

通过命名空间context找到对应的parser:ComponentScanBeanDefinitionParser

public BeanDefinition parse(Element element, ParserContext parserContext) {
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		// Actually scan for bean definitions and register them.
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
}

原理就是找到basePackages下的所有类,判断是否有对应的注解,有就注册beanDefinition到容器中,扫描的默认注解如下:

  1. @Component
  2. @javax.annotation.ManagedBean,如果有的话
  3. @javax.inject.Named,如果有的话

在解析的最后一步registerComponents,这一步做了很重要的事,开启注解配置:annotation-config,添加了一些重要的BeanPostProcessor:

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
		if (beanFactory != null) {
			if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
				beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
			}
			if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
				beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			}
		}

		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		......

		return beanDefs;
}

这里的代码只保留了重点,主要添加了以下BPP:

  1. AutowiredAnnotationBeanPostProcessor,用来处理Autowired注解
  2. CommonAnnotationBeanPostProcessor,用来处理Resource注解
  3. ConfigurationClassPostProcessor,用来处理Configuration和Bean注解

@Autowired

在第一章时,我们提出一个问题:类中的哪些成员需要容器自动注入,答案就是可以通过Autowired注解,注解的成员变量就会由IoC容器自动注入,我们来看下其实现原理。
Autowired注解是由AutowiredAnnotationBeanPostProcessor这个BPP来实现功能,当添加了component-scan,会开启annotation-config,就会注册这个BPP。

public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {

	/**
	 * Post-process the given merged bean definition for the specified bean.
	 * @param beanDefinition the merged bean definition for the bean
	 * @param beanType the actual type of the managed bean instance
	 * @param beanName the name of the bean
	 * @see AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors
	 */
	void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);

	/**
	 * A notification that the bean definition for the specified name has been reset,
	 * and that this post-processor should clear any metadata for the affected bean.
	 * <p>The default implementation is empty.
	 * @param beanName the name of the bean
	 * @since 5.1
	 * @see DefaultListableBeanFactory#resetBeanDefinition
	 */
	default void resetBeanDefinition(String beanName) {
	}

}

AutowiredAnnotationBeanPostProcessor实现了MergedBeanDefinitionPostProcessor接口,接口中的postProcessMergedBeanDefinition会在bean的实例创建后、属性注入前调用。

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
		metadata.checkConfigMembers(beanDefinition);
}

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
		// Fall back to class name as cache key, for backwards compatibility with custom callers.
		String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
		// Quick check on the concurrent map first, with minimal locking.
		InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
		if (InjectionMetadata.needsRefresh(metadata, clazz)) {
			synchronized (this.injectionMetadataCache) {
				metadata = this.injectionMetadataCache.get(cacheKey);
				if (InjectionMetadata.needsRefresh(metadata, clazz)) {
					if (metadata != null) {
						metadata.clear(pvs);
					}
					metadata = buildAutowiringMetadata(clazz);
					this.injectionMetadataCache.put(cacheKey, metadata);
				}
			}
		}
		return metadata;
}

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
		if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
			return InjectionMetadata.EMPTY;
		}

		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
		Class<?> targetClass = clazz;

		do {
			final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

			ReflectionUtils.doWithLocalFields(targetClass, field -> {
				MergedAnnotation<?> ann = findAutowiredAnnotation(field);
				if (ann != null) {
					if (Modifier.isStatic(field.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static fields: " + field);
						}
						return;
					}
					boolean required = determineRequiredStatus(ann);
					currElements.add(new AutowiredFieldElement(field, required));
				}
			});

			ReflectionUtils.doWithLocalMethods(targetClass, method -> {
				Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
				if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
					return;
				}
				MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
				if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
					if (Modifier.isStatic(method.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static methods: " + method);
						}
						return;
					}
					if (method.getParameterCount() == 0) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation should only be used on methods with parameters: " +
									method);
						}
					}
					boolean required = determineRequiredStatus(ann);
					PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
					currElements.add(new AutowiredMethodElement(method, required, pd));
				}
			});

			elements.addAll(0, currElements);
			targetClass = targetClass.getSuperclass();
		}
		while (targetClass != null && targetClass != Object.class);

		return InjectionMetadata.forElements(elements, clazz);
}

postProcessMergedBeanDefinition方法的主要作用就是判断当前bean、bean的成员变量、成员方法有没有相应的注解,有的话就缓存起来。主要逻辑在buildAutowiringMetadata方法中:

  1. 扫描所有成员变量上是否有this.autowiredAnnotationTypes,注意this.autowiredAnnotationTypes的初始化值是@Autowired、@Value、@javax.inject.Inject
  2. 扫描所有方法上是否有this.autowiredAnnotationTypes
  3. 如果扫描到,就组装元信息,缓存下来

AutowiredAnnotationBeanPostProcessor还实现了postProcessProperties方法,这个方法会在bean实例化后,属性注入(populateBean)阶段调用。

@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
			metadata.inject(bean, beanName, pvs);
		}
		catch (BeanCreationException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
}

从缓存中获得前面缓存“注入元信息metadata”,注入

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			for (InjectedElement element : elementsToIterate) {
				element.inject(target, beanName, pvs);
			}
		}
}

如果这个bean没有被Autowired或Value注解的成员或方法,elementsToIterate会为空,否则注入相应值。

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			Field field = (Field) this.member;
			Object value;
			if (this.cached) {
				try {
					value = resolvedCachedArgument(beanName, this.cachedFieldValue);
				}
				catch (NoSuchBeanDefinitionException ex) {
					// Unexpected removal of target bean for cached argument -> re-resolve
					value = resolveFieldValue(field, bean, beanName);
				}
			}
			else {
				value = resolveFieldValue(field, bean, beanName);
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
}

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			if (checkPropertySkipping(pvs)) {
				return;
			}
			Method method = (Method) this.member;
			Object[] arguments;
			if (this.cached) {
				try {
					arguments = resolveCachedArguments(beanName);
				}
				catch (NoSuchBeanDefinitionException ex) {
					// Unexpected removal of target bean for cached argument -> re-resolve
					arguments = resolveMethodArguments(method, bean, beanName);
				}
			}
			else {
				arguments = resolveMethodArguments(method, bean, beanName);
			}
			if (arguments != null) {
				try {
					ReflectionUtils.makeAccessible(method);
					method.invoke(bean, arguments);
				}
				catch (InvocationTargetException ex) {
					throw ex.getTargetException();
				}
			}
}

如果是成员变量,那就resolveFieldValue,其实就是从beanFactory中getBean,也就是第二章中介绍的流程;
如果是方法,那就resolveMethodArguments,然后调用方法。
以上就@Autowired的原理,注意@Autowired虽然是通过BPP实现,但并不是通过常用的postProcessBeforeInitialization和postProcessAfterInitialization方法实现。
不管是resolveFieldValue还是resolveMethodArguments,最后都是调用beanFactory.resolveDependency,这个方法是按类型获取bean

@Resource

Resource注解是由CommonAnnotationBeanPostProcessor这个BPP来实现功能,同样是在开启annotation-config时注册。
CommonAnnotationBeanPostProcessor功能逻辑基本与AutowiredAnnotationBeanPostProcessor一样,只不过最终是通过调用beanFactory.resolveBeanByName来获取要注入的bean,按名字获取bean

@Configuration

@Configuration由ConfigurationClassPostProcessor实现功能,ConfigurationClassPostProcessor可以说是Spring最重要的PostProcessor之一,它可以处理以下注解:

  • @Configuration
  • @Component
  • @ComponentScan
  • @Import
  • @ImportResource
  • @Bean
    ConfigurationClassPostProcessor

ConfigurationClassPostProcessor会添加新的bean,而BPP是在所有bean定义都加载并实例化后才调用,所以ConfigurationClassPostProcessor本质上是一个BFPP,在bean工厂创建并加载所有bean定义后开始作用,往bean工厂中添加新的bean定义。下图是主体逻辑:

  1. processConfigBeanDefinitions,遍历beanFactory中的所有beanName,判断是否候选者
  2. 如果类上有@Configuration且proxyBeanMethods属性为true,则是候选者,FULL模式
  3. 如果类上有@Configuration或者有Component、ComponentScan、Import、ImportResource、Bean中任一注解,则是候选者,LITE模式
  4. 解析每个候选者
  5. 如果有@PropertySources,processPropertySource
  6. 如果@ComponentScans和@ComponentScan,则扫描,并添加bean,和前面component-scan逻辑一样
  7. 如果有@Import,则处理Import
  8. 找到所有@Bean注解的方法
  9. this.reader.loadBeanDefinitions(configClasses),这一步会把上面找到的class全部转为beanDefinition,注册到beanFactory中
  10. 调用另一个BFPP方法postProcessBeanFactory
  11. 遍历beanFactory中所有beanName,找到FULL模式的bean,做CGLib增强,也就是说@Bean注解的方法被一个直接返回bean的方法替换
  12. 添加一个BPP ImportAwareBeanPostProcessor
  13. 在beanFactory实例化所有bean时,对于@Bean注解的方法,装配方法的参数,执行方法,获得bean的实例。再再次执行该方法时,因为第11步的CGLib增强,会直接返回bean,而不是再执行一次方法
@Configuration
public class AppConfig {

	@Bean
	public UserDao userDao(){
		// 会被打印几次 ??
		System.out.println("注入UserDao");
		return new UserDao();
	}

	@Bean
	public OrderDao orderDao(){
		// 在这里调用userDao()方法
		userDao();
		return new OrderDao();
	}

}
// 启动容器
public class MainApplication {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
	}

}

正如上面13步所说,有@Configuration注解且属性proxyBeanMethods=true(默认为true)的类是FULL模式,会做类增强。所以userDao方法会被一个直接返回UserDao的方法替换,只会在bean实例化时执行一次,后面就直接返回bean。

@Import

@Import注解让我们可以像import库一样将一些第三方类注册到IoC容器中。简单介绍一下常用的场景:

  1. @Import(A.class)普通类,将是把类A作为bean注册到IoC容器中
  2. @Import(A.class),类A实现了ImportSelector接口,那么将selectImports方法返回的类注册到IoC容器中
  3. @Import(A.class),类A实现了ImportBeanDefinitionRegistrar接口,那么调用接口的registerBeanDefinitions方法将类注册到IoC容器中

基于注解

前一章讲到了基于XML配置的ClassPathXmlApplicationContext启动过程,其过程简单来讲就是解析XML,从中读取beanDefinition,注册到beanFactory中,然后实例化。如果XML配置了component-scan,还会扫描指定的包,找到@Component注解的类,注册到IoC容器。
那么基于注解配置的AnnotationConfigApplicationContext,因为没有了XML,我们可以猜到它的过程少了解析XML的过程,只有后面几步。
我们来分析下源码,看是不是这样。

public AnnotationConfigApplicationContext() {
		StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
		this.reader = new AnnotatedBeanDefinitionReader(this);
		createAnnotatedBeanDefReader.end();
		this.scanner = new ClassPathBeanDefinitionScanner(this);
}

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
		this(registry, getOrCreateEnvironment(registry));
}

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		Assert.notNull(environment, "Environment must not be null");
		this.registry = registry;
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

可以看到,在AnnotationConfigApplicationContext的默认构造函数中,创建AnnotatedBeanDefinitionReader实例,在AnnotatedBeanDefinitionReader的构造函数中AnnotationConfigUtils.registerAnnotationConfigProcessors,和在XML中配置component-scan一样,开启注解配置:annotation-config,注册了一些重要的BPP和BFPP

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
		this();
		register(componentClasses);
		refresh();
}

如果是带参的构造函数,比如ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class):

  1. 调用默认构造函数,开启annotation-config
  2. 注册参数传递的类
  3. refresh,就是上一章分析过的启动流程

如果MyConfig类上有@Configuration注解,就会由BFPP ConfigurationClassPostProcessor处理。
通过以上三章的内容,基本描述了Spring的依赖注入(IoC)功能,在后面的章节再介绍Spring的另一个重要功能AOP。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值