Spring的初始化和XML解析的实现
前言
Spring是什么?它是一个应用程序框架,为应用程序的开发提供强大的支持,例如对事务处理和持久化的支持等;它也是一个bean容器,管理bean对象的整个生命周期,维护bean的各种存在状态,例如bean对象的实例化、销毁、bean的单实例和多实例状态等。
Spring作为Java发展史上不可忽视的存在,说他重新定义了Java也不为过。它功能强大,着实为日常开发提供了大大的便利。表面越简单的东西,背后越复杂。
从本章节开始,我们一起分析Spring的源码,看它到底是怎么样来实现我们常说常用的诸如IOC、Annotation、AOP、事务等功能的。
1、Spring的入口
在我们的项目中,web.xml必不可少,其中就定义了Spring的监听器。
org.springframework.web.context.ContextLoaderListener
我们来看ContextLoaderListener类,可以看到它实现了ServletContextListener接口,
contextInitialized就是Spring初始化的入口方法。
Spring还有一个入口,叫做org.springframework.web.servlet.DispatcherServlet,它们之间是父子容器的关系,最终都会调用到同一个方法org.springframework.context.support.AbstractApplicationContext.refresh()。
2、初始化
Spring的初始化第一步就是要加载配置文件,然后解析里面的配置项。
ok,我们来到XmlWebApplicationContext类的loadBeanDefinitions方法。
protectedvoidloadBeanDefinitions(XmlBeanDefinitionReaderreader)throwsIOException{
String[]configLocations=getConfigLocations();
if(configLocations!=null){
for(StringconfigLocation:configLocations){
reader.loadBeanDefinitions(configLocation);
}
}
}
可以看到,configLocations是一个数组,它获取的就是配置文件。在笔者的项目中,只有一个配置文件,名字是applicationContext.xml。下一步就是通过loadBeanDefinitions这个方法解析这个配置文件。
3、解析XML配置
首先把一个配置文件封装成一个Resource对象,然后获取Resource对象的输入流,转换成InputSource对象,最后解析成Document对象。下面代码只保留了主要部分。
publicintloadBeanDefinitions(Stringlocation,SetactualResources) throwsBeanDefinitionStoreException{ ResourceLoaderresourceLoader=getResourceLoader(); if(resourceLoaderinstanceofResourcePatternResolver){ //Resourcepatternmatchingavailable. try{ //这里的location就是配置文件-applicationContext.xml,转成Resource对象 Resource[]resources=resourceLoader).getResources(location); //获取resources对象的输入流再转成JDK的InputSource对象,最后解析成Document InputStreaminputStream=resources.getInputStream(); InputSourceinputSource=newInputSource(inputStream); Documentdoc=doLoadDocument(inputSource,resource); } catch(IOExceptionex){ thrownewBeanDefinitionStoreException( "Couldnotresolvebeandefinitionresourcepattern["+location+"]",ex); } } }
applicationContext.xml配置文件解析成Document对象,它的Root节点信息如下:
[ [#text:], [context:component-scan:null], [#text:], [bean:null], [#text:], [bean:null], [#text:], [bean:null], [#text:], [bean:null], [#text:], [#comment:指定了表现层资源的前缀和后缀 viewClass:JstlView表示JSP模板页面需要使用JSTL标签库 prefix和suffix:查找视图页面的前缀和后缀,比如传进来的逻辑视图名为hello,则该该 jsp视图页面应该存放在“WEB-INF/jsp/hello.jsp”], [#text:], [bean:null], [#text:] ]
4、加载Bean信息
上一步我们看到Spring已经把applicationContext.xml这个配置文件解析成了Document对象,接下来就是关键的一步。先看源码
//这里拿到的是Document对象的根节点,根节点信息参考上图
protectedvoidparseBeanDefinitions(Elementroot,BeanDefinitionParserDelegatedelegate){
if(delegate.isDefaultNamespace(root)){
NodeListnl=root.getChildNodes();
for(inti=0;i
4.1component-scan的解析
首先定位到自定义解析方法delegate.parseCustomElement(ele);
最终调用了org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(Elementelement,ParserContextparserContext),不过它是怎么调用到这个类的呢?说起来就比较有意思了。
我们先来看Spring里面的一个配置文件,/META-INF/spring.handlers
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
这里面配置了不同标签的处理类,比如context标签处理类就是ContextNamespaceHandler,然后通过反射实例化这个处理类,调用它的init()方法。init()方法里面它又注册了一堆处理类,其中就有我们很感兴趣的component-scan。
publicNamespaceHandlerresolve(StringnamespaceUri){
//handlerMappings里有个方法loadAllProperties(),获取Spring所有的配置项
MaphandlerMappings=getHandlerMappings();
ObjecthandlerOrClassName=handlerMappings.get(namespaceUri);
if(handlerOrClassName==null){
returnnull;
}
elseif(handlerOrClassNameinstanceofNamespaceHandler){
return(NamespaceHandler)handlerOrClassName;
}
else{
StringclassName=(String)handlerOrClassName;
try{
//以context:component-scan举例
//这里拿到的className就是org.springframework.context.config.ContextNamespaceHandler
//通过反射,实例化这个ContextNamespaceHandler,然后调用init方法
Class>handlerClass=ClassUtils.forName(className,this.classLoader);
NamespaceHandlernamespaceHandler=BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri,namespaceHandler);
returnnamespaceHandler;
}
}
}
publicvoidinit(){
registerBeanDefinitionParser("annotation-config",
newAnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan",
newComponentScanBeanDefinitionParser());
//...未完
}
最终Spring就可以通过component-scan这个标签,拿到ComponentScanBeanDefinitionParser类,调用它的parse()方法。
publicBeanDefinitionparse(Elementelement,ParserContextparserContext){
//获取包扫描路径,对应配置文件中的base-package="com.viewscenes.netsupervisor"
StringbasePackage=element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage=parserContext.getReaderContext().getEnvironment().
resolvePlaceholders(basePackage);
//这里可能有多个包路径,分割成数组
String[]basePackages=StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
/**
*configureScanner配置扫描器。
*scanner.doScan扫描执行
*registerComponents这里重点是对registerComponents的支持
*
*@return
*/
ClassPathBeanDefinitionScannerscanner=configureScanner(parserContext,element);
SetbeanDefinitions=scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(),beanDefinitions,element);
returnnull;
}
4.1.1configureScanner配置扫描器
这里面重点就是注册了默认的过滤器。use-default-filters,默认值是true,如果配置文件配置了此属性的值为false,有些注解就加不进来,到下一步扫描的时候就注册不了Bean。
protectedvoidregisterDefaultFilters(){
//这个就是配置的use-default-filters,如果配置了false。那么下面的
//Component、ManagedBean、Named注解都不会被扫描到
if(useDefaultFilters){
this.includeFilters.add(newAnnotationTypeFilter(Component.class));
ClassLoadercl=ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try{
this.includeFilters.add(newAnnotationTypeFilter(
((Class)ClassUtils.forName("javax.annotation.ManagedBean",cl)),
false));
logger.debug("JSR-250'javax.annotation.ManagedBean'
foundandsupportedforcomponentscanning");
}
catch(ClassNotFoundExceptionex){
//JSR-2501.1API(asincludedinJavaEE6)notavailable-simplyskip.
}
//...未完
}
}
4.1.2doScan扫描
doScan分为三个步骤。
- findCandidateComponents扫描包路径下的所有class文件,过滤有Component注解的类,转换成BeanDefinition对象,加入一个LinkedHashSet中。
- 循环上一步返回的LinkedHashSet,设置基本属性,比如setLazyInit、setScope。
- 注册BeanDefinition对象,向Map容器中缓存beanName和BeanDefinition,向List中加入beanName。
protectedSetdoScan(String...basePackages){
SetbeanDefinitions=newLinkedHashSet();
for(StringbasePackage:basePackages){
//findCandidateComponents方法扫描class文件,判断Component注解,转成BeanDefinition对象返回。
//值得注意的是,Component不止是@Component,还有
//@Controller、@Service、@Repository,因为在这三个注解上面还有个@Component。
//这就相当于它们都是Component的子注解。
Setcandidates=findCandidateComponents(basePackage);
for(BeanDefinitioncandidate:candidates){
ScopeMetadatascopeMetadata=this.scopeMetadataResolver.
resolveScopeMetadata(candidate);
//设置属性,没有配置的都是默认值
candidate.setScope(scopeMetadata.getScopeName());
candidate.setxxx(scopeMetadata.getxxxName());
StringbeanName=this.beanNameGenerator.generateBeanName(candidate,this.registry);
//registerBeanDefinition方法注册BeanDefinition,等同于下面两句
//this.beanDefinitionMap.put(beanName,beanDefinition);
//this.beanDefinitionNames.add(beanName);
registerBeanDefinition(definitionHolder,this.registry);
}
}
returnbeanDefinitions;
}
最后整个方法返回的就是beanDefinition对象的Set集合,以两个Controller为例。
[
Genericbean:class[com.viewscenes.netsupervisor.controller.IndexController];scope=;abstract=false;lazyInit=false;autowireMode=0;dependencyCheck=0;autowireCandidate=true;primary=false;factoryBeanName=null;factoryMethodName=null;initMethodName=null;destroyMethodName=null;definedinfile[D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\IndexController.class],
Genericbean:class[com.viewscenes.netsupervisor.controller.UserController];scope=;abstract=false;lazyInit=false;autowireMode=0;dependencyCheck=0;autowireCandidate=true;primary=false;factoryBeanName=null;factoryMethodName=null;initMethodName=null;destroyMethodName=null;definedinfile[D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\UserController.class]
]
4.1.3对annotation-config的支持
我们知道,在Spring配置文件有个配置是context:annotation-config但如果配置了context:component-scan就不必再配置config,这是因为在解析component-scan的时候已经默认添加了annotation-config的支持,除非你手动设置了annotation-config="false",不过这可不太妙,因为在IOC的时候就没办法支持@Autowired等注解了。
protectedvoidregisterComponents(XmlReaderContextreaderContext,
SetbeanDefinitions,Elementelement){
booleanannotationConfig=true;
if(element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)){
annotationConfig=Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
}
if(annotationConfig){//判断annotation-config属性的值
SetbeanDefs=newLinkedHashSet(4);
if(!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)){
RootBeanDefinitiondef=newRootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
beanDefs.add(registerPostProcessor(registry,def,AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if(!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)){
RootBeanDefinitiondef=newRootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
beanDefs.add(registerPostProcessor(registry,def,REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if(jsr250Present&&!registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)){
RootBeanDefinitiondef=newRootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
beanDefs.add(registerPostProcessor(registry,def,COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
......未完
}
}
4.2bean标签的解析
bean标签的解析,就是默认的处理方法。
获取bean标签的id,并且把beanName赋值为id,设置别名。新建AbstractBeanDefinition对象,通过反射设置beanClass,解析property属性名称和值。
publicBeanDefinitionHolderparseBeanDefinitionElement(Elementele,BeanDefinitioncontainingBean){
//获取bean_id
Stringid=ele.getAttribute(ID_ATTRIBUTE);
StringbeanName=id;
AbstractBeanDefinitionbeanDefinition=
parseBeanDefinitionElement(ele,beanName,containingBean);
String[]aliasesArray=StringUtils.toStringArray(aliases);
//最后返回已经包含了beanName、class对象和一系列方法的BeanDefinition对象
returnnewBeanDefinitionHolder(beanDefinition,beanName,aliasesArray);
}
publicAbstractBeanDefinitionparseBeanDefinitionElement(Elementele,
StringbeanName,BeanDefinitioncontainingBean){
StringclassName=ele.getAttribute(CLASS_ATTRIBUTE).trim();
try{
//根据className反射设置setBeanClass和setBeanClassName
AbstractBeanDefinitionbd=createBeanDefinition(className,parent);
//设置默认方法setScope、setLazyInit、setAutowireMode...
parseBeanDefinitionAttributes(ele,beanName,containingBean,bd);
//设置property属性
parsePropertyElements(ele,bd);
returnbd;
}
returnnull;
}
注册BeanDefinition对象,和component-scan扫描的bean注册一样。向容器中填充对象。
不管是XML配置的Bean,还是通过component-scan扫描注册的Bean它们最后都是殊途同归的,会转换成一个BeanDefinition对象。记录着这个Bean对象的属性和方法,最后都注册到容器中,等待在实例化和IOC的时候遍历它们。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。