SpringMVC源代码深度解析(扫描和注册的注解Bean)

我们在 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。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值