Stereotype Annotation
俗稱為模式注解。Spring核心部分提供了幾種內建的模式注解
,如@Component,@Repository,@Service,@Controller,@Configuration等。這些注解均派生於@Component
。
由於Java語言規定,Annotation不允許繼承,沒有類派生子類的特性,因此Spring采用元標注的方式實現注解之間的派生
。
2:@Component派生性
@Component注解作為Spring容器托管的通用模式組件,任何被@Component標注的組件均為組件掃描的候選對象。
任何論證過程離不開所處的環境,需要開發人員具備一定工程意識,包括軟件版本,特性范圍,兼容情況等。因此,論證過程從最低版本開始推導,逐步證明不同版本得提升和差異。
3:@Component注解派生性原理
當ClassPathBeanDefinitionScanner#doScan(String... basePackages)
調用時,它利用basePackages參數迭代執行的findCandidateComponents(String basePackage)
,每次執行結果都生成候選的BeanDefinition集合,即candidates變量。
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider{ ... protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); //獲取候選的BeanDefinition集合 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); for (String basePackage : basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage); ... } return beanDefinitions; } ... }
而findCandidateComponents(String basePackage)從父類ClassPathScanningCandidateComponentProvider
中繼承。
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware { ... public Set<BeanDefinition> findCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(); try { //獲取查詢的package,並處理占位符情況${...},轉換為ClassLoader資源(.class)搜索路徑 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); ... //resource迭代執行,當資源可讀取時,獲取該資源的MetadataReader對象 for (Resource resource : resources) { ... if (resource.isReadable()) { try { //包含了類和注解元信息讀取方法 MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); //判斷資源是否為候選的組件,通過excludeFilters和includeFilters進行判斷 if (isCandidateComponent(metadataReader)) { //基於ASM,支持AnnotatedBeanDefinition接口 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); //判斷BeanDefinition是否候選組件 if (isCandidateComponent(sbd)) { ... candidates.add(sbd); } ... } } ... return candidates; } ... /** * Determine whether the given class does not match any exclude filter * and does match at least one include filter. * @param metadataReader the ASM ClassReader for the class * @return whether the class qualifies as a candidate component */ protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException{ for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return isConditionMatch(metadataReader); } } return false; } /** * Determine whether the given bean definition qualifies as candidate. * <p>The default implementation checks whether the class is not an interface * and not dependent on an enclosing class. * <p>Can be overridden in subclasses. * @param beanDefinition the bean definition to check * @return whether the bean definition qualifies as a candidate component */ protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { AnnotationMetadata metadata = beanDefinition.getMetadata(); return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); } /** * Register the default filter for {@link Component @Component}. * <p>This will implicitly register all annotations that have the * {@link Component @Component} meta-annotation including the * {@link Repository @Repository}, {@link Service @Service}, and * {@link Controller @Controller} stereotype annotations. * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and * JSR-330's {@link javax.inject.Named} annotations, if available. * */ @SuppressWarnings("unchecked") protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); ... } try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); ... } } }
默認情況下,ClassPathScanningCandidateComponentProvider構造參數useDefaultFilters為true
,並且顯示傳遞給父類構造參數。該方法給屬性includeFilters
增添了@Component
類型AnnotationTypeFilter的TypeFilter。
ClassPathBeanDefinitionScanner默認過濾器引入標注@Component,@Repository,@Service或者@Controller等類。同理,它也能夠標注所有@Component的"派生"注解。
@Component
注解只包含一個value屬性定義,所以其“派生”的注解也只能包含一個vlaue屬性定義。
Dubbo實現@Service
注解掃描實例:
ClassPathBeanDefinitionScanner允許自定義類型過濾規則。因此,Dubbo的@Service沒有標注@Component情況下,通過scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class))方式達到識別@Service標注類情況。但是沒有使用
@Component
注解的派生性。
Mybatis實現@Mapper
注解掃描實例:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner{ ... public ClassPathMapperScanner(BeanDefinitionRegistry registry) { super(registry, false); } /** * Configures parent scanner to search for the right interfaces. It can search * for all interfaces or just for those that extends a markerInterface or/and * those annotated with the annotationClass */ public void registerFilters() { boolean acceptAllInterfaces = true; // if specified, use the given annotation and / or marker interface if (this.annotationClass != null) { addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } // override AssignableTypeFilter to ignore matches on the actual marker interface if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } if (acceptAllInterfaces) { // default include filter that accepts all classes addIncludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } }); } // exclude package-info.java addExcludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); } }); } /** * {@inheritDoc} */ @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); } private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); ... //復雜對象構建考慮使用FactoryBean接口 // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean //添加泛型參數 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); ... } } ... }
4:思考擴展
思考1:利用ClassPathBeanDefinitionScanner
類配合includeFilters
和excludeFilters
定制化批量注冊Bean到Spring容器中。常常可以通過注解方式來包含或者排除候選類。
TypeFilter常用實現
-
AnnotationTypeFilter:注解類型過濾器
-
AssignableTypeFilter:確定此對象表示的類或者接口是否為給定類或者接口相同。
-
RegexPatternTypeFilter:判斷給定的類名是否符合指定正則表達式。
思考2:復雜對象構建考慮使用FactoryBean
實現類。
思考3:如果是讀取類和注解信息可以考慮基於ASM或者反射,使用方式往下可以獲取。當獲取已加載的類信息可以考慮反射(反射大前提是被反射的Class被ClassLoader加載
),ASM用於不需要將類路徑package下的Class全部加載,Spring應用指定Java package掃描Spring模式注解時,利用的就是基於ASM方式獲取類或者注解信息。基於ASM獲取會獲得更大性能。
思考4:資源讀取考慮使用ResourcePatternResolver
,這個對象的獲取可以通過Spring提供的工具類
ResourcePatternUtils.getResourcePatternResolver(resourceLoader)
。在使用的時候,考慮處理
占位符${...}
的情況,注意資源是否可讀。
5:多層次@Component派生性
(1):具體發展過程不再細說,詳解請看SpringBoot編程思想這本書
。其多層次@Component注解派生性構建在Spring4.x
。其核心處理類為AnnotationMetadataReadingVisitor
,其采用遞歸的方式查找元注解
。
(2):Spring中,MetadataReader接口唯一實現非公開類SimpleMetadataReader
。可以通過SimpleMetadataReaderFactory(ASM字節碼操作)
和CachingMetadataReaderFactory
獲取。
其中在SimpleMetadataReader實現上看,ClassMetadataReadingVisitor
和AnnotationMetadataReadingVisitor
分別是ClassMetadatta
和AnnotationMetadata
實現類。
由於ClassPathBeanDefinitionScanner
在尋找候選的BeanDefinition過程中,將指定basePackage參數下
的*.class資源進行元信息解析,也就是ClassMetadata
和AnnotationMetadata
對象。
AnnotationMetadataReadingVisitor
實現上使用了AnnotationAttributesReadingVisitor
,該類主要實現方法是visitEnd()
。Spring2.5實現未采用層次遞歸獲取Annotation[],所以僅支持單層次的@Component派生。Spring3.x實現僅兩層@Component派生。Spring4.x開始采用遞歸方式查找元注解。相關實現在Spring為公開類AnnotationAttributesReadingVisitor。
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor { private final MultiValueMap<String, AnnotationAttributes> attributesMap; private final Map<String, Set<String>> metaAnnotationMap; ... @Override public void visitEnd() { super.visitEnd(); Class<?> annotationClass = this.attributes.annotationType(); if (annotationClass != null) { List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType); if (attributeList == null) { this.attributesMap.add(this.annotationType, this.attributes); } else { attributeList.add(0, this.attributes); } Set<Annotation> visited = new LinkedHashSet<Annotation>(); Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass); if (!ObjectUtils.isEmpty(metaAnnotations)) { for (Annotation metaAnnotation : metaAnnotations) { if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) { recursivelyCollectMetaAnnotations(visited, metaAnnotation); } } } if (this.metaAnnotationMap != null) { Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>(visited.size()); for (Annotation ann : visited) { metaAnnotationTypeNames.add(ann.annotationType().getName()); } this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames); } } } private void recursivelyCollectMetaAnnotations(Set<Annotation> visited, Annotation annotation) { Class<? extends Annotation> annotationType = annotation.annotationType(); String annotationName = annotationType.getName(); if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) && visited.add(annotation)) { try { // Only do attribute scanning for public annotations; we'd run into // IllegalAccessExceptions otherwise, and we don't want to mess with // accessibility in a SecurityManager environment. if (Modifier.isPublic(annotationType.getModifiers())) { this.attributesMap.add(annotationName, AnnotationUtils.getAnnotationAttributes(annotation, false, true)); }
//實現遞歸查找元注解 for (Annotation metaMetaAnnotation : annotationType.getAnnotations()) { recursivelyCollectMetaAnnotations(visited, metaMetaAnnotation); } } catch (Throwable ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to introspect meta-annotations on [" + annotation + "]: " + ex); } } } } }
(3):思考擴展
-
考慮使用ASM的方式讀取類或者注解相關信息。(不需要全部將指定路徑下的類加載)
-
MetadataReaderFactory:獲取MetadataReader工廠
-
SimpleMetadataReaderFactory:簡單獲取MetadataReader工廠實現
-
ClassReader:基於ASM讀取類相關信息,公開類,不建議單獨使用。
-
AnnotationMetadataReadingVisitor:基於ASM讀取注解元數據相關信息,不建議單獨使用。
-
MethodMetadataReadingVisitor:基於ASM讀取方法相關信息,不建議單獨使用。
-
-
CachingMetadataReaderFactory:繼承SimpleMetadataReaderFactory,增加緩存MetadataReader資源功能。
-
-
MetadataReader:獲取訪問類和注解相關信息。通過MetadataReaderFactory獲取。
-
Resource getResource():獲取類文件資源引用
-
ClassMetadata getClassMetadata():讀取基礎類的基本元數據
-
AnnotationMetadata getAnnotationMetadata():讀取底層類完整注解元數據,包含注解方法的注解元數據。
-
-
-
考慮使用反射的方式讀取類或者注解相關信息(比較費時而且該類必須被ClassLoader加載)
-
StandardClassMetadata:基於反射讀取類元數據,可建議單獨使用。
-
StandardAnnotationMetadata:基於反射讀取注解元數據,可建議單獨使用
-
StandardMethodMetadata:基於反射讀取方法元數據,可建議單獨使用
-
-
考慮使用Spring內部支持的有用工具類
,都是來自於spring-core包中。多使用spring內建API,學習他們的長處。
-
ClassUtils:類工具類
-
CollectionUtils:集合工具類
-
NumberUtils:Number工具類
-
MimeTypeUtils:媒體類型工具類
-
IdGenerator:Id生成器
-
StringUtils:字符串工具類
-
ResourceUtils:資源工具類
-
ReflectionUtils:反射工具類
-
MethodIntrospector:方法自省工具類(EventListenerMethodProcessor#processBean中有使用)
-
PatternMatchUtils:正則資源匹配工具類
-
ObjectUtils:對象工具類
-
3:組合注解
組合注解
指某個注解"元標注"一個或多個其他注解,其目的在於將這些關聯的注解行為組合成單個自定義注解。
Spring Framework的類加載通過ASM實現,如ClassReader
。相對於ClassLoader體系,Spring ASM更為底層,讀取的是類資源
,直接操作其中的字節碼,獲取相關元信息。如MetadataReader接口
。
/** * Simple facade for accessing class metadata, * as read by an ASM {@link org.springframework.asm.ClassReader}. * * @author Juergen Hoeller * @since 2.5 */ public interface MetadataReader { /** * Return the resource reference for the class file. */ Resource getResource(); /** * Read basic class metadata for the underlying class. */ ClassMetadata getClassMetadata(); /** * Read full annotation metadata for the underlying class, * including metadata for annotated methods. */ AnnotationMetadata getAnnotationMetadata(); }
AnnotationMetadataReadingVisitor
同時實現了ClassMetadata及AnnotationMetadata。因此,元注解的實現集中到AnnotationMetadataReadingVisitor
和AnnotationAttributesReadingVisitor
之中。
MetadataReader
對象通過MetadataReaderFactory
對象獲取。
/** * Factory interface for {@link MetadataReader} instances. * Allows for caching a MetadataReader per original resource. * * @author Juergen Hoeller * @since 2.5 * @see SimpleMetadataReaderFactory * @see CachingMetadataReaderFactory */ public interface MetadataReaderFactory { /** * Obtain a MetadataReader for the given class name. * @param className the class name (to be resolved to a ".class" file) * @return a holder for the ClassReader instance (never {@code null}) * @throws IOException in case of I/O failure */ MetadataReader getMetadataReader(String className) throws IOException; /** * Obtain a MetadataReader for the given resource. * @param resource the resource (pointing to a ".class" file) * @return a holder for the ClassReader instance (never {@code null}) * @throws IOException in case of I/O failure */ MetadataReader getMetadataReader(Resource resource) throws IOException; }
具體某個注解的元注解
信息則通過getMetaAnnotationTypes(String)
方法查詢。
AnnotationMetadata
實現AnnotationMetadataReadingVisitor(ASM實現)
,StandardAnnotationMetadata(反射)
。
-
注解元信息抽象:
AnnotationMetadata
-
AnnotationMetadataReadingVisitor
-
AnnotationAttributesReadingVisitor
(遞歸查找元注解)
-
-
-
類元信息抽象:
ClassMetadata
-
方法元信息抽象:
MethodMetadata
-
注解屬性抽象:
AnnotationAttributes
-
屬性環境抽象:
Environment
-
屬性文件抽象:
PropertySource
-
元信息讀取抽象:
MetadataReader
-
通過
MetadataReaderFactory
獲取
-
方法內省:MethodIntrospector
Map<Method, EventListener> annotatedMethods = null; annotatedMethods = MethodIntrospector.selectMethods(targetType, (MethodIntrospector.MetadataLookup<EventListener>) method -> AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));