SpringMVC 源码深度解析<context:component-scan>(扫描和注冊的注解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: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 源码深度解析<context:component-scan>(扫描和注冊的注解Bean)的更多相关文章
- 源码深度解析SpringMvc请求运行机制(转)
源码深度解析SpringMvc请求运行机制 本文依赖的是springmvc4.0.5.RELEASE,通过源码深度解析了解springMvc的请求运行机制.通过源码我们可以知道从客户端发送一个URL请 ...
- Spring源码深度解析之Spring MVC
Spring源码深度解析之Spring MVC Spring框架提供了构建Web应用程序的全功能MVC模块.通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术,例如JavaServer ...
- spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
@Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...
- mybatis 3.x源码深度解析与最佳实践(最完整原创)
mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...
- VueRouter 源码深度解析
VueRouter 源码深度解析 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还 ...
- spring源码深度解析— IOC 之 容器的基本实现
概述 上一篇我们搭建完Spring源码阅读环境,spring源码深度解析—Spring的整体架构和环境搭建 这篇我们开始真正的阅读Spring的源码,分析spring的源码之前我们先来简单回顾下spr ...
- spring源码深度解析— IOC 之 默认标签解析(上)
概述 接前两篇文章 spring源码深度解析—Spring的整体架构和环境搭建 和 spring源码深度解析— IOC 之 容器的基本实现 本文主要研究Spring标签的解析,Spring的标签 ...
- spring源码深度解析— IOC 之 开启 bean 的加载
概述 前面我们已经分析了spring对于xml配置文件的解析,将分析的信息组装成 BeanDefinition,并将其保存注册到相应的 BeanDefinitionRegistry 中.至此,Spri ...
- Spring源码深度解析之事务
Spring源码深度解析之事务 目录 一.JDBC方式下的事务使用示例 (1)创建数据表结构 (2)创建对应数据表的PO (3)创建表和实体之间的映射 (4)创建数据操作接口 (5)创建数据操作接口实 ...
随机推荐
- ajax连接池和XMLHttpRequest
连接池 我们公司在路由和交换机web界面和后端交互全部采用的是自己封装的ajax组件完成的,组件有点老了,代码风格和其中的某些用法现在看起来都有点不习惯.今天把这个组件的核心部分的ajax连接池记录下 ...
- bzoj 3781 小B的询问(莫队算法)
[题意] 若干个询问sigma{ cnt[i]^2 } cnt[i]表示i在[l,r]内的出现次数. [思路] 莫队算法,裸题. 一个cnt数组即可维护插入与删除. [代码] #include< ...
- 指向函数的指针数组(C++)
我们能够创建一个指向函数的指针数组.为了选择一个函数,只需要使用数组的下标,然后间接引用这个指针.这种方式支持表格式驱动码的概念:可以根据状态变量去选择被执行函数,而不用条件语句或case语句.这种设 ...
- 深入浅出谈存储之NAS是什么
深入浅出谈存储之NAS是什么 2012年02月17日16:42 来源:新浪博客 作者:林沛满 编辑:曾智强 查看全文 赞(0)评论(0) 分享 标签: NAS , 企业NAS , 存储系统 [IT16 ...
- 【MySQL】源码编译安装和配置MySql 5.5.32(单实例)
[需求描述] 在CentOS环境中,通过编译源码的方式,安装并且配置“单实例”的MySQL5.5.32数据库. MySQL的安装目录为:/application/mysql-5.5.32 MySQL数 ...
- 让CSS3给你的文字加上边框宽度,并实现镂空效果
这次,我主要向大家介绍一下CSS3下的-webkit-text-stroke属性,并分享几个用该属性制作的镂空文字效果. 1.-webkit-text-stroke属性简介 CSS边框的一个不足就是只 ...
- MacTerminal快捷键
[MacTerminal快捷键] 在Mac系统中并没有Home.End等键,所以在使用时并不是特别的顺手,但是有几个键位组合可以使Terminal的操作更加灵活方便. 1.将光标移动到行首:ctrl ...
- nginx配置 首页不显示 index.html首页是显示域名
原状况如下: 访问:www.test.com 敲回车后浏览器中自动跳转致: www.test.com/index.html 公司新需求如下: 访问:www.test.com 敲回车后浏览器中url不变 ...
- junit4学习(Annotation)
在一个测试类中,所有被@Test注解修饰的public,void方法都是testcase,可以被JUNIT执行. @Retention(value=RUNTIME) @Target(value=MET ...
- Win8制作和使用恢复盘
制作和使用恢复盘要制作恢复盘,请执行以下操作:注:确保计算机连接到交流电源.1. 将指针移至屏幕的右上角或右下角以显示超级按钮,然后单击搜索.2. 根据操作系统的不同,执行以下某项操作:• 在 Win ...