我们在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: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去扫描指定包下的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 对象,这个对象在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来进行解析的,如图所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY29uZ2Nvbmc2OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY29uZ2Nvbmc2OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

接下来我们開始解析这个标签, <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。如图所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY29uZ2Nvbmc2OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

我们到这边已经把相应的通过在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工厂,也就是DefaultListableBeanFactoryMap<String, BeanDefinition> beanDefinitionMap中以BeanName为key。

SpringMVC 源码深度解析&lt;context:component-scan&gt;(扫描和注冊的注解Bean)的更多相关文章

  1. 源码深度解析SpringMvc请求运行机制(转)

    源码深度解析SpringMvc请求运行机制 本文依赖的是springmvc4.0.5.RELEASE,通过源码深度解析了解springMvc的请求运行机制.通过源码我们可以知道从客户端发送一个URL请 ...

  2. Spring源码深度解析之Spring MVC

    Spring源码深度解析之Spring MVC Spring框架提供了构建Web应用程序的全功能MVC模块.通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术,例如JavaServer ...

  3. spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理

    @Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...

  4. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  5. VueRouter 源码深度解析

    VueRouter 源码深度解析 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还 ...

  6. spring源码深度解析— IOC 之 容器的基本实现

    概述 上一篇我们搭建完Spring源码阅读环境,spring源码深度解析—Spring的整体架构和环境搭建 这篇我们开始真正的阅读Spring的源码,分析spring的源码之前我们先来简单回顾下spr ...

  7. spring源码深度解析— IOC 之 默认标签解析(上)

    概述 接前两篇文章  spring源码深度解析—Spring的整体架构和环境搭建  和  spring源码深度解析— IOC 之 容器的基本实现 本文主要研究Spring标签的解析,Spring的标签 ...

  8. spring源码深度解析— IOC 之 开启 bean 的加载

    概述 前面我们已经分析了spring对于xml配置文件的解析,将分析的信息组装成 BeanDefinition,并将其保存注册到相应的 BeanDefinitionRegistry 中.至此,Spri ...

  9. Spring源码深度解析之事务

    Spring源码深度解析之事务 目录 一.JDBC方式下的事务使用示例 (1)创建数据表结构 (2)创建对应数据表的PO (3)创建表和实体之间的映射 (4)创建数据操作接口 (5)创建数据操作接口实 ...

随机推荐

  1. 五种开源协议的比较(BSD,Apache,GPL,LGPL,MIT)(整理)

    BSD开源协议(original  BSD license.FreeBSD  license.Original  BSD license) BSD开源协议是一个给于使用者很大自由的协议.基本上使用者可 ...

  2. Selenium启动本地firefox的profile

    ProfilesIni pi = new ProfilesIni();FirefoxProfile profile = pi.getProfile("default");WebDr ...

  3. LR之脚本调试

    1.概述 2.Animated run和Non-animated run 3.调试小技巧 4.日志设置

  4. Java之--Java语言基础组成(关键字、标识符、注释、常量和变量、运算符)

    Java语言基础组成-关键字.标识符.注释.常量和变量.运算符 Java语言由8个模块构成,分别为:1.关键字:2.标识符(包名.类名.接口名.常量名.变量名等):3.注释:4.常量和变量:5.运算符 ...

  5. oracle 数据库远程导出

    exp 用户名/密码@IP:端口/数据库名 file=文件路径 full=y; exp scebm1/ebm@10.3.10.16:1521/scebm file=D:scebm20140527.dm ...

  6. Java Core 学习笔记——3.char/Unicode/代码点/代码单元

    通用字符集(UCS) UCS是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所制定的标准字符集. UCS包括了其他所有的字符集(包含了已知语言的所以字符). ISO/IEC 1 ...

  7. jszs 快速排序

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  8. 在RHEL5.4下安装ORACLE11G

    以root身份登录到系统,新增组和用户: #groupadd oinstall #groupadd dba #useradd -g oinstall -G dba oracle #passwd ora ...

  9. hibernate里createSQLQuery的addEntity()和setResultTransformer()方法

    http://langgufu.iteye.com/blog/1565397 ————————————————————————————————————————————————————————————— ...

  10. RedHat/CentOS6.4---永久关闭iptables

    今天无意中发现一个现象,当我关闭iptables并且停止iptables服务,但是总会有一些出奇的事情发生,当我再次启动系统,查看iptables状态,iptables又自动开启,很是无奈啊!在Red ...