zoukankan      html  css  js  c++  java
  • SpringMVC 源码深度解析<context:component-scan>(扫描和注冊的注解Bean)

        

        我们在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。源码例如以下:

      

    @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:exclude-filter type="regex" expression="cn.test.*.*.controller"/>
    		<context:exclude-filter type="regex" expression="cn.test.*.*.controller2"/>
    </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模式,代码例如以下:

    比如:
    //控制层
    @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 解析、注冊)里有解析,今天注解属于扩展的标签,是由NamespaceHandlerBeanDefinitionParser来解析。源码例如以下:

     
    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 对象,这个对象在project初始化时就直接实例化放在缓存中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)创建扫描相应包下的classjar文件的对象ClassPathBeanDefinitionScanner 。由这个类来实现扫描包下的classjar文件并把注解的Bean包装成BeanDefinition

        (3BeanDefinition注冊到Bean工厂。

     

     第一:扫描是由ComponentScanBeanDefinitionParserdoScan方法来实现的。源码例如以下:

       
    	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;
    	}
    
      

       进行扫描注解并包装成BeanDefinitionComponentScanBeanDefinitionParser由父类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。并扫描相应下的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);实现的。

    源码例如以下:

           

    <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路径获取到了。就能获取里面的信息。

         

      2isCandidateComponent实现的标签是里配置的<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.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);实现的,源码例如以下:

       
     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中 以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。



        



  • 相关阅读:
    高阶函数 练习
    斐波那契数列(Fibonacci sequence)递归函数
    顺序循环队列的基本操作(二)
    顺序循环队列基本操作(一)
    顺序栈的基本操作
    双链表的插入删除
    头插法实现链表逆置
    带头结点单链表的基本操作
    顺序表基本操作
    实现原数组列变成行,再将每一行首尾倒置
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5368093.html
Copyright © 2011-2022 走看看