不管是spring Test還是Spring mvc最終都會(huì)調(diào)用到refresh()方法匙隔,下面我們重點(diǎn)看下refresh做了些什么
Spring test是在AbstractGenericContextLoader這個(gè)類中調(diào)用refresh()方法绪颖,并且會(huì)在refresh()方法之前裝載bean信息loadBeanDefinitions(context, mergedConfig);
@Override
public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].",
mergedConfig));
}
validateMergedContextConfiguration(mergedConfig);
GenericApplicationContext context = new GenericApplicationContext();
ApplicationContext parent = mergedConfig.getParentApplicationContext();
if (parent != null) {
context.setParent(parent);
}
prepareContext(context);
prepareContext(context, mergedConfig);
customizeBeanFactory(context.getDefaultListableBeanFactory());
loadBeanDefinitions(context, mergedConfig);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context);
context.refresh();
context.registerShutdownHook();
return context;
}
Spring mvc在servelet裝載是會(huì)調(diào)用init()方法,自然會(huì)調(diào)DispatcherServlet的init方法欢策,最終會(huì)調(diào)用父類的org.springframework.web.servlet.HttpServletBean的init()方法
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
真正加載bean的方法是initServletBean()跟進(jìn)去會(huì)發(fā)現(xiàn)這個(gè)方法是在類FrameworkServlet中實(shí)現(xiàn)
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
內(nèi)部是通過調(diào)用initWebApplicationContext()來初始化一個(gè)WebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
// No fixed context defined for this servlet - create a local one.
WebApplicationContext parent =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
wac = createWebApplicationContext(parent);
}
if (!this.refreshEventReceived) {
// Apparently not a ConfigurableApplicationContext with refresh support:
// triggering initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
因?yàn)閯傞_始wac為空,主要看createWebApplicationContext這個(gè)方法
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
// Assign the best possible id value.
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
"." + getServletName());
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
}
}
else {
// Servlet 2.5's getContextPath available!
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath() +
"/" + getServletName());
}
wac.setParent(parent);
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.setConfigLocation(getContextConfigLocation());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
postProcessWebApplicationContext(wac);
wac.refresh();
return wac;
}
這個(gè)方法前面會(huì)設(shè)置一些相關(guān)參數(shù)赏淌,例如踩寇,設(shè)置web容器,容器配置信息六水,然后會(huì)調(diào)用一個(gè)refresh()方法俺孙。refresh()方法如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
obtainFreshBeanFactory()方法的描述:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
這里refreshBeanFactory()方法有兩個(gè)實(shí)現(xiàn)GenericApplicationContext中的實(shí)現(xiàn)如下:
@Override
protected final void refreshBeanFactory() throws IllegalStateException {
if (this.refreshed) {
throw new IllegalStateException(
"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
}
this.beanFactory.setSerializationId(getId());
this.refreshed = true;
}
因?yàn)镚enericApplicationContext在初始化的時(shí)候已經(jīng)創(chuàng)建好了beanFactory了,所以這refreshBeanFactory()方法沒有實(shí)質(zhì)性的操作缩擂。另一個(gè)實(shí)現(xiàn)是在AbstractRefreshableApplicationContext這個(gè)類鼠冕,它是AbstractApplicationContext的子類,不論XmlWebApplicationContext胯盯、還是ClassPathXmlApplicationContext都繼承了它懈费,因此都能調(diào)用到這個(gè)一樣的初始化方法,來看看body部分的代碼
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
bean的加載是在loadBeanDefinitions(beanFactory)方法中博脑,它由AbstractXmlApplicationContext類中的方法實(shí)現(xiàn)憎乙,web項(xiàng)目中將會(huì)由類:XmlWebApplicationContext來實(shí)現(xiàn)票罐,其實(shí)差不多,主要是看啟動(dòng)文件是在那里而已泞边,如果在非web類項(xiàng)目中沒有自定義的XmlApplicationContext该押,那么其實(shí)功能可以參考XmlWebApplicationContext,可以認(rèn)為是一樣的功能阵谚。那么看看loadBeanDefinitions方法如下:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
XmlBeanDefineitionReader蚕礼,是讀取XML中spring的相關(guān)信息(也就是解析SpringContext.xml的),這里通過getConfigLocations()獲取到的就是這個(gè)或多個(gè)文件的路徑梢什,會(huì)循環(huán)奠蹬,
通過XmlBeanDefineitionReader來解析,跟蹤到loadBeanDefinitions方法里面嗡午,會(huì)發(fā)現(xiàn)方法實(shí)現(xiàn)體在XmlBeanDefineitionReader的父類:AbstractBeanDefinitionReader中囤躁,代碼如下:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
到目前為止只解析到springContext.xml在哪里,但是還沒解析到springContext.xml的內(nèi)容荔睹,可能有多個(gè)spring配置文件狸演,這里會(huì)出現(xiàn)多個(gè)Resource。接下來有很多層調(diào)用僻他,會(huì)以此調(diào)用AbstractBeanDefinitionReader.loadBeanDefinitions(Resource... resources)調(diào)用方法:XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource)開始解析XML
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//從中獲取已經(jīng)封裝的Resource對(duì)象并再次從Resource中獲取其中的InputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
數(shù)據(jù)準(zhǔn)備階段的邏輯:首先對(duì)傳入的resource參數(shù)做封裝宵距,目的是考慮到Resource可能存在編碼要求的情況,其次中姜,通過SAX讀取XML文件的方式來準(zhǔn)備InputSource對(duì)象消玄,最后將準(zhǔn)備的數(shù)據(jù)通過參數(shù)傳入真正的核心處理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
上面的代碼主要是根據(jù)xml文件的驗(yàn)證模式,加載xml丢胚,并得到document翩瓜,最后根據(jù)返回的document注冊(cè)bean信息。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
這里ValidationModeForResource(resource)方法是獲取xml文件的驗(yàn)證模式携龟,比較常用的xml文件的驗(yàn)證模式有兩種:DTD和XSD的區(qū)別:
DTD(Document Type Definition)即文檔類型定義兔跌,是一種XML約束模式語言,是XML文件的驗(yàn)證機(jī)制峡蟋,屬于XML文件組成的一部分坟桅。DTD是一種保證XML文檔格式正確的有效方法,可以通過比較XML文檔和DTD文件來看文檔是否符合規(guī)范蕊蝗,元素和標(biāo)簽使用是否正確仅乓。一個(gè)DTD文檔包含:元素的定義規(guī)則,元素間關(guān)系的定義規(guī)則蓬戚,元素可使用的屬性夸楣,可使用的實(shí)體或符合規(guī)則
使用DTD驗(yàn)證模式的時(shí)候需要在XML文件的頭部聲明,以下是在Spring中使用DTD聲明方式的代碼:
\<?xml version="1.0" encoding="UTF-8"?\>
\<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd"\>
...
XML Schema語言就是XSD(XML Schemas Definition)。XML Schema描述了XML文檔的結(jié)構(gòu)豫喧,可以用一個(gè)指定的XML Schema來驗(yàn)證某個(gè)XML文檔石洗,以檢查該XML文檔是否符合其要求,文檔設(shè)計(jì)者可以通過XML Schema指定一個(gè)XML文檔所允許的結(jié)構(gòu)和內(nèi)容紧显,并可據(jù)此檢查一個(gè)XML文檔是否是有效的
在使用XML Schema文檔對(duì)XML實(shí)例文檔進(jìn)行檢驗(yàn)讲衫,除了要聲明名稱空間外(xmlns=http://www.Springframework.org/schema/beans),還必須指定該名稱空間所對(duì)應(yīng)的XML Schema文檔的存儲(chǔ)位置孵班,通過schemaLocation屬性來指定名稱空間所對(duì)應(yīng)的XML Schema文檔的存儲(chǔ)位置涉兽,它包含兩個(gè)部分,一部分是名稱空間的URI篙程,另一部分就該名稱空間所標(biāo)識(shí)的XML Schema文件位置或URL地址
\<?xml version="1.0" encoding="UTF-8"?\>
\<beans xmlns="http://www.Springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema/beans"
xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd"\>
....
\</beans\>
驗(yàn)證模式的讀然ㄍ帧:
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果手動(dòng)指定了驗(yàn)證模式則使用指定的驗(yàn)證模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//使用自動(dòng)檢驗(yàn)?zāi)J? int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}
自動(dòng)檢測(cè)驗(yàn)證模式的功能是在函數(shù)detectValidationMode方法中實(shí)現(xiàn)的,在detectValidationMode函數(shù)中又將自動(dòng)檢測(cè)驗(yàn)證模式的工作委托給了專門處理類XmlValidationModeDetector的detectValidationMode方法
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
Spring通過判斷是否包含DOCTYPE,判斷采用哪種驗(yàn)證方式房午,包含就是DTD,否則就是XSD
獲取document
經(jīng)過了驗(yàn)證模式準(zhǔn)備的步驟就可以進(jìn)行Document加載了丹允,對(duì)于文檔的讀取委托給了DocumentLoader去執(zhí)行郭厌,這里的DocumentLoader是個(gè)接口,而真正調(diào)用的是DefaultDocumentLoader雕蔽,解析代碼如下
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
首選創(chuàng)建DocumentBuildFactory折柠,再通過DocumentBuilderFactory創(chuàng)建DocumentBuilder,進(jìn)而解析InputSource來返回Document對(duì)象批狐。對(duì)于參數(shù)entityResolver扇售,傳入的是通過getEntityResolver()函數(shù)獲取的返回值,代碼如下
protected EntityResolver getEntityResolver() {
if (this.entityResolver == null) {
// Determine default EntityResolver to use.
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader != null) {
this.entityResolver = new ResourceEntityResolver(resourceLoader);
}
else {
this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
}
}
return this.entityResolver;
}
EntityResolver用法
如果SAX應(yīng)用程序需要實(shí)現(xiàn)自定義處理外部實(shí)體嚣艇,則必須實(shí)現(xiàn)此接口并使用setEntityResolver方法向SAX驅(qū)動(dòng)器注冊(cè)一個(gè)實(shí)例承冰。也就是說,對(duì)于解析一個(gè)XML食零,SAX首先讀取該XML文檔上的聲明困乒,根據(jù)聲明去尋找相應(yīng)的DTD定義,以便對(duì)文檔進(jìn)行一個(gè)驗(yàn)證贰谣,默認(rèn)的尋找規(guī)則娜搂,即通過網(wǎng)絡(luò)(實(shí)現(xiàn)上就是聲明DTD的URI地址)來下載相應(yīng)的DTD聲明,并進(jìn)行認(rèn)證吱抚。下載的過程是一個(gè)漫長的過程百宇,而且當(dāng)網(wǎng)絡(luò)中斷或不可用時(shí),這里會(huì)報(bào)錯(cuò)秘豹,就是因?yàn)橄鄳?yīng)的DTD聲明沒有被找到的原因携御。
EntityResolver的作用是項(xiàng)目本身就可以提供一個(gè)如何尋找DTD聲明的方法,即由程序來實(shí)現(xiàn)尋找DTD聲明的過程,比如將DTD文件放到項(xiàng)目中某處因痛,在實(shí)現(xiàn)時(shí)直接將此文檔讀取并返回給SAX即可婚苹,entityResolver的接口方法聲明:
InputSource resolveEntity(String publicId, String systemId)
這里它接收兩個(gè)參數(shù)publicId,systemId并返回InputSource對(duì)象鸵膏,以特定配置文件來解析
(1)如果在解析驗(yàn)證模式為XSD的配置文件膊升,代碼如下:
\<?xml version="1.0" encoding="UTF-8"?\>
\<beans xmlns="http://www.Springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd"\>
....
\</beans\>
讀取到兩個(gè)參數(shù):
- public:null
- systemId:http://www.Springframework.org/schema/beans/Spring-beans.xsd
(2)如果解析驗(yàn)證模式為DTD的配置文件,代碼如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
....
</beans>
讀取到以下兩個(gè)參數(shù)
- publicId:-//Spring//DTD BEAN 2.0//EN
- systemId:http://www.Springframework.org/dtd/Spring-beans-2.0.dtd
根據(jù)之前Spring中通過getEntityResolver()方法對(duì)EntityResolver的獲取谭企,我們知道廓译,Spring中使用DelegatingEntityResolver類為EntityResolver的實(shí)現(xiàn)類,resolveEntity實(shí)現(xiàn)方法如下:
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
對(duì)不同的驗(yàn)證模式债查,Spring使用了不同的解析器解析非区,比如加載DTD類型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去當(dāng)前路徑下尋找,而加載XSD類型的PluggableSchemaResolver類的resolveEntity是默認(rèn)到META-INF/Spring.schemas文件中找到systemId所對(duì)應(yīng)的XSD文件并加載盹廷。
解析并注冊(cè)BeanDefinition
當(dāng)把文件轉(zhuǎn)換成Document后征绸,接下來就是對(duì)bean的提取及注冊(cè),當(dāng)程序已經(jīng)擁有了XML文檔文件的Document實(shí)例對(duì)象時(shí)俄占,就會(huì)被引入下面這個(gè)方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//使用DefaultBeanDefinitionDocumentReader實(shí)例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//加載及注冊(cè)bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinitionDocumentReader是一個(gè)接口管怠,而實(shí)例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通過此方法缸榄,BeanDefinitionDocumentReader真正的類型其實(shí)已經(jīng)是DefaultBeanDefinitionDocumentReader了渤弛,進(jìn)入DefaultBeanDefinitionDocumentReader后,發(fā)現(xiàn)這個(gè)方法的重要目的之一就是提取root甚带,以便于再次將root作為參數(shù)繼續(xù)BeanDefinition的注冊(cè)
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在Spring的XML配置里面有兩大類Bean聲明她肯,一個(gè)是默認(rèn)的,如:
<bean id="test" class="test.TestBean"/>
另一種是自定義的鹰贵,如:
<context:component-scan base-package="com.lufax.spring"/>
而這兩種方式的讀取及解析差別是非常大的晴氨,如果采用Spring默認(rèn)的配置,Spring當(dāng)然知道該怎么做碉输,但如果是自定義的瑞筐,那么就需要用戶實(shí)現(xiàn)一些接口及配置了。對(duì)于根節(jié)點(diǎn)或子節(jié)點(diǎn)如果是默認(rèn)命名空間的話采用parseDefaultElement方法進(jìn)行解析腊瑟,否則使用delegate.parseCustomElement方法對(duì)自定義命名空間進(jìn)行解析聚假。而判斷是否默認(rèn)命名空間還是自定義命名空間的辦法其實(shí)是使用node.getNamespaceURI()獲取命名空間,并與Spring中固定的命名空間http://www.springframework.org/schema/beans進(jìn)行對(duì)比闰非,如果一致則認(rèn)為是默認(rèn)膘格,否則就認(rèn)為是自定義