Spring 源码学习笔记(二) Java Bean容器

个人学习Spring源码系列 Java Bean容器

源码地址 https://github.com/spring-projects/spring-framework

目录

个人学习Spring源码系列 整体架构篇

什么是Spring Bean?

Spring Bean的作用域

Spring Bean自动装配方式

容器的基本用法

功能分析

JavaBean 核心类

容器基础 XmlBeanFactory

验证模式

获取Document

解析及注册BeanDefinitions

核心逻辑底部doRegisterBeanDefinitions(root)



 Java Bean是Spring中最核心的东西,因为Spring就像是个大水桶,Bean就像是容器中的水。

什么是Spring Bean?

1.Spring beans 是那些形成Spring应用的主干的java对象

2.它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建

3.Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。

 

Spring Bean的作用域

1.singleton:SpringIoc容器只会创建该Bean的唯一实例

2.prototype:每次请求都创建一个实例

3.request:每次HTTP请求都会产生一个新的bean

4.session :每次会话创建一个实例

5.global session :全局的HttpSession中,容器会返回该bean的同一个实例。

 

Spring Bean自动装配方式

1.no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。

2.byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。

3.byType::通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。

4.constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。

5.autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。

 

容器的基本用法

简单看下bean的定义代码如下

public class MyBeanDemo { 
    private String beanName = "bean"; 

    public String getBeanName() { 
        return beanName; 
    } 

    public void setBeanName(String beanName) { 
        this.beanName = beanName; 
    } 
}

这个基本的bean没有特别之处,spring的的目的就是让我们的bean成为一个纯粹的的POJO,这就是spring追求的,接下来就是在配置文件中定义这个bean,配置文件如下

<?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"> 
    <bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo"> 
        <property name="beanName" value="bean demo1"/> 
    </bean> 
</beans>

在上面的配置中我们可以看到bean的声明方式,在spring中的bean定义有N中属性,但是我们只要像上面这样简单的声明就可以使用了。

测试代码如下:

public class TestDemo { 
    public static void main(String[] args) { 
        BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-bean.xml")); 
        try { 
            MyBeanDemo bean = (MyBeanDemo)factory.getBean("demo");
            System.out.println(bean.getBeanName()); } 
        catch (BeansException e) { 
            e.printStackTrace(); 
        } 
    } 
}

运行上述测试代码就可以看到输出结果如下图:

功能分析

以上代码完成了下面几个功能:

1.读取配置文件spring-bean.xml

2.根据spring-beanxml中的配置找到对应的类的配置,并实例化

3.调用实例化后的实例

完成Spring功能基本架构至少需要3个类

ConfigReader:用于读取及验证配置文件。我们要用配置文件里面的东西,当然首先要做的就是读取,然后放置在内存中。
ReflectionUtil:用于根据配置文件中的配置进行反射实例化。比如在spring-bean.xml中我们就可以根据bean.demo进行实例化。
APP:用于完成整个逻辑的串联。


JavaBean 核心类

以上是JavaBean容器加载的相关类图(图片来自https://blog.csdn.net/yubotianxiao/article/details/56497336)

- AliasRegistry:定义对alias的简单增删改等操作
- SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现
- SingletonBeanRegistry:定义对单例的注册及获取
- BeanFactory定义获取bean及bean的各种属性
- DefaultSingletonBeanRegistry:默认对接口SingletonBeanRegistry各函数的实现
- HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持
- BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作
- FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能
- ConfigurableBeanFactory:提供配置Factory的各种方法
- ListableBeanFactory:根据各种条件获取bean的配置清单
- AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurationBeanFactory的功能
- AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器
- AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现
- ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等
- DefaultListableBeanFactory综合上面所有功能,是Spring注册及加载Bean的默认实现  
XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取Bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性,在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册 。

 

容器基础 XmlBeanFactory

接下来基于以下代码进行分析

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));

时序图从TestDemo测试类开始,首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了。有了Resource后就可以对BeanFactory进行初始化操作。

配置文件的封装

Spring的配置文件读取是通过ClassPathResource进行封装的,Spring对其内部使用到的资源实现了自己的抽象结构Resource接口来封装底层资源,如下源码:

    public interface InputStreamSource {
	InputStream getInputStream() throws IOException;
    }

    public interface Resource extends InputStreamSource {
	boolean exists();

	default boolean isReadable() {
		return true;
	}

	default boolean isOpen() {
		return false;
	}

	default boolean isFile() {
		return false;
	}

	URL getURL() throws IOException;

	URI getURI() throws IOException;

	File getFile() throws IOException;

	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	long contentLength() throws IOException;

	long lastModified() throws IOException;

	Resource createRelative(String relativePath) throws IOException;

	String getFilename();

	String getDescription();
    }

InputStreamSource封装任何能返回InputStream的类,比如File、Classpath下的资源和Byte Array等, 它只有一个方法定义:getInputStream(),该方法返回一个新的InputStream对象。


Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。首先,它定义了3个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。另外,Resource接口还提供了不同资源到URL、URI、File类型的转换,以及获取lastModified属性、文件名(不带路径信息的文件名,getFilename())的方法,为了便于操作,Resource还提供了基于当前资源创建一个相对资源的方法:createRelative(),还提供了getDescription()方法用于在错误处理中的打印信息。
对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等,相关类图如下所示:

 

日常开发中我们可以直接使用spring提供的类来加载资源文件,比如在希望加载资源文件时可以使用下面的代码:

Resource resource = new ClassPathResource("spring-bean.xml");
InputStream is = resource.getInputStream();

当通过Resource相关类完成了对配置文件进行封装后,配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。接下来就进入到XmlBeanFactory的初始化过程了,XmlBeanFactory的初始化有若干办法,Spring提供了很多的构造函数,在这里分析的是使用Resource实例作为构造函数参数的办法,代码如下:

public XmlBeanFactory(Resource resource) throws BeansException { 
    //调用XmlBeanFactory(Resource,BeanFactory)构造方法
    this(resource, null); 
} 

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { 
    super(parentBeanFactory); 
    this.reader.loadBeanDefinitions(resource); 
}

上面函数中的代码this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现,时序图中提到的XmlBeanDefinitionReader加载数据就是在这里完成的。

这里的super(parentBeanFactory) 可以跟踪到父类AbstractAutowireCapableBeanFactory

        /**
	 * Create a new AbstractAutowireCapableBeanFactory.
	 */
	public AbstractAutowireCapableBeanFactory() {
		super();
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
		if (NativeDetector.inNativeImage()) {
			this.instantiationStrategy = new SimpleInstantiationStrategy();
		}
		else {
			this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
		}
	}

ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能,举例A中有B属性,当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B。但是在某些情况B不会被初始化,例如这里的实现了BeanNameAware接口,在这里自动装配时忽略给定的依赖接口。

XMLBeanFactory loadBeanDefinitions加载Bean的过程

1.封装资源文件,首先对参数Resource使用EncodedResource类进行封装

        /**
	 * Load bean definitions from the specified XML file.
	 * @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
	 */
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

2.获取输入流 从Resource中获取对应的InputStream并构造InputSource

3.通过构造InputStream实例和Resource实例继续调用函数 doLoadBeanDefinitions()

	/**
	 * 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);
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		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);
		}
	}
	/**
	 * Actually load the specified document using the configured DocumentLoader.
	 * @param inputSource the SAX InputSource to read from
	 * @param resource the resource descriptor for the XML file
	 * @return the DOM Document
	 * @throws Exception when thrown from the DocumentLoader
	 * @see #setDocumentLoader
	 * @see DocumentLoader#loadDocument
	 */
	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

	/**
	 * Determine the validation mode for the specified {@link Resource}.
	 * If no explicit validation mode has been configured, then the validation
	 * mode gets {@link #detectValidationMode detected} from the given resource.
	 * <p>Override this method if you would like full control over the validation
	 * mode, even when something other than {@link #VALIDATION_AUTO} was set.
	 * @see #detectValidationMode
	 */
	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)获取对XML文件的验证模式

(2)加载XML文件并得到对应的Document

(3)根据返回的Document注册Bean信息


验证模式

调用了detectValidationMode(),在这里将自动检测验证模式的工作委托给专门处理类XmlValidationModeDetector.detectValidationMode()

	/**
	 * Detect which kind of validation to perform on the XML file identified
	 * by the supplied {@link Resource}. If the file has a {@code DOCTYPE}
	 * definition then DTD validation is used otherwise XSD validation is assumed.
	 * <p>Override this method if you would like to customize resolution
	 * of the {@link #VALIDATION_AUTO} mode.
	 */
	protected int detectValidationMode(Resource resource) {
		if (resource.isOpen()) {
			throw new BeanDefinitionStoreException(
					"Passed-in Resource [" + resource + "] contains an open stream: " +
					"cannot determine validation mode automatically. Either pass in a Resource " +
					"that is able to create fresh streams, or explicitly specify the validationMode " +
					"on your XmlBeanDefinitionReader instance.");
		}

		InputStream inputStream;
		try {
			inputStream = resource.getInputStream();
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
					"Did you attempt to load directly from a SAX InputSource without specifying the " +
					"validationMode on your XmlBeanDefinitionReader instance?", ex);
		}

		try {
			return this.validationModeDetector.detectValidationMode(inputStream);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
					resource + "]: an error occurred whilst reading from the InputStream.", ex);
		}
	}
	/**
	 * 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.
		try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
			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;
		}
	}

获取Document

XmlBeanFactoryReader对于文档读取并没有亲力亲为,而是委托给了DocumentLoader去执行,真正调用的是DefaultDocumentLoader

public interface DocumentLoader {

	/**
	 * Load a {@link Document document} from the supplied {@link InputSource source}.
	 * @param inputSource the source of the document that is to be loaded
	 * @param entityResolver the resolver that is to be used to resolve any entities
	 * @param errorHandler used to report any errors during document loading
	 * @param validationMode the type of validation
	 * {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_DTD DTD}
	 * or {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_XSD XSD})
	 * @param namespaceAware {@code true} if support for XML namespaces is to be provided
	 * @return the loaded {@link Document document}
	 * @throws Exception if an error occurs
	 */
	Document loadDocument(
			InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
			throws Exception;

}
public class DefaultDocumentLoader implements DocumentLoader {

	/**
	 * 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.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		return builder.parse(inputSource);
	}

}

解析及注册BeanDefinitions

当文件被转换为Document后,程序已经拥有了XML文档文件的Document实例对象,就会被引入到registerBeanDefinition方法.

	/**
	 * 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;
	}
	/**
	 * 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;
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}

核心逻辑底部doRegisterBeanDefinitions(root)

	/**
	 * Register each bean definition within the given root {@code <beans/>} element.
	 */
	@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
	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)) {
                                //处理profile属性
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
                
                //解析前处理,留给子类实现
		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
                //解析后处理,留给子类实现
		postProcessXml(root);

		this.delegate = parent;
	}

parseBeanDefinitions() 处理了profile后就可以进行XML的读取了

	/**
	 * 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) {

                //对Bean的处理
		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)) {
                                                //对Bean的处理
						parseDefaultElement(ele, delegate);
					}
					else {
                                                //对Bean的处理
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值