背景
上篇最后給大家了一個(gè)建議,建議配置bean掃描包時(shí)使用如下寫(xiě)法:
spring-mvc.xml
spring.xml
文中提到通過(guò)以上配置,就可以在Spring MVC容器中只注冊(cè)有@Controller注解的bean,Spring容器注冊(cè)除了@Controller的其它bean。
有的同學(xué)留言問(wèn)為什么這樣寫(xiě)就達(dá)到這種效果了呢?
也有人可能認(rèn)為我是無(wú)腦從網(wǎng)上抄來(lái)的,我有什么依據(jù),憑什么這么說(shuō)?經(jīng)過(guò)ISO 9000認(rèn)證了嗎?
為了維護(hù)文章的權(quán)威性以及我的臉面,本篇我就繼續(xù)帶大家從官網(wǎng)和源碼兩方面進(jìn)行分析。
2. 流程分析
2.1 Java注解
不是說(shuō)好的講嗎,怎么注解亂入了。
放心,雖然看源碼累,寫(xiě)讓大家看懂的文章更累,但是我還沒(méi)瘋。
為什么講注解,因?yàn)镾pring中很多地方用到注解,本文及前幾篇文章大家或多或少也都有看到。
因此在這里加個(gè)小灶,和大家一起回顧一下注解的知識(shí)點(diǎn)。
先查看官方文檔:
https://docs.oracle.com/javase/tutorial/java/annotations/predefined.html
Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.Annotations have a number of uses, among them:* Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.* Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.* Runtime processing — Some annotations are available to be examined at runtime.上面一段話翻譯過(guò)來(lái):注解是原數(shù)據(jù)的一種形式,對(duì)標(biāo)注的代碼邏輯上沒(méi)有直接的影響,只是用來(lái)提供程序的一些信息。主要用處如下:* 為編譯器提供信息,比如錯(cuò)誤檢測(cè)或者警告提示。* 在編譯和部署期處理期,程序可以根據(jù)注解信息生成代碼、xml文件。* 在程序運(yùn)行期用來(lái)做一些檢查。
2.2 Java元注解
JAVA為了開(kāi)發(fā)者能夠靈活定義自己的注解,因此在java.lang.annotation包中提供了4種元注解,用來(lái)注解其它注解。
查看官方文檔對(duì)這4種元注解的介紹:
1.@Retention
@Retention annotation specifies how the marked annotation is stored:* RetentionPolicy.SOURCE – The marked annotation is retained only in the source level and is ignored by the compiler.* RetentionPolicy.CLASS – The marked annotation is retained by the compiler at compile time, but is ignored by the Java Virtual Machine (JVM).* RetentionPolicy.RUNTIME – The marked annotation is retained by the JVM so it can be used by the runtime environment.
翻譯:指定標(biāo)記的注解存儲(chǔ)范圍。可選范圍是原文件、class文件、運(yùn)行期。
2.@Documented
@Documented annotation indicates that whenever the specified annotation is used those elements should be documented using the Javadoc tool. (By default, annotations are not included in Javadoc.) For more information, see the Javadoc tools page.
翻譯:因?yàn)樽⒔饽J(rèn)是不會(huì)被JavaDoc工具處理的,因此@Documented用來(lái)要求注解能被JavaDoc工具處理并生成到API文檔中 。
3.@Target
@Target annotation marks another annotation to restrict what kind of Java elements the annotation can be applied to. A target annotation specifies one of the following element types as its value:* ElementType.ANNOTATION_TYPE can be applied to an annotation type.* ElementType.CONSTRUCTOR can be applied to a constructor.* ElementType.FIELD can be applied to a field or property.* ElementType.LOCAL_VARIABLE can be applied to a local variable.* ElementType.METHOD can be applied to a method-level annotation.* ElementType.PACKAGE can be applied to a package declaration.* ElementType.PARAMETER can be applied to the parameters of a method.* ElementType.TYPE can be applied to any element of a class.
翻譯:用來(lái)標(biāo)識(shí)注解的應(yīng)用范圍。可選的范圍是注解、構(gòu)造函數(shù)、類(lèi)屬性、局部變量、包、參數(shù)、類(lèi)的任意元素。
4.@Inherited
@Inherited annotation indicates that the annotation type can be inherited from the super class. (This is not true by default.) When the user queries the annotation type and the class has no annotation for this type, the class’ superclass is queried for the annotation type. This annotation applies only to class declarations.
翻譯:默認(rèn)情況下注解不會(huì)被子類(lèi)繼承,被@Inherited標(biāo)示的注解可以被子類(lèi)繼承。
上面就是對(duì)4種元注解的介紹,其實(shí)大部分同學(xué)都知道,這里只是一起做個(gè)回顧,接下來(lái)進(jìn)入正體。
2.3 @Controller介紹
查看官方文檔:
Indicates that an annotated class is a “Controller” (e.g. a web controller).This annotation serves as a specialization of @Component, allowing for implementation classes to be autodetected through classpath scanning. It is typically used in combination with annotated handler methods based on the RequestMapping annotation.
翻譯:@Controller注解用來(lái)標(biāo)明一個(gè)類(lèi)是Controller,使用該注解的類(lèi)可以在掃描過(guò)程中被檢測(cè)到。通常@Controller和@RequestMapping注解一起使用來(lái)創(chuàng)建handler函數(shù)。
我們?cè)趤?lái)看看源碼,在org.springframework.stereotype包下找到Controller類(lèi)。
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @interface Controller { String value() default “”;}
可以看到Controller聲明為注解類(lèi)型,類(lèi)上的@Target({ElementType.TYPE}) 注解表明@Controller可以用到任意元素上,@Retention(RetentionPolicy.RUNTIME)表明注解可以保存到運(yùn)行期,@Documented表明注解可以被生成到API文檔里。
除定義的幾個(gè)元注解外我們還看到有個(gè)@Component注解,這個(gè)注解是干什么的呢?
查看官方文檔:
Indicates that an annotated class is a “component”. Such classes are considered as candidates for auto-detection when using annotation-based configuration and classpath scanning.
翻譯:被@Component注解標(biāo)注的類(lèi)代表該類(lèi)為一個(gè)component,被標(biāo)注的類(lèi)可以在包掃描過(guò)程中被檢測(cè)到。
再看源碼:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Component { String value() default “”;}
可以看到@Component注解可以用在任意類(lèi)型上,保留在運(yùn)行期,能生成到API文檔中。
再回到@Controller注解,正是因?yàn)锧Controller被@Component標(biāo)注,因此被@Controller標(biāo)注的類(lèi)也能在類(lèi)掃描的過(guò)程中被發(fā)現(xiàn)并注冊(cè)。
另外Spring中還用@Service和@Repositor注解定義bean,@Service用來(lái)聲明service類(lèi),@Repository用來(lái)聲明DAO累。
其源碼如下:
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @interface Service { String value() default “”;}@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @interface Repository { String value() default “”;}
2.4 源碼剖析
鋪墊都結(jié)束了,現(xiàn)在開(kāi)始重頭戲。
和元素一樣, 也屬于自定義命名空間,對(duì)應(yīng)的解析器是ComponentScanBeanDefinitionParser。
自定義命名空間的解析過(guò)程可以參考上篇,此處不再介紹。
我們進(jìn)入CommponentScanBeanDefinitionParser類(lèi)的parse()方法。
@Overridepublic BeanDefinition parse(Element element, ParserContext parserContext) { //此處 BASE_PACKAGE_ATTRIBUTE = “base-package”; //1.獲取要掃描的包 String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); //此處CONFIG_LOCATION_DELIMITERS = “,; “, //把,或者;分割符分割的包放到數(shù)組里面 String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); //2.創(chuàng)建掃描器 ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); //3.掃描包并注冊(cè)bean Set beanDefinitions = scanner.doScan(basePackages); return null;}
上面掃描注冊(cè)過(guò)程可以分為3步。
(1)獲取要掃描的包。
(2)創(chuàng)建掃描器。
(3)掃描包并注冊(cè)bean。
第1步邏輯比較簡(jiǎn)單,就是單純的讀取配置文件的”base-package”屬性得到要掃描的包列表。
我們從第2步開(kāi)始分析。
2.4.1 創(chuàng)建掃描器
進(jìn)入configureScanner方法()。
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) { //useDefaultFilters默認(rèn)為true,即掃描所有類(lèi)型bean boolean useDefaultFilters = true; //1.此處USE_DEFAULT_FILTERS_ATTRIBUTE = “use-default-filters”,獲取其XML中設(shè)置的值 if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) { useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)); } //2.創(chuàng)建掃描器 ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters); //3.解析過(guò)濾類(lèi)型 parseTypeFilters(element, scanner, parserContext); //4.返回掃描器 return scanner;}
創(chuàng)建掃描器的方法分為4步。
(1)獲取掃描類(lèi)范圍。
(2)根據(jù)掃描范圍初始化掃描器。
(3)設(shè)置掃描類(lèi)的過(guò)濾器。
(4)返回創(chuàng)建的掃描器。
第1步也比較簡(jiǎn)單,從配置文件中獲得“use-default-filters”屬性的值,默認(rèn)是true,即掃描所有類(lèi)型的注解。
我們進(jìn)入第2步的createScanner()方法,看看如何創(chuàng)建掃描器。
protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) { //新建一個(gè)掃描器 return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters, readerContext.getEnvironment(),readerContext.getResourceLoader());}
沿調(diào)用棧進(jìn)入ClassPathBeanDefinitionScanner()方法。
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, //如果useDefaultFilters為true,注冊(cè)默認(rèn)過(guò)濾器 if (useDefaultFilters) { //注冊(cè)默認(rèn)過(guò)濾器 registerDefaultFilters(); }}
進(jìn)入registerDefaultFilters()方法。
protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class));}
可以看到上面方法把Component注解類(lèi)型加入到了掃描白名單中,因此被@Component標(biāo)注的類(lèi)都會(huì)被掃描注冊(cè)。
在此,大家也明白為什么@Controller、@service、@Repository標(biāo)注的類(lèi)會(huì)被注冊(cè)了吧,因?yàn)檫@些注解都用@Component標(biāo)注了。
我們?cè)龠M(jìn)入第3步的parseTypeFilters()方法,看如何設(shè)置過(guò)濾器。
protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) { //解析exclude-filter和include-filter元素 //獲取元素所有子節(jié)點(diǎn) NodeList nodeList = element.getChildNodes(); //遍歷元素子節(jié)點(diǎn) for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { String localName = parserContext.getDelegate().getLocalName(node); //解析include-filter元素 ,此處 INCLUDE_FILTER_ELEMENT = "include-filter" if (INCLUDE_FILTER_ELEMENT.equals(localName)) { //創(chuàng)建類(lèi)型過(guò)濾器 TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext); //把解析出來(lái)的類(lèi)型加入白名單 scanner.addIncludeFilter(typeFilter); } //解析exclude-filter元素,此處EXCLUDE_FILTER_ELEMENT = "exclude-filter" else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) { //創(chuàng)建類(lèi)型過(guò)濾器 TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext); //把解析出來(lái)的類(lèi)型加入黑名單 scanner.addExcludeFilter(typeFilter); } }}
進(jìn)入createTypeFilter()方法查看實(shí)現(xiàn)邏輯。
protected TypeFilter createTypeFilter(Element element, ClassLoader classLoader, ParserContext parserContext) { //獲取xml中type屬性值,此處FILTER_TYPE_ATTRIBUTE = “type” String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE); //獲取xml中expression屬性值,此處FILTER_EXPRESSION_ATTRIBUTE = “expression”,獲取xml中該屬性值 String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE); expression = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(expression); //如果是注解類(lèi)型,創(chuàng)建注解類(lèi)型過(guò)濾器,并把需要過(guò)濾的注解類(lèi)設(shè)置進(jìn)去 if (“annotation”.equals(filterType)) { return new AnnotationTypeFilter((Class) ClassUtils.forName(expression, classLoader)); }}
上面就是創(chuàng)建掃描器的過(guò)程,主要是將XML文件中設(shè)置的類(lèi)型添加到白名單和黑名單中。
2.4.2 掃描注冊(cè)bean
得到掃描器后,開(kāi)始掃描注冊(cè)流程。
進(jìn)入doScan()方法。
protected Set doScan(String… basePackages) { Set beanDefinitions = new LinkedHashSet(); //遍歷所有需要掃描的包 for (String basePackage : basePackages) { //1.在該包中找出用@Component注解的類(lèi),放到候選列表中 Set candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { //2.判斷容器中是否已經(jīng)有bean信息,如果沒(méi)有就注冊(cè) if (checkCandidate(beanName, candidate)) { //生成bean信息 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); //添加bean信息到bean定義列表中 beanDefinitions.add(definitionHolder); //3.把bean注冊(cè)到IOC容器中 registerBeanDefinition(definitionHolder, this.registry); } }}
掃描注冊(cè)過(guò)程分為3步。
(1)從包中找出需要注冊(cè)的bean并放到候選列表中。
(2)遍歷候選列表中的所有bean,判斷容器中是否已經(jīng)存在bean。
(3)如果不存在bean,就把bean信息注冊(cè)到容器中。
接下來(lái)依次分析上面掃描注冊(cè)流程。
2.4.2.1 查找候選bean
我們先看第1步,查找候選bean的過(guò)程。進(jìn)入findCandidateComponents()方法。
public Set findCandidateComponents(String basePackage) { Set candidates = new LinkedHashSet(); //1.獲取包的classpath String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + ‘/’ + this.resourcePattern; //2.把包下的所有class解析成resource資源 Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); //遍歷所有類(lèi)resource for (Resource resource : resources) { if (resource.isReadable()) { //3.獲取類(lèi)的元信息 MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); //4.判斷是否候選component if (isCandidateComponent(metadataReader)) { //5.根據(jù)類(lèi)元信息生成beanDefinition ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); //6.判斷該bean是否能實(shí)例化 if (isCandidateComponent(sbd)) { //7.加入候選類(lèi)列表 candidates.add(sbd); } //8.返回候選components選列表 return candidates;}
查找bean的流程比較繁瑣,可以分為以下8步。
(1)獲取包掃描路徑。
(2)把包路徑下的所有類(lèi)解析成resource類(lèi)。
(3)解析resource類(lèi),獲取類(lèi)的元信息。
(4)根據(jù)類(lèi)元信息判斷該類(lèi)是否在白名單中。
(5)如果在白名單中,生成beanDefinition信息。
(6)根據(jù)beanDefinition信息判斷類(lèi)是否能實(shí)例化。
(7)如果可以實(shí)例化,將beanDefinition信息加入到候選列表中。
(8)返回保存beanDefinition信息的候選列表。
還記得BeanDefinition是什么吧,主要是保存bean的信息。如果不記得看看Spring注冊(cè)流程。
因?yàn)槠渌壿嫳容^簡(jiǎn)單,在此我們重點(diǎn)分析第4步和第6步。
先看第4步,進(jìn)入isCandidateComponent()方法。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { //1.遍歷黑名單,若傳入的類(lèi)元信息在黑名單中返回false for (TypeFilter tf : this.excludeFilters) { //判斷是否和傳入的類(lèi)匹配 if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } //2.遍歷白名單,若傳入的類(lèi)元信息在白名單中返回true for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { //根據(jù)@Conditional注解判斷是否注冊(cè)bean,如果沒(méi)有@Conditional注解,返回true. return isConditionMatch(metadataReader); } } return false;}
可以看到上面主要邏輯是判斷該類(lèi)是否在白名單或黑名單列表中,如果在白名單,則返回true,在黑名單返回false。黑、白名單的值就是創(chuàng)建掃描流程中通過(guò)parseTypeFilters()方法設(shè)置進(jìn)去的。
再稍微提一下上面@Conditional注解,此注解是Spring 4中加入的,作用是根據(jù)設(shè)置的條件來(lái)判斷要不要注冊(cè)bean,如果沒(méi)有標(biāo)注該注解,默認(rèn)注冊(cè)。我們?cè)谶@里不展開(kāi)細(xì)說(shuō),有興趣的同學(xué)可以自己查閱相關(guān)資料。
我們?cè)倏吹?步,進(jìn)入isCandidateComponent()方法。
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { //獲取元類(lèi)信息 AnnotationMetadata metadata = beanDefinition.getMetadata(); //判斷是否可以實(shí)例化 return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));}
可以看到上面是根據(jù)該類(lèi)是不是接口、抽象類(lèi)、嵌套類(lèi)等信息來(lái)判斷能否實(shí)例化的。
2.4.2.2 判斷bean是否已經(jīng)注冊(cè)
候選bean列表信息已經(jīng)得到,再看看如何對(duì)列表中的bean做進(jìn)一步判斷。
進(jìn)入checkCandiates()方法。
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) { if (!this.registry.containsBeanDefinition(beanName)) { return true; } return false;}
上面方法比較簡(jiǎn)單,主要是查看容器中是否已經(jīng)有bean的定義信息。
2.4.2.3 注冊(cè)bean
對(duì)bean信息判斷完成后,如果bean有效,就開(kāi)始注冊(cè)bean。
進(jìn)入registerBeanDefinition()方法。
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);}
再進(jìn)入registerBeanDefinition()方法。
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { //得到beanname String beanName = definitionHolder.getBeanName(); //注冊(cè)bean信息 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); //注冊(cè)bean的別名 String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); }}
上面流程大家有沒(méi)有似曾相識(shí),和Spring解析注冊(cè)流程文中注冊(cè)bean的邏輯一樣。
到此就完成了掃描注冊(cè)bean流程的分析。接下來(lái)就是bean的實(shí)例化等流程,大家同樣可以參考Spring解析注冊(cè)流程一文。
3.小結(jié)
看完上面的分析,相信大家對(duì)有了深入的了解。
現(xiàn)在回到開(kāi)頭的那段代碼。會(huì)不會(huì)有“誠(chéng)不我欺也”的感覺(jué)。
最后,我再把那段代碼貼出來(lái),大家對(duì)著代碼在腦海里想象一下其解析流程,檢驗(yàn)一下掌握程度。
如果有哪一步卡住了,建議再回頭看看我的文章,直至能在腦海中有一個(gè)完整的流程圖,甚至能想到對(duì)應(yīng)的源代碼段。
如果能做到這樣,說(shuō)明你真正理解了,接下來(lái)就可以愉快的和小伙伴炫技或者和面試官去侃大山了。
spring-mvc.xml
spring.xml
本文完。
推薦閱讀: