Stereotype Annotation
俗稱爲模式註解。Spring核心部分提供了幾種內建的模式註解
,如@Component,@Repository,@Service,@Controller,@Configuration等。這些註解均派生於@Component
。java
因爲Java語言規定,Annotation不容許繼承,沒有類派生子類的特性,所以Spring採用元標註的方式實現註解之間的派生
。正則表達式
@Component註解做爲Spring容器託管的通用模式組件,任何被@Component標註的組件均爲組件掃描的候選對象。spring
任何論證過程離不開所處的環境,須要開發人員具有必定工程意識,包括軟件版本,特性範圍,兼容狀況等。所以,論證過程從最低版本開始推導,逐步證實不一樣版本得提高和差別。編程
當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)從父類ClassPathScanningCandidateComponentProviderapp
中繼承。ide
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); ... } } ... }
思考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)
。在使用的時候,考慮處理
佔位符${...}
的狀況,注意資源是否可讀。
派生性
(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開始採用遞歸方式查找元註解。
(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:對象工具類
組合註解
指某個註解"元標註"一個或多個其餘註解,其目的在於將這些關聯的註解行爲組合成單個自定義註解。
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));