一 注解声明Bean介绍
我们再回到这张图看,之前的两篇文章介绍了SpringIOC的源码(Spring容器(IOC)源码解析),但是我们是根据XML这个配置文件介绍的,在我们实际工作中目前来说用XML定义Bean的情况已经很少了,基本都是基于注解声明Bean定义,这种情况Bean是如何被注册到IOC容器的呢?我们围绕这个问题探讨。
我们从上图就可以看出来,XML解析和注解解析要走不同的分支,我们可以自己考虑一下XML是根据指定的XML文件地址,找到具体Bean声明文件,然后解析内部标签,根据标签属性给Bean配置不同属性或一些定义信息,之后转换成BeanDefinition对象由后续的IOC容器进行Bean的实例化和初始化;其实注解是同样的原理,只不过需要不同的逻辑去解析而已,首先我们要定义Bean的扫描包路径,Spring会把定义的扫描包路径所有类都拿到,然后判断是否使用了声明Bean注解,根据注解获取相应定义,转换成BeanDefinition对象,之后的流程和XML解析是一样的。
二 声明Bean注解都有什么
我们常用的声明Bean的注解有@Component @Controller @Service
@Repository @Configuration。
这些声明Bean注解有什么不同呢,其实他们的核心都是@Component,他们内部目前来说是没有什么差异的,都是引用@Component,既然都是@Component为什么Spring要定义不同的注解,第一为了在不同场景和分层内有明确区分,方便使用者阅读;最核心的还是Spring的高扩展性,Spring在编码的任何阶段都充分考虑扩展性,比如我们都用@Component注解声明bean有问题吗,其实是没问题的,他们都是可以正常注册到IOC中的,如果某天Spring需要对Controller的Bean做一些特殊定义或逻辑的时候,那通过这一个注解就无法扩展了,所以Spring在外边包了一层,而@Component只做Bean声明定义,这样也说明Spring遵守编码单一性原则。我们日常的开发中也要注意此类问题保证类和方法的单一性原则。
三 声明Bean源码
通过源码来验证一下声明Bean的注解核心是不是都是@Component注解。
// @Controller注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
// @Service注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
// @Repository注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
// @Configuration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
/**
* Explicitly specify the name of the Spring bean definition associated with the
* {@code @Configuration} class. If left unspecified (the common case), a bean
* name will be automatically generated.
* <p>The custom name applies only if the {@code @Configuration} class is picked
* up via component scanning or supplied directly to an
* {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
* is registered as a traditional XML bean definition, the name/id of the bean
* element will take precedence.
* @return the explicit component name, if any (or empty String otherwise)
* @see AnnotationBeanNameGenerator
*/
@AliasFor(annotation = Component.class)
String value() default "";
/**
* Specify whether {@code @Bean} methods should get proxied in order to enforce
* bean lifecycle behavior, e.g. to return shared singleton bean instances even
* in case of direct {@code @Bean} method calls in user code. This feature
* requires method interception, implemented through a runtime-generated CGLIB
* subclass which comes with limitations such as the configuration class and
* its methods not being allowed to declare {@code final}.
* <p>The default is {@code true}, allowing for 'inter-bean references' via direct
* method calls within the configuration class as well as for external calls to
* this configuration's {@code @Bean} methods, e.g. from another configuration class.
* If this is not needed since each of this particular configuration's {@code @Bean}
* methods is self-contained and designed as a plain factory method for container use,
* switch this flag to {@code false} in order to avoid CGLIB subclass processing.
* <p>Turning off bean method interception effectively processes {@code @Bean}
* methods individually like when declared on non-{@code @Configuration} classes,
* a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
* behaviorally equivalent to removing the {@code @Configuration} stereotype.
* @since 5.2
*/
boolean proxyBeanMethods() default true;
}
通过源码可以看到他们的核心确实是@Component注解,而且内部属性定义等都是一模一样的,唯独@Configuration多了一个代理属性,这也就是刚刚说到的扩展性了。
Spring是通过扫描类上是否有@Component注解来注册Bean的,所以只要类上有@Component Spring肯定会注册成Bean。
四 源码环境准备
还是在之前的项目定义一个声明注解Bean
通过注解扫描包进行测试
五 @Component源码解析
入口方法,初始化一些基础类;进行扫描解析成BeanDefinition对象;Spring的refresh方法,包括增强,bean实例化和初始化。
// 创建一个新的 AnnotationConfigApplicationContext,扫描给定包中的组件,为这些类注册 bean 定义,并自动刷新上下文
public AnnotationConfigApplicationContext(String... basePackages) {
// 初始化一些配置基础类
this();
// 注解扫描的核心,这一步已经把Bean注册好了BeanDefinition对象了
// 这块是和XML不一样的地方,XML是在refresh()方法内解析,注解是在这一步解析
scan(basePackages);
// 和XML是一样的,注解和XML解析内部有一些差异,后边补充
refresh();
}
和XML入口对比的看下,其实第一步都是初始化基础类,第二步注解是扫描包,并注册成BeanDefinition对象,而XML是设置配置文件路径,第三步核心的都是调用的同一个方法,包括Beanfactory增强,bean实例化和初始化。
// 使用给定的父级创建一个新的 ClassPathXmlApplicationContext,从给定的 XML 文件加载定义。
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 构造器调用,一直到AbstractApplicationContext.class
// 创建一个AbstractApplicationContext,合并父级配置到当前
super(parent);
// 设置配置文件路径
setConfigLocations(configLocations);
// refresh Spring容器
// bean的创建和实例化都是在refresh()方法中进行的,refresh()函数中包含了几乎所有的ApplicationContext中提供的全部功能。
if (refresh) {
refresh();
}
}
接着进入扫描包方法
// 在指定的包内执行扫描
@Override
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
.tag("packages", () -> Arrays.toString(basePackages));
this.scanner.scan(basePackages);
scanPackages.end();
}
// 在指定的包内执行扫描
public int scan(String... basePackages) {
// 获取当前IOC BeanDefinition数量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 完成包扫描
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
// 注册注释配置处理器,后置增强处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
// 判断此次共新增多少BeanDefinition
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
进行包扫描,注册bean定义,其实看到BeanDefinitionHolder我们就很熟悉了,这步已经注册成BeanDefinition对象,并可供SpringIOC进行实例化了。
// 在指定的基本包内执行扫描,返回注册的 bean 定义
// 此方法不注册一个注解配置处理器而是让这件事给调用者
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 多个包遍历扫描
for (String basePackage : basePackages) {
// 扫描包下有Spring Component注解,并且生成BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 封装BeanDefinitionHolder 并注册Spring容器
for (BeanDefinition candidate : candidates) {
// 设置scope,默认是singleton
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 生成代理类信息
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册到Spring容器,这个看第一篇文章和XML注册是一样的,https://mp.weixin.qq.com/s/gYWfjPBrnyDJQ7CPTvkadw
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
扫描包下的class文件,把有Component注解的封装BeanDefinition列表返回。
// 扫描包的类路径
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// 带组件索引处理
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
// 一般情况进到这块进行扫描
else {
return scanCandidateComponents(basePackage);
}
}
继续进入scanCandidateComponents(basePackage)分析,将包路径转换为文件绝对路径进行扫描其下面所有*.class文件,并将其解析为自己的资源对象Resource,接着最终要一步,遍历筛选候选组件isCandidateComponent(metadataReader) ,在此操作之前将resource文件通过反射获取到类的元信息,后面进行注解判断需要
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// classpath下对应包下面的所有*.class文件,resolveBasePackage(basePackage) 方法将包名称转换为路径名称,拼接为文件绝对路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 文件解析为资源对象
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
// 判断是否可读
if (resource.isReadable()) {
try {
//此处通过反射获取类元信息
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 进行过滤
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
获取类元信息
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
if (this.metadataReaderCache instanceof ConcurrentMap) {
// No synchronization necessary...
// 从本地缓存获取是否被创建过
MetadataReader metadataReader = this.metadataReaderCache.get(resource);
// 创建过则直接返回,否则进行创建
if (metadataReader == null) {
// 获取.class类元信息
metadataReader = super.getMetadataReader(resource);
// 存放到本地缓存
this.metadataReaderCache.put(resource, metadataReader);
}
return metadataReader;
}
else if (this.metadataReaderCache != null) {
synchronized (this.metadataReaderCache) {
MetadataReader metadataReader = this.metadataReaderCache.get(resource);
if (metadataReader == null) {
metadataReader = super.getMetadataReader(resource);
this.metadataReaderCache.put(resource, metadataReader);
}
return metadataReader;
}
}
else {
return super.getMetadataReader(resource);
}
}
获取类元信息
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
// 默认是SimpleMetadataReader实例
return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
}
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
// 解析.class元信息
getClassReader(resource).accept(visitor, PARSING_OPTIONS);
this.resource = resource;
this.annotationMetadata = visitor.getMetadata();
}
在isCandidateComponent(metadataReader)方法里,做了两件事,第一件判断有没有excludeFilters注解(这种注解的bean不被注入),第二件是判断有没有includeFilters注解(这种注解就是@Component这种注解,可以进行注入),其实判断也很简单,返回true的成功候选者,spring 中AnnotationConfigApplicationContext容器只识别@Component和@ManageBean两个注解,这段最核心的部分tf.match(metadataReader, getMetadataReaderFactory())进行判断
// 确定给定的类是否不匹配任何排除过滤器,并且匹配至少一个包含过滤器
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
通过反射获取注解信息和出入的includeFilter注解进行判断
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// This method optimizes avoiding unnecessary creation of ClassReaders
// as well as visiting over those readers.
if (matchSelf(metadataReader)) {
return true;
}
ClassMetadata metadata = metadataReader.getClassMetadata();
if (matchClassName(metadata.getClassName())) {
return true;
}
if (this.considerInherited) {
String superClassName = metadata.getSuperClassName();
if (superClassName != null) {
// Optimization to avoid creating ClassReader for super class.
Boolean superClassMatch = matchSuperClass(superClassName);
if (superClassMatch != null) {
if (superClassMatch.booleanValue()) {
return true;
}
}
else {
// Need to read super class to determine a match...
try {
if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
return true;
}
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not read super class [" + metadata.getSuperClassName() +
"] of type-filtered class [" + metadata.getClassName() + "]");
}
}
}
}
}
if (this.considerInterfaces) {
for (String ifc : metadata.getInterfaceNames()) {
// Optimization to avoid creating ClassReader for super class
Boolean interfaceMatch = matchInterface(ifc);
if (interfaceMatch != null) {
if (interfaceMatch.booleanValue()) {
return true;
}
}
else {
// Need to read interface to determine a match...
try {
if (match(ifc, metadataReaderFactory)) {
return true;
}
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +
metadata.getClassName() + "]");
}
}
}
}
}
return false;
}
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
六 注解解析堆栈流程
七 refresh方法XML和注解解析的差别
XML主要是在obtainFreshBeanFactory()做XML的解析及bean注册,但是我们刚看到了注解其实是在scan(basePackages)方法里做的,所以这一步肯定不用再做了。
obtainFreshBeanFactory()方法里就2个方法,他们都是工厂,所以根据不同的工厂适配不同的具体实现。
注解会进到GenericApplicationContext.class的refreshBeanFactory()和getBeanFactory()方法,此处只是把之前创建好的beanFactory返回。
而XML都会进去到AbstractRefreshableApplicationContext.class的这2个方法,XML解析可以查看之前的文章(Spring容器(IOC)源码解析(一)
八 UML类图
九 总结
Spring注解bean注册过程。第一步,初始化时设置了Component类型过滤器;第二步,根据指定扫描包扫描.class文件,生成Resource对象;第三步、解析.class文件并注解归类,生成MetadataReader对象;第四步、使用第一步的注解过滤器过滤出有@Component类;第五步、生成BeanDefinition对象;第六步、把BeanDefinition注册到Spring容器。