SpringMVC 源代码深度解析<context:component-scan>(扫描和注册的注解Bean)

来源:互联网 发布:seo综合查询工具 编辑:程序博客网 时间:2024/06/03 05:06
    

    我们在SpringMVC开发项目中,有的用注解和XML配置Bean,这两种都各有自己的优势,数据源配置比较经常用XML配置,控制层依赖的service比较经常用注解等(在部署时比较不会改变的),我们经常比较常用的注解有@Component是通用标注,@Controller标注web控制器,@Service标注Servicec层的服务,@Respository标注DAO层的数据访问。SpringMVC启动时怎么被自动扫描然后解析并注册到Bean工厂中去(放到DefaultListableBeanFactory中的Map<String, BeanDefinition> beanDefinitionMap中 以BeanNamekey)?我们今天带着这些问题来了解分析这实现的过程,我们在分析之前先了解一下这些注解。

   @Controller标注web控制器,@Service标注Service层的服务,@Respository标注DAO层的数据访问。@Component是通用标注,只是定义为一个类为BeanSpringMVC会把所有添加@Component注解的类作为使用自动扫描注入配置路径下的备选对象。@Controller@Service\@Respository只是更加的细化,都是被@Component标注,所以我们比较不推荐使用@Component。源代码如下:

  

[java] view plain copy
print?
  1. @Target({ElementType.TYPE})  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Documented  
  4. @Component  
  5. public @interface Service {  
  6.     String value() default "";  
  7. }  
  8.   
  9. @Target({ElementType.TYPE})  
  10. @Retention(RetentionPolicy.RUNTIME)  
  11. @Documented  
  12. @Component  
  13. public @interface Controller {  
  14.     String value() default "";  
  15. }  
  16.   
  17. @Target({ElementType.TYPE})  
  18. @Retention(RetentionPolicy.RUNTIME)  
  19. @Documented  
  20. @Component  
  21. public @interface Repository {  
  22.     String value() default "";  
  23. }  

  都是有标示@Component

  我们在配置文件中,标示配置需要扫描哪些包下,也可以配置对某个包下不扫描,代码如下:

[java] view plain copy
print?
  1. <context:component-scan base-package="cn.test">  
  2.         <context:exclude-filter type="regex" expression="cn.test.*.*.controller"/>  
  3.         <context:exclude-filter type="regex" expression="cn.test.*.*.controller2"/>  
  4. </context:component-scan>  

说明:

   <context:exclude-filter>指定的不扫描包,<context:exclude-filter>指定的扫描包

SpringMVC先读取配置文件,然后根据context:component-scan中属性base-package去扫描指定包下的classjar文件,把标示@Controller标注web控制器,@Service标注Servicec层的服务,@Respository标注DAO层的数据访问等注解的都获取,并注册为Bean类放到Bean工厂,我们接下来要分析的这个过程。我们平时项目开发都是这样的注解,实现MVC模式,代码如下:

[java] view plain copy
print?
  1. 例如:  
  2. //控制层  
  3. @Controller  
  4. @RequestMapping(value="/test")  
  5. public class TestController2 {  
  6.     @Autowired  
  7.     private TestService testService;  
  8.     @RequestMapping(value="/index")  
  9.     public String getIndex(Model model){  
  10.           
  11.         return "";  
  12.     }  
  13. }  
  14.   
  15. //服务层  
  16. @Service("testService")  
  17. public class TestServiceImpl implements  TestService{  
  18. }  

  我们今天的入口点就在这,因为解析注解的到注册,也是先读取配置文件并解析,在解析时扫描对应包下的Java类,里面有DefaultBeanDefinitionDocumentReader这个类,doRegisterBeanDefinitions这个方法实现解析配置文件的Bean,这边已经读取进来形成Document 形式存储,然后开始解析Bean,是由BeanDefinitionParserDelegate类实现的,BeanDefinitionParserDelegate完成具体Bean的解析(例如:bean标签、import标签等)这个在上一篇SpringMVC 源代码深度解析 IOC容器(Bean 解析、注册)里有解析,今天注解属于扩展的标签,是由NamespaceHandlerBeanDefinitionParser来解析。源代码如下:

 
[java] view plain copy
print?
  1. public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {  
  2.         String namespaceUri = getNamespaceURI(ele);  
  3.         NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);  
  4.         if (handler == null) {  
  5.             error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);  
  6.             return null;  
  7.         }  
  8.         return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));  
  9.     }  

  NamespaceHandler这边这边起到了什么作用,根据不同的Namespace获取不同的NamespaceHandler,因为我们在Beans标签配置了命名空间,然后就可以配置对应的标签,解析标签时,比较有自己的所实现的NamespaceHandler来解析,如图所示:



 

   NamespaceHandler中的parse方法是它的子类类NamespaceHandlerSupport实现的,获取通过findParserForElement方法获取BeanDefinitionParser 对象,这个对象在工程初始化时就直接实例化放在缓存中Map<String, BeanDefinitionParser>,然后通过localName获取,源代码如下:

[java] view plain copy
print?
  1. public BeanDefinition parse(Element element, ParserContext parserContext) {  
  2.     return findParserForElement(element, parserContext).parse(element, parserContext);  
  3. }  
  4. private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {  
  5.     String localName = parserContext.getDelegate().getLocalName(element);  
  6.     BeanDefinitionParser parser = this.parsers.get(localName);  
  7.     if (parser == null) {  
  8.         parserContext.getReaderContext().fatal(  
  9.                 "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);  
  10.     }  
  11.     return parser;  
  12. }  

  为什么要获取BeanDefinitionParser ,因为BeanDefinitionParser 类是解析配置文件中的<context:component-scan>,<aop:config>等标签,但是不同的标签是由不同的BeanDefinitionParser来进行解析的,如图所示:


  


   接下来我们开始解析这个标签, <context:component-scan>标签的解析是由ComponentScanBeanDefinitionParser类解析的,接下来我们要分析它怎么解析注解的Bean,并把Bean注册到Bean工厂,源代码如下:

  

[java] view plain copy
print?
  1. public BeanDefinition parse(Element element, ParserContext parserContext) {  
  2.        //获取context:component-scan 配置的属性base-package的值  
  3.     String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),  
  4.             ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);  
  5.        //创建扫描对应包下的class文件的对象  
  6.     ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);  
  7.        //扫描对应包下的class文件并有注解的Bean包装成BeanDefinition  
  8.     Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);  
  9.     registerComponents(parserContext.getReaderContext(), beanDefinitions, element);  
  10.     return null;  
  11. }  

说明:

    (1)获取context:component-scan 配置的属性base-package的值,然后放到数组。

    (2)创建扫描对应包下的classjar文件的对象ClassPathBeanDefinitionScanner ,由这个类来实现扫描包下的classjar文件并把注解的Bean包装成BeanDefinition

    (3BeanDefinition注册到Bean工厂。

 

 第一:扫描是由ComponentScanBeanDefinitionParserdoScan方法来实现的,源代码如下:

   
[java] view plain copy
print?
  1. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {  
  2.        //新建队列来保存BeanDefinitionHolder  
  3.     Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();  
  4.        //循环需要扫描的包  
  5.     for (String basePackage : basePackages) {  
  6.            //进行扫描注解并包装成BeanDefinition  
  7.         Set<BeanDefinition> candidates = findCandidateComponents(basePackage);  
  8.         for (BeanDefinition candidate : candidates) {  
  9.             ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);  
  10.             candidate.setScope(scopeMetadata.getScopeName());  
  11.             String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);  
  12.             if (candidate instanceof AbstractBeanDefinition) {  
  13.                 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);  
  14.             }  
  15.             if (candidate instanceof AnnotatedBeanDefinition) {  
  16.                 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);  
  17.             }  
  18.             if (checkCandidate(beanName, candidate)) {  
  19.                 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);  
  20.                 definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);  
  21.                 beanDefinitions.add(definitionHolder);  
  22.                    //对BeanDefinition进行注册  
  23.                 registerBeanDefinition(definitionHolder, this.registry);  
  24.             }  
  25.         }  
  26.     }  
  27.     return beanDefinitions;  
  28. }  
  

   进行扫描注解并包装成BeanDefinitionComponentScanBeanDefinitionParser由父类ClassPathScanningCandidateComponentProvider的方法findCandidateComponents实现的,源代码如下:

    
[java] view plain copy
print?
  1. public Set<BeanDefinition> findCandidateComponents(String basePackage) {  
  2.     Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();  
  3.     try {  
  4.           //base-package中的值替换为classpath*:cn/test/**/*.class  
  5.         String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +  
  6.                 resolveBasePackage(basePackage) + "/" + this.resourcePattern;  
  7.            //获取所以base-package下的资源  
  8.         Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);  
  9.            boolean traceEnabled = logger.isTraceEnabled();  
  10.         boolean debugEnabled = logger.isDebugEnabled();  
  11.         for (Resource resource : resources) {  
  12.             if (traceEnabled) {  
  13.                 logger.trace("Scanning " + resource);  
  14.             }  
  15.             if (resource.isReadable()) {  
  16.                 try {  
  17.                     MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);  
  18.                         //对context:exclude-filter进行过滤  
  19.                     if (isCandidateComponent(metadataReader)) {  
  20.                           //包装BeanDefinition  
  21.                         ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);  
  22.                         sbd.setResource(resource);  
  23.                         sbd.setSource(resource);  
  24.                         if (isCandidateComponent(sbd)) {  
  25.                             if (debugEnabled) {  
  26.                                 logger.debug("Identified candidate component class: " + resource);  
  27.                             }  
  28.                             candidates.add(sbd);  
  29.                         }  
  30.                         else {  
  31.                             if (debugEnabled) {  
  32.                                 logger.debug("Ignored because not a concrete top-level class: " + resource);  
  33.                             }  
  34.                         }  
  35.                     }  
  36.                     else {  
  37.                         if (traceEnabled) {  
  38.                             logger.trace("Ignored because not matching any filter: " + resource);  
  39.                         }  
  40.                     }  
  41.                 }  
  42.                 catch (Throwable ex) {  
  43.                     throw new BeanDefinitionStoreException(  
  44.                             "Failed to read candidate component class: " + resource, ex);  
  45.                 }  
  46.             }  
  47.             else {  
  48.                 if (traceEnabled) {  
  49.                     logger.trace("Ignored because not readable: " + resource);  
  50.                 }  
  51.             }  
  52.         }  
  53.     }  
  54.     catch (IOException ex) {  
  55.         throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);  
  56.     }  
  57.       return candidates;  
  58. }  

说明:

     (1)先根据context:component-scan 中属性的base-package="cn.test"配置转换为classpath*:cn/test/**/*.class,并扫描对应下的classjar文件并获取类对应的路径,返回Resources

     (2)根据<context:exclude-filter>指定的不扫描包,<context:exclude-filter>指定的扫描包配置进行过滤不包含的包对应下的classjar。

    (3)封装成BeanDefinition放到队列里。

   

   1)怎么根据packageSearchPath获取包对应下的class路径,是通过PathMatchingResourcePatternResolver类,findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));获取配置包下的class路径并封装成Resource,实现也是getClassLoader().getResources(path);实现的。源代码如下:

       

[java] view plain copy
print?
  1. <span style="font-size:18px;">public Resource[] getResources(String locationPattern) throws IOException {  
  2.         Assert.notNull(locationPattern, "Location pattern must not be null");  
  3.         if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {  
  4.             // a class path resource (multiple resources for same name possible)  
  5.             if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {  
  6.                 // a class path resource pattern  
  7.                 return findPathMatchingResources(locationPattern);  
  8.             }  
  9.             else {  
  10.                 // all class path resources with the given name  
  11.                 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));  
  12.             }  
  13.         }  
  14.         else {  
  15.             // Only look for a pattern after a prefix here  
  16.             // (to not get fooled by a pattern symbol in a strange prefix).  
  17.             int prefixEnd = locationPattern.indexOf(":") + 1;  
  18.             if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {  
  19.                 // a file pattern  
  20.                 return findPathMatchingResources(locationPattern);  
  21.             }  
  22.             else {  
  23.                 // a single resource with the given name  
  24.                 return new Resource[] {getResourceLoader().getResource(locationPattern)};  
  25.             }  
  26.         }  
  27.     }  
  28.   
  29. protected Resource[] findAllClassPathResources(String location) throws IOException {  
  30.         String path = location;  
  31.         if (path.startsWith("/")) {  
  32.             path = path.substring(1);  
  33.         }  
  34.         Enumeration<URL> resourceUrls = getClassLoader().getResources(path);  
  35.         Set<Resource> result = new LinkedHashSet<Resource>(16);  
  36.         while (resourceUrls.hasMoreElements()) {  
  37.             URL url = resourceUrls.nextElement();  
  38.             result.add(convertClassLoaderURL(url));  
  39.         }  
  40.         return result.toArray(new Resource[result.size()]);  
  41.     }  
  42. </span>  

    说明:getClassLoader().getResources获取classpath*:cn/test/**/*.class下的cn/test包下的class的路径信息。并返回了URL。这里能把对应class路径获取到了,就能获取里面的信息。

     

  2isCandidateComponent实现的标签是里配置的<context:exclude-filter>指定的不扫描包,<context:exclude-filter>指定的扫描包的过滤,源代码如下:

      

[java] view plain copy
print?
  1. <span style="font-size:18px;">protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {  
  2.         for (TypeFilter tf : this.excludeFilters) {  
  3.             if (tf.match(metadataReader, this.metadataReaderFactory)) {  
  4.                 return false;  
  5.             }  
  6.         }  
  7.         for (TypeFilter tf : this.includeFilters) {  
  8.             if (tf.match(metadataReader, this.metadataReaderFactory)) {  
  9.                 AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();  
  10.                 if (!metadata.isAnnotated(Profile.class.getName())) {  
  11.                     return true;  
  12.                 }  
  13.                 AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);  
  14.                 return this.environment.acceptsProfiles(profile.getStringArray("value"));  
  15.             }  
  16.         }  
  17.         return false;  
  18.     }</span>  

说明: this.excludeFilterspattern属性,值是就是<context:exclude-filter type="regex" expression="cn.test.*.*.controller"/>cn.test.*.*.controllerthis.pattern.matcher(metadata.getClassName()).matches();通过这个去匹配,如果是就返回false。如图所示:

  


我们到这边已经把对应的通过在XML配置把注解扫描解析并封装成BeanDefinition

   接下来我们来分析一下注册到Bean工厂,大家还记得ComponentScanBeanDefinitionParserdoScan方法,然后到工厂的是由registerBeanDefinition(definitionHolder, this.registry);实现的,源代码如下:

   
[java] view plain copy
print?
  1. protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {  
  2.     BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);  
  3. }  
  4.   
  5.  public static void registerBeanDefinition(  
  6.         BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)  
  7.         throws BeanDefinitionStoreException {  
  8.   
  9.     // Register bean definition under primary name.  
  10.     String beanName = definitionHolder.getBeanName();  
  11.     registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());  
  12.   
  13.     // Register aliases for bean name, if any.  
  14.     String[] aliases = definitionHolder.getAliases();  
  15.     if (aliases != null) {  
  16.         for (String aliase : aliases) {  
  17.             registry.registerAlias(beanName, aliase);  
  18.         }  
  19.     }  
  20. }  
  21.   
  22. public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)  
  23.         throws BeanDefinitionStoreException {  
  24.   
  25.     Assert.hasText(beanName, "Bean name must not be empty");  
  26.     Assert.notNull(beanDefinition, "BeanDefinition must not be null");  
  27.   
  28.     if (beanDefinition instanceof AbstractBeanDefinition) {  
  29.         try {  
  30.             ((AbstractBeanDefinition) beanDefinition).validate();  
  31.         }  
  32.         catch (BeanDefinitionValidationException ex) {  
  33.             throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,  
  34.                     "Validation of bean definition failed", ex);  
  35.         }  
  36.     }  
  37.   
  38.     synchronized (this.beanDefinitionMap) {  
  39.         Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);  
  40.         if (oldBeanDefinition != null) {  
  41.             if (!this.allowBeanDefinitionOverriding) {  
  42.                 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,  
  43.                         "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +  
  44.                         "': There is already [" + oldBeanDefinition + "] bound.");  
  45.             }  
  46.             else {  
  47.                 if (this.logger.isInfoEnabled()) {  
  48.                     this.logger.info("Overriding bean definition for bean '" + beanName +  
  49.                             "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");  
  50.                 }  
  51.             }  
  52.         }  
  53.         else {  
  54.             this.beanDefinitionNames.add(beanName);  
  55.             this.frozenBeanDefinitionNames = null;  
  56.         }  
  57.         this.beanDefinitionMap.put(beanName, beanDefinition);  
  58.     }  
  59.   
  60.     resetBeanDefinition(beanName);  
  61. }  

说明:DefaultListableBeanFactory要实现的保存到Map<String, BeanDefinition> beanDefinitionMap中 以BeanNamekey,如果有,就不用保存了。DefaultListableBeanFactory我们在上一篇SpringMVC 源代码深度解析 IOC容器(Bean 解析、注册)有介绍过了,DefaultListableBeanFactory继承了BeanFactory。


总结:

     (1)因为解析注解的到注册,也是先读取配置文件并解析,在解析时扫描对应包下的JAVA类,里面有DefaultBeanDefinitionDocumentReader这个类,doRegisterBeanDefinitions这个方法实现解析配置文件的Bean,这边已经读取进来形成Document 形式存储。然后注解属于扩展的标签,是由NamespaceHandlerBeanDefinitionParser来解析。

   (2)根据context:component-scan中属性base-package去扫描指定包下的classjar文件,获取对应的路径信息,然后根据配置<context:exclude-filter>指定的扫描包配置进行过滤不包含的包对应下的classjar路径的Resources。

     (3)把标示@Controller标注web控制器,@Service标注Servicec层的服务,@Respository标注DAO层的数据访问等注解路径都获取包装成BeanDefinition,并注册为Bean类放到Bean工厂,也就是DefaultListableBeanFactoryMap<String, BeanDefinition> beanDefinitionMapBeanNamekey。



    



 

阅读全文
0 0
原创粉丝点击