我们在 SpringMVC 开发项目中,有的用注解和 XML 配置 Bean, 这两种都各有自己的优势,数据源配置比较经常用 XML 配置,控制层依赖的 service 比较经常用注解等(在部署时比较不会改变的),我们经常比较常用的注解有 @Component 是通用标注, @Controller 标注 web 控制器, @Service 标注 Servicec 层的服务, @Respository 标注 DAO 层的数据访问。 SpringMVC启动时怎么被自动扫描然后解析并注册到 Bean 工厂中去(放到 DefaultListableBeanFactory 中的Map<String, BeanDefinition> beanDefinitionMap 中 以 BeanName 为 key )?我们今天带着这些问题来了解分析这实现的过程,我们在分析之前先了解一下这些注解。
@Controller 标注 web 控制器, @Service 标注 Service 层的服务, @Respository 标注 DAO层的数据访问。 @Component 是通用标注,只是定义为一个类为 Bean , SpringMVC 会把所有添加 @Component 注解的类作为使用自动扫描注入配置路径下的备选对象。 @Controller 、 @Service\@Respository 只是更加的细化,都是被 @Component 标注,所以我们比较不推荐使用 @Component 。源代码如下:
@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
我们在配置文件中,标示配置需要扫描哪些包下,也可以配置对某个包下不扫描,代码如下:
<context:component-scan base-package="cn.test"> <context:include-filter type="regex" expression="cn.test.*.*.controller" /> <context:exclude-filter type="regex" expression="cn.test.*.*.controller2"/> </context:component-scan>
说明:
<context:exclude-filter>指定的不扫描,<context:include-filter>指定的扫描
SpringMVC 先读取配置文件,然后根据 context:component-scan 中属性 base-package 去扫描指定包下的 class 和 jar 文件,把标示 @Controller 标注 web 控制器, @Service 标注 Servicec层的服务, @Respository 标注 DAO 层的数据访问等注解的都获取,并注册为 Bean 类放到 Bean 工厂,我们接下来要分析的这个过程。我们平时项目开发都是这样的注解,实现 MVC 模式,代码如下:
例如: //控制层 @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 来解析。源代码如下:
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<String, BeanDefinitionParser> ,然后通过 localName 获取,源代码如下:
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 类是解析配置文件中的 <context:component-scan>,<aop:config> 等标签,但是不同的标签是由不同的 BeanDefinitionParser来进行解析的,如图所示:
接下来我们开始解析这个标签, <context:component-scan> 标签的解析是由 ComponentScanBeanDefinitionParser 类解析的,接下来我们要分析它怎么解析注解的 Bean ,并把 Bean 注册到Bean 工厂,源代码如下:
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; }
说明:
( 1 )获取 context:component-scan 配置的属性 base-package 的值,然后放到数组。
( 2 )创建扫描对应包下的 class 和 jar 文件的对象 ClassPathBeanDefinitionScanner ,由这个类来实现扫描包下的 class 和 jar 文件并把注解的 Bean 包装成 BeanDefinition 。
( 3 ) BeanDefinition 注册到 Bean 工厂。
第一:扫描是由 ComponentScanBeanDefinitionParser 的 doScan 方法来实现的,源代码如下:
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 是 ComponentScanBeanDefinitionParser 由父类 ClassPathScanningCandidateComponentProvider 的方法 findCandidateComponents 实现的,源代码如下:
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; }
说明:
(1) 先根据 context:component-scan 中属性的 base-package="cn.test" 配置转换为 classpath*:cn/test/**/*.class ,并扫描对应下的 class 和 jar 文件并获取类对应的路径,返回 Resources
(2) 根据 <context:exclude-filter> 指定的不扫描包, <context:exclude-filter> 指定的扫描包配置进行过滤不包含的包对应下的 class 和 jar。
( 3 )封装成 BeanDefinition 放到队列里。
1 )怎么根据 packageSearchPath 获取包对应下的 class 路径,是通过 PathMatchingResourcePatternResolver 类, findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); 获取配置包下的 class 路径并封装成 Resource ,实现也是 getClassLoader().getResources(path); 实现的。源代码如下:
<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()]); } </span>
说明: getClassLoader().getResources 获取 classpath*:cn/test/**/*.class 下的 cn/test 包下的 class 的路径信息。并返回了 URL 。这里能把对应 class 路径获取到了,就能获取里面的信息。
2 ) isCandidateComponent 实现的标签是里配置的 <context:exclude-filter> 指定的不扫描包,<context:exclude-filter> 指定的扫描包的过滤,源代码如下:
<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 属性,值是就是 <context:exclude-filter type="regex" expression="cn.test.*.*.controller"/> 的 cn.test.*.*.controller 值 this.pattern.matcher(metadata.getClassName()).matches(); 通过这个去匹配,如果是就返回 false 。如图所示:
我们到这边已经把对应的通过在 XML 配置把注解扫描解析并封装成 BeanDefinition 。
接下来我们来分析一下注册到 Bean 工厂,大家还记得 ComponentScanBeanDefinitionParser的 doScan 方法,然后到工厂的是由 registerBeanDefinition(definitionHolder, this.registry); 实现的,源代码如下:
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<String, BeanDefinition> 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 文件,获取对应的路径信息,然后根据配置 <context:exclude-filter> 指定的扫描包配置进行过滤不包含的包对应下的 class 和 jar路径的 Resources。
(3) 把标示 @Controller 标注 web 控制器, @Service 标注 Servicec 层的服务, @Respository 标注 DAO 层的数据访问等注解路径都获取包装成 BeanDefinition ,并注册为 Bean 类放到 Bean 工厂,也就是 DefaultListableBeanFactory Map<String, BeanDefinition> beanDefinitionMap 中 以 BeanName 为 key。