背景
使用了很久Spring,但对Spring的内在实现并不了解,正好最近有些时间,研究下Spring的源码
首先写一个测试方法
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml")); User userTest = (User) bf.getBean("testBean"); System.out.println(userTest.getEmail());
和对应的配置文件beans.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:myName="http://www.wjs.com/schema/user" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.wjs.com/schema/user http://www.wjs.com/schema/user.xsd"> <bean id = "testBean" class="com.zero.pojo.User"> <property name="userName" value="zhang"></property> <property name="email" value = "18"></property> </bean></beans>
以上代码的功能使用过Spring的都能猜出来
- 读取配置文件beans.xml
- 根据beans.xml中的配置找到对应的类,并进行实例化
- 调用实例化后的类
现在开始调试代码
1.1初始化ClassPathResource
new ClassPathResource("beans.xml")
进入代码时首先执行了new ClassPathResource("beans.xml"),在这里加载了配置文件路径到ClassPathResource的path属性中。
public ClassPathResource(String path) { this(path, (ClassLoader)null); } public ClassPathResource(String path, @Nullable ClassLoader classLoader) { Assert.notNull(path, "Path must not be null"); String pathToUse = StringUtils.cleanPath(path); if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } this.path = pathToUse; this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader(); }
1.2初始化XmlBeanFactory
new XmlBeanFactory(new ClassPathResource("beans.xml"))
初始化ClassPathResource后,开始初始化XmlBeanFactory,在构造函数内部再调用构造函数,this.reader.loadBeanDefinitions(resource);这个才是资源加载的真正实现
public XmlBeanFactory(Resource resource) throws BeansException { //调用XmlBeanFactory(Resource, BeanFactory)构造方法 this(resource, (BeanFactory)null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader = new XmlBeanDefinitionReader(this); this.reader.loadBeanDefinitions(resource); }
1.2.1调用AbstractAutowireCapableBeanFactory的构造方法
super(parentBeanFactory);
不过在此之前还有一个super(parentBeanFactory);它会一直调用到AbstractAutowireCapableBeanFactory的构造方法中,这里的ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能, 当有忽略的接口类,自动装配会忽略这部分类的初始化装配,因为某种情况下,此时的接口实现类不能初始化
public AbstractAutowireCapableBeanFactory() { this.instantiationStrategy = new CglibSubclassingInstantiationStrategy(); this.parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); this.allowCircularReferences = true; this.allowRawInjectionDespiteWrapping = false; this.ignoredDependencyTypes = new HashSet(); this.ignoredDependencyInterfaces = new HashSet(); this.currentlyCreatedBean = new NamedThreadLocal("Currently created bean"); this.factoryBeanInstanceCache = new ConcurrentHashMap(); this.factoryMethodCandidateCache = new ConcurrentHashMap(); this.filteredPropertyDescriptorsCache = new ConcurrentHashMap(); this.ignoreDependencyInterface(BeanNameAware.class); this.ignoreDependencyInterface(BeanFactoryAware.class); this.ignoreDependencyInterface(BeanClassLoaderAware.class); }
1.2.2设置变量reader
this.reader = new XmlBeanDefinitionReader(this);
执行完父类的构造方法后,开始设置变量this.reader = new XmlBeanDefinitionReader(this);
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { //执行AbstractBeanDefinitionReader构造方法 super(registry); this.errorHandler = new SimpleSaxErrorHandler(this.logger); this.validationModeDetector = new XmlValidationModeDetector(); this.resourcesCurrentlyBeingLoaded = new NamedThreadLocal("XML bean definition resources currently being loaded"); } //设置resourceLoader和environment protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; //设置resourceLoader if (this.registry instanceof ResourceLoader) { this.resourceLoader = (ResourceLoader)this.registry; } else { this.resourceLoader = new PathMatchingResourcePatternResolver(); } //设置environment if (this.registry instanceof EnvironmentCapable) { this.environment = ((EnvironmentCapable)this.registry).getEnvironment(); } else { this.environment = new StandardEnvironment(); } }
1.2.3资源加载
this.reader.loadBeanDefinitions(resource);
设置完变量后开始执行this.reader.loadBeanDefinitions(resource)进行资源加载
方法pkg:org.springframework.beans.factory.xml
- 封装资源文件,当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装
- 获取输入流,从Resource中获取对应InputStream并构造InputSource
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions
loadBeanDefinitions具体实现过程
构造好encodedResource对象后,再次转入loadBeanDefinitions,这个方法内部才是真正的数据准备阶段
对传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分this.doLoadBeanDefinitions(inputSource, encodedResource.getResource())
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return this.loadBeanDefinitions(new EncodedResource(resource)); } 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<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { //从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { //InputSource这个类不来自Spring,全路径是org.xml.sax.InputSource 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(); } } }
1.3加载XML文件根据得到的Document信息注册Bean
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
doLoadBeanDefinitions
- 加载XML文件,并得到对应的Document Document doc = doLoadDocument(inputSource, resource);
- 根据返回的Document注册Bean信息 return registerBeanDefinitions(doc, resource)
这些步骤支撑整个Spring容器部分的实现
/** * Actually load bean definitions from the specified XML file. * @param inputSource the SAX InputSource to read from * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #doLoadDocument * @see #registerBeanDefinitions */ 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); } }
doLoadDocument
- 获取XML文件的验证模式 DTD(文档类型定义)还是XSD (XML Schema语言) this.getValidationModeForResource(resource)
加载XML文件,得到对应Document
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware()); }
1.3.1获取资源的验证模式
this.getValidationModeForResource(resource)
/** * Gets the validation mode for the specified {@link Resource}. If no explicit * validation mode has been configured then the validation mode is * {@link #detectValidationMode detected}. * <p>Override this method if you would like full control over the validation * mode, even when something other than {@link #VALIDATION_AUTO} was set. */ protected int getValidationModeForResource(Resource resource) { int validationModeToUse = getValidationMode(); //如果手动指定了使用手动指定的验证模式类型的 if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } //未指定使用自动检测 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; }
1.3.2自动判断资源认证模式
自动判断实际就是获取资源的输入流,判断是否包含DOCTPE
DTD模式XML文件带有<!DOCTYPE beans PU.... 而XSD不带
int detectedMode = detectValidationMode(resource);
/** * Detect the validation mode for the XML document in the supplied {@link InputStream}. * Note that the supplied {@link InputStream} is closed by this method before returning. * @param inputStream the InputStream to parse * @throws IOException in case of I/O failure * @see #VALIDATION_DTD * @see #VALIDATION_XSD */ 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(); } }
hasDoctype(content)
/** * Does the content contain the DTD DOCTYPE declaration? */ private boolean hasDoctype(String content) { return content.contains(DOCTYPE); }
1.4加载Document
- 加载Document
this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
/** * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured * XML parser. */ @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); }
创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document
1.5注册Bean信息
- 根据返回的Document注册Bean信息
在将文件转换为Document后,接下来就是提取和注册bean,当程序已经有了XML文件的Document实例和上下文信息后
int count = this.registerBeanDefinitions(doc, resource);
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. * <p>Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //记录统计前BeanDefinition的加载个数 int countBefore = getRegistry().getBeanDefinitionCount(); //加载及注册bean documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //记录本次加载的BeanDefinition个数 return getRegistry().getBeanDefinitionCount() - countBefore; }
1.5.1实例化createBeanDefinitionDocumentReader
createBeanDefinitionDocumentReader
BeanDefinitionDocumentReader是一个接口,实例化工作是在createBeanDefinitionDocumentReader中执行,通过此方法,BeanDefinitionDocumentReader已经转变成了DefaultBeanDefinitionDocumentReader
createBeanDefinitionDocumentReader
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class; /** * Create the {@link BeanDefinitionDocumentReader} to use for actually * reading bean definitions from an XML document. * <p>The default implementation instantiates the specified "documentReaderClass". * @see #setDocumentReaderClass */ protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass)); }
public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException { Assert.notNull(clazz, "Class must not be null"); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } else { try { return instantiateClass(clazz.getDeclaredConstructor()); } catch (NoSuchMethodException var3) { Constructor<T> ctor = findPrimaryConstructor(clazz); if (ctor != null) { return instantiateClass(ctor); } else { throw new BeanInstantiationException(clazz, "No default constructor found", var3); } } catch (LinkageError var4) { throw new BeanInstantiationException(clazz, "Unresolvable class definition", var4); } } }
1.5.2BeanDefinition的注册
进入DefaultBeanDefinitionDocumentReader后,开始提取root,并将提取到的root作为参数进行BeanDefinition的注册
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
doRegisterBeanDefinitions(root);
这里就是逻辑的核心
doRegisterBeanDefinitions(root);
/** * Register each bean definition within the given root {@code <beans/>} element. * 在给定的根{@code <beans />}元素中注册每个bean定义 */ 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)) { //处理profile属性,使用profile来读取不同的配置文件,切换使用不同的环境(DEV/PRD) 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)) { if (logger.isInfoEnabled()) { logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } //解析前处理,留给子类实现,模板方法模式,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做操作,只需要重写这两个类就行了 preProcessXml(root); parseBeanDefinitions(root, this.delegate); //解析后处理,留给子类实现 postProcessXml(root); this.delegate = parent; }
1.5.3解析profile属性
处理profile属性,使用profile来读取不同的配置文件,切换使用不同的环境(DEV/PRD)
root.getAttribute(PROFILE_ATTRIBUTE);
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
1.5.4解析并注册BeanDefinition
parseBeanDefinitions(root, this.delegate);
处理掉profile后,就可以开始进行XML的读取了,Spring的XML配置文件中有两大类Bean声明,默认命名空间和自定义命名空间,接下来就是判断命名空间类型并进行相应解析
- 默认命名空间
<bean id = "testBean" class="com.zero.pojo.User"> <property name="userName" value="zhang"></property> <property name="email" value = "18"></property> </bean>
- 自定义命名空间
<myName:user id="testBean" userName="name" email="A"/>
parseBeanDefinitions(root, this.delegate);
/** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ 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; //对bean的处理 //默认命名空间 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } //自定义命名空间 else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }