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

转自:http://www.2cto.com/kf/201411/349920.html

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

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

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

都是有标示@Component

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

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

说明:

指定的不扫描包,指定的扫描包

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

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

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

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

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



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

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

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



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

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

说明:

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

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

(3)BeanDefinition注册到Bean工厂。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
     protected Set<beandefinitionholder> doScan(String... basePackages) {
         //新建队列来保存BeanDefinitionHolder
         Set<beandefinitionholder> beanDefinitions = new LinkedHashSet<beandefinitionholder>();
         //循环需要扫描的包
         for (String basePackage : basePackages) {
             //进行扫描注解并包装成BeanDefinition
             Set<beandefinition> candidates = findCandidateComponents(basePackage);
             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进行注册
                     registerBeanDefinition(definitionHolder, this .registry);
                 }
             }
         }
         return beanDefinitions;
     }
</beandefinition></beandefinitionholder></beandefinitionholder></beandefinitionholder>

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public Set<beandefinition> findCandidateComponents(String basePackage) {
         Set<beandefinition> candidates = new LinkedHashSet<beandefinition>();
         try {
            //base-package中的值替换为classpath*:cn/test/**/*.class
             String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                     resolveBasePackage(basePackage) + "/" + this .resourcePattern;
             //获取所以base-package下的资源
             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);
                          //对context:exclude-filter进行过滤
                         if (isCandidateComponent(metadataReader)) {
                            //包装BeanDefinition
                             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;
     }
</beandefinition></beandefinition></beandefinition>

说明:

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

(2)根据指定的不扫描包,指定的扫描包配置进行过滤不包含的包对应下的class和jar。

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

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

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

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

2)isCandidateComponent实现的标签是里配置的指定的不扫描包,指定的扫描包的过滤,源代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<span style= "font-size:18px;" > 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)) {
                 AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
                 if (!metadata.isAnnotated(Profile. class .getName())) {
                     return true ;
                 }
                 AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile. class );
                 return this .environment.acceptsProfiles(profile.getStringArray( "value" ));
             }
         }
         return false ;
     }</span>

说明: this.excludeFilters有pattern属性,值是就是的cn.test.*.*.controller值this.pattern.matcher(metadata.getClassName()).matches();通过这个去匹配,如果是就返回false。如图所示:


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

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

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

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


总结:

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

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

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


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值