先做个基础知识普及,如何理解注解?
我的理解,注解就是扩展版的接口,接口的使用场景很有限,只能由类去实现接口,而注解则丰富的多,它可以用在类,实例属性,方法,参数等上面;
我对接口的理解是:接口的核心是用来表示一类事物的,比如:Person接口,凡是继承Person接口的都是人,Driver接口,凡是继承这个接口的都是司机。这种设计的作用就是业务逻辑(框架代码)可用通过类的这种继承关系来找到某一类对象。
注解大致延续和丰富了接口的这种先天的作用,一般情况下,注解被用作:标注一类事物(同接口一样);扩展一种功能;
1,标注一类事务
很好理解,比如Spring Bean,当一个类被@Component,@Service,@Repository等修饰的时候,它就表示是一个Spring Bean,在初始化的时候就会被Spring容器管理;
2,扩展一种功能
通常框架代码通过动态代理的形式增强被标注的类或者方法,比如:@Transactional,@Aspect,@Pointcut等;
上面说的是注解的作用,但是注解是如何起作用的?这就好比定一个标准,得有人去执行标准,而执行标准的就是框架的源码,也就是本篇要研究的,标准定义被Service修饰就是一个Bean,框架代码在处理的时候当遇到类被Service注解修饰,就会把类封装成BeanDefinition进行注册;其他人随便写一个注解@MyService,加到类头上,Spring就不会把它当成Bean来管理。
spirng配置文件,配置扫描路径:
这么一行配置,背后当然是有对应处理代码,代码就在命名空间句柄类中初始化
META-INF/spring.handler ——句柄配置文件,会把对应关系维护起来,在解析配置文件的时候方便找到对应的解析类
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
Spring处理文件,通常是由解析类和扫描类组成,或者叫解析器和扫描器,解析器中获取扫描器,由扫描器负责扫描配置文件,得到数据后,解析器再做后续处理。
从上面句柄类可以看到 component-scan 对应的处理类:ComponentScanBeanDefinitionParser,处理注解Bean就是这样的套路,下面解析代码中先得到一个扫描器,调用扫描器doScan方法获取BeanDefinitionHolder集合,扫描器能够扫描出哪些是Bean,其中必定有扫描的过滤规则,可用猜想到规则就是那些注解(接口)。跟代码发现在构造扫描器对象的时候,就会把过滤规则生成。
org.springframework.context.annotation.ComponentScanBeanDefinitionParser
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 这个方法虽然返回类型是BeanDefinition,但最终返回的是null,
// 因为扫描路径下肯定不止一个bean,即便要返回也应该是返回集合才对。
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);
// 这里获取beanDefinitions集合,那么判断逻辑肯定也在这个里面,我们重点看这里
Set beanDefinitions = scanner.doScan(basePackages);
// 注册BeanDefinition到BeanFactory
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
扫描器,在器构造方法中调用父类的registerDefaultFilters方法初始化过滤规则。
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
protected Set doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet();
// 循环配置的多个路径
for (String basePackage : basePackages) {
// 找一个目录下的所有BeanDefinition
Set candidates = findCandidateComponents(basePackage);
// 循环这些BeanDefinition
for (BeanDefinition candidate : candidates) {
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);
// 把BeanDefinition注册进registry(这个registry就是BeanFactory)
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
下面来看看匹配逻辑,就是:为什么只有常用的那几个注解才会被解析成Bean?自定义的不行
扫描器父类:org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
父类的 注册默认过滤规则 方法
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
// 上来就把Component注解加进去
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
// 后面两个分别是tomcat和标准java的两个注解
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
这里提一下includeFilters 和 excludeFilters
includeFilters是配合use-default-filters一起使用,只有use-default-filters=false才行,下面配置表示Spring只扫描用Controller注解修饰的类
excludeFilters在use-default-filters=true或者不配置use-default-filters时可用,下面配置表示Spring不会管理用Controller注解修饰的类
说完这个,其实已经解释了为什么自定义的不行,因为还要配置才行,比如:自定义一个特殊的目录,目录下的class只有用自定义的注解才会被Spring管理
继续看查询Bean的逻辑,依然是父类提供的方法,方法很简单:
1,遍历目录下所有的资源(class),
2,逐一判断是否符合Bean条件;
// 查询候选的组件bean
public Set findCandidateComponents(String basePackage) {
Set candidates = new LinkedHashSet();
try {
// 要扫描的路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 获取全部的Class
Resource[] resources = this.resourcePatternResolver.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 = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
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;
}
校验是否符合Bean的条件,逻辑match方法中
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
// excludeFilters剔除哪些注解,通常不会配置
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
// includeFilters包含的注解,通常也不配置,但是有默认值
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}
调用TypeFilter的match方法,上面已经分析了includeFilters里面放的TypeFilter是封装了Component.class。
// 上来就把Component注解加进去
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter
过滤器父类提供的匹配模板方法 ,具体的匹配规则在子类中实现
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;
}
// 略去部分代码
return false;
}
过滤器子类:org.springframework.core.type.filter.AnnotationTypeFilter
@Override
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
// 这里面两个判断逻辑
// metadata.hasAnnotation 是否被@Component注释
// metadata.hasMetaAnnotation 是否被@Component注释的子注解注释
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor
这是资源的注解元数据(可能的Bean对象的元数据),它有两个成员变量:
具体的判断逻辑则是先从Set集合中找,没有的话再到Map中找,找到了则返回true,表示是一个Bean。
例如:用Service标注
Set集合记录:[org.springframework.stereotype.Service]
Map集合记录:{org.springframework.stereotype.Service=[org.springframework.stereotype.Component]}
但是includeFilters集合中的数据是Component而不是Service,所以通过集合校验不成立,再通过Map校验,逻辑见下面代码片段,方法:hasMetaAnnotation,拿value中的数据与includeFilters比对,这样就匹配成功了。
至此,把Bean封装成BeanDefinitionHolder数组返回给处理器。
protected final Set annotationSet = new LinkedHashSet(4);
protected final Map> metaAnnotationMap = new LinkedHashMap>(4);
public boolean hasAnnotation(String annotationName) {
// 集合操作
return this.annotationSet.contains(annotationName);
}
@Override
public boolean hasMetaAnnotation(String metaAnnotationType) {
Collection> allMetaTypes = this.metaAnnotationMap.values();
for (Set metaTypes : allMetaTypes) {
if (metaTypes.contains(metaAnnotationType)) {
return true;
}
}
return false;
}