《Spring技术内幕》笔记-第二章 IoC容器的实现
简单介绍
1,在Spring中,SpringIoC提供了一个主要的JavaBean容器。通过IoC模式管理依赖关系。并通过依赖注入和AOP切面增强了为JavaBean这样子的POJO提供事务管理,生命周期管理等功能。
2,Spring IoC的设计中,主要包括两个基本的容器系列:
-1,BeanFactory系列。该序列实现了容器的基本功能。
-2。ApplicationContext应用上下文。
Spring IoC容器的设计
1,例如以下图,IoC容器的接口设计图。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
2,简单介绍
-1,从BeanFactory到HierarchicalbeanFactory再到ConfigurableBeanFactory是一条基本的BeanFactory设计路径。BeanFactory定义了基本的IoC容器接口。
HierarchicalBeanFactory接口继承了BeanFactory接口,并再其基础上添加了getParentBeanFactory()接口功能,使得BeanFactory具备双亲IoC管理功能。ConfigurableBeanFactory主要定义了对BeanFactory的配置功能。
-2。以ApplicationContext为核心的接口设计。
BeanFactory的应用场景
BeanFactory是最主要的IoC容器。
用户在使用容器的时候能够通过&符来获取FactoryBean本身。FactoryBean跟普通Bean不同。通过BeanFactory类的getBean方法直接获取到的并非该FactoryBean的实例,而是 该FactoryBean中方法getObject返回的对象。
但我们能够通过其他途径获取到该FactoryBean的实例。方法就是在通过 getBean方法获取实例时在參数name前面加上“&”符号就可以。
String FACTORY_BEAN_PREFIX = "&";
1。BeanFactory的主要接口,以及其作用例如以下:
-1。容器推断是否包括指定名字的bean。
boolean containsBean(String name);
-2,推断指定的bean是否是prototype类型。
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
-3。推断指定的bean是否是singleton。
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
-4,推断指定的bean是否与给定类型相匹配。
boolean isTypeMatch(String name, Class<? > targetType) throws NoSuchBeanDefinitionException;
-5,获取指定Bean的类型。
Class<? > getType(String name) throws NoSuchBeanDefinitionException;
-6。指定bean的全部别名。
String[] getAliases(String name);
2,BeanFactory的容器设计原理。
BeanFactory接口提供了使用IoC的规范,而且Spring实现了一系列容器的实现供开发者使用。
以XmlBeanFactory为例。例如以下为XmlBeanFactory的继承结构图。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
下面是XmlBeanFactory的代码,在Spring 4中。该类已经不推荐被使用了。
@Deprecated
@SuppressWarnings({"serial", "all"})
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* 通过给定的资源创建一个XmlBeanFactory实例。
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* Create a new XmlBeanFactory with the given input stream,
* which must be parsable using DOM.
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
该类继承了DefaultListableBeanFactory类。DefaultListableBeanFactory该类实现一个最为基础的IoC容器。
例如以下是一个简单的使用方法举例:
ClassPathResource re = new ClassPathResource("bean.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xdr = new XmlBeanDefinitionReader(beanFactory);
xdr.loadBeanDefinitions(re);
System.out.println(xdr.getRegistry());
ApplicationContext的应用场景
1。 ApplicationContext在BeanFactory的基础上添加了非常多新的特性:
-1。支持不同的信息源。ApplicationContext扩展了MessageSource接口,这些为国际化提供服务。
-2,訪问资源。对ResourceLoader和Resource的支持。能够从不同的地方获取到资源。
-3。支持应用事件。
继承ApplicationEventPublisher,从而引入的事件机制。
与Bean的生命周期结合,方便管理Bean。
-4,在ApplicationContext添加附加服务。
2,ApplicationContext设计原理,以FileSystemApplicationContext为例。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
在实例化该应用中,调用了AbstractApplicationContext的refresh方法。该方法是IoC容器启动的主要方法。
另一个主要方法是:
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
该方法通过FileSystemResource获取资源路径。
IoC容器的初始化过程
简单来说IoC的初始化是由AbstractApplicationContext的refresh方法实现的。整个启动过程包含三个部分。BeanDefinition的Resource定位、加载和注冊三个基本部分。Spring将三个模块分开。并使用不同的模块完毕。
第一个过程是Resource定位过程。这个Resource定位是指Spring找到我们定义的Bean配置的xml文件。
第二步。BeanDefinition的加载。这个过程是把用户定义好的Bean表示成IoC容器内部的数据结构,而这个数据结构就是BeanDefinition。详细说。BeanDefination实际就是POJO在容器中的抽象,通过这个BeanDefinition定义的数据结构,使IoC容器可以方便的对POJO对象。也就是Bean进行管理。
第三步,是向IoC容器注冊这些BeanDefinition的过程。这个过程通过调用BeanDefinationRegistry接口实现。
这个注冊过程把加载过程(第二步)得到的BeanDefinition向容器注冊。IoC内部,将BeanDefinition注入到一个HashMap中去,IoC容器就是通过HashMap来持有这些BeanDefinition数据的。
一般,IoC的初始化过程,不包含依赖注入。依赖注入一般发生在应用第一次通过getBean向容器获取Bean的时候。
1,BeanDefinition的Resource定位。
在使用DefaultListableBeanFactory的时候,首先须要使用Resource来进行资源定位容器使用的BeanDefinition。
使用ClassPathResource进行资源定位。
ClassPathResource re = new ClassPathResource("bean.xml");
在此处定义的资源文件不能被DefaultListableBeanFactory直接使用,通过BeanDefinitionReader来读取。
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xdr = new XmlBeanDefinitionReader(beanFactory);
xdr.loadBeanDefinitions(re);
在此处我们发现使用比較麻烦。所以使用ApplicationContext,由于在ApplicationContext中。Spring为我们提供了一系列的资源定位,比方FileSystemXmlApplicationContext。
以FileSystemXmlApplicationContext为例,解说相应的源代码。
该构造函数通过文件路劲来生成相应的FileSystemXmlApplicationContext
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
通过refresh方法来加载BeanDefinition。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
通过文件系统来获取资源。
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
2,BeanDefinition的加载和解析。
对IoC容器来说。加载过程相当于把BeanDefinition在IoC容器中转换成一个Spring内部数据结构的过程。IoC对对象的管理都是通过对其持有的BeanDefination进行相关操作来实现的。
这些BeanDefinition是通过HashMap来维护的。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
如上代码,refresh()方法启动了容器初始化。是加载BeanDefination的入口方法。
refresh()详细的代码结构例如以下:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//在子类中启动refreshBeanFactory()的地方。
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.
//设置BeanFactory的后置处理。
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//调用BeanFactory的后置处理器。这些处理器是在Bean定义中向容器注冊的
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//注冊Bean的后处理器。在Bean的创建过程中调用
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
//对上下文中的消息源初始化
initMessageSource();
// Initialize event multicaster for this context.
//初始化上下文中的时间机制
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//初始化其它Bean
onRefresh();
// Check for listener beans and register them.
//检查监听bean并注冊
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//实例化全部(non-lazy-init) 单例。
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//公布容器事件。结束
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
//防止Bean资源占用,销毁已经创建的单例。
destroyBeans();
// Reset 'active' flag.
//重置active标志
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
-1。prepareRefresh():
/**
* Prepare this context for refreshing, setting its startup date and
* active flag as well as performing any initialization of property sources.
*/
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
synchronized (this.activeMonitor) {
this.active = true;
}
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
}
-2。启动refreshBeanFactory():
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/**
* Tell the subclass to refresh the internal bean factory.
* @return the fresh BeanFactory instance
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
终于调用,AbstractRefreshableApplicationContext中的refreshBeanFactory()方法。
/**
* This implementation performs an actual refresh of this context's underlying
* bean factory, shutting down the previous bean factory (if any) and
* initializing a fresh bean factory for the next phase of the context's lifecycle.
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {//假设已经存在BeanFactory
destroyBeans();//销毁
closeBeanFactory();
}
try { //创建IoC容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);//加载BeanFactory。抽象方法
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
这里调用的loadBeanDefinations()是一个抽象方法,详细实如今AbstractXmlApplicationContext。
/**
* Loads the bean definitions via an XmlBeanDefinitionReader.
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #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.setEnvironment(this.getEnvironment());
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);
}
接着就是调用loadBeanDefinations的地方,首先得到BeanDefinition信息的Resource定位,然后调用XmlBeanDefinitionReader来读取,详细过程托付给BeanDefinitionReader来完毕的。
XML文件则是通过XmlBeanDefinitionReader来加载BeanDefination到容器中。
注:在XmlBeanDefinationReader中找到的loadBeanDefination()方法。与书中的代码块不同。
此处可能是Spring版本号的问题。
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 {//输入流读取资源
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}//调用解析XML文件
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();
}
}
}
doLoadBeanDefinitions()方法:
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);
}
}
registerbeanDefinitions()方法代码:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//获取XML文档读取
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
//详细解析过程
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinition加载过程中,首先调用XML解析器,生成XML解析对象。然后再进行详细的解析。
createBeanDefinitionDocumentReader()方法:
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
获取到详细的解析器。然后托付给BeanDefinitionParserDelegate来完毕详细的解析。例如以下查看DefaultBeanDefinitionDocuementReader中的processBeanDefinition(Element , BeanDefinitionParserDelegate)方法:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
//BeanDefinitionHolder是对BeanDefinition的封装。
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//向IoC容器注冊解析到的BeanDefinition。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
BeanDefinitionHolder:
public class BeanDefinitionHolder implements BeanMetadataElement {
private final BeanDefinition beanDefinition;
private final String beanName;
private final String[] aliases;
........
在BeanDefinitionParserDelegate类中,定义了对各种SpringBean类的处理。
查看其相应的parseBeanDefinitionElement方法:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);//bean元素中的ID
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);//bean元素中的name
List<String> aliases = new ArrayList<String>();//aliases
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);//具体解析过程,返回AbstractBeanDefinition对象,该对象定义了Bean的基本属性
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
查看详细的解析代码,parseBeanDefinitionEleent方法:
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//生成BeanDefinition对象
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//解析属性,并设置Description信息
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析bean元素信息
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//构造函数
parseConstructorArgElements(ele, bd);
//成员变量
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
return null;
}
当中相应的各种异常信息,可能我们在编程工作中常常遇到。
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);//解析
}
}
}
/**
* Parse a property element.
*/
public void parsePropertyElement(Element ele, BeanDefinition bd) {
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);//获取property名字
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
this.parseState.push(new PropertyEntry(propertyName));
try {
if (bd.getPropertyValues().contains(propertyName)) {//是否包括
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
Object val = parsePropertyValue(ele, bd, propertyName);//解析
PropertyValue pv = new PropertyValue(propertyName, val);//获取值
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
String elementName = (propertyName != null) ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element";
// Should only have one child element: ref, value, list, etc.
NodeList nl = ele.getChildNodes();//xml文档解析
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we're looking for.
if (subElement != null) {
error(elementName + " must not contain more than one sub-element", ele);
}
else {
subElement = (Element) node;
}
}
}
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);//是否是ref引用
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);//值引用
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
if (hasRefAttribute) {//ref引用
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
}//value 引用
else if (hasValueAttribute) {
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
}//假设还有子元素。触发对子元素的解析
else if (subElement != null) {
return parsePropertySubElement(subElement, bd);
}
else {
// Neither child element nor "ref" or "value" attribute found.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
3,BeanDefinition在IoC容器中的注冊
BeanDefinition完毕加载和解析过程后。须要对其进行注冊操作。
注冊是在DefaultListableBeanFactory中,通过HashMap来加载Beandefinition的。
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
调用顺序:XmlBeanDefinitionReader调用loadBeanDefinitions(EncodedResource)方法,然后到registerBeanDefinitions()方法。在该方法中registerBeanDefinitions()方法被调用。在DefaultListableBeanFactory中,实现了BeanDefinitionRegistry接口。
@Override
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) {//是否存在同样名字
BeanDefinition 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 (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
" with a framework-generated bean definition ': replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}
else {//注冊bean。将bean的name放入List
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
//map中增加
this.beanDefinitionMap.put(beanName, beanDefinition);
}
resetBeanDefinition(beanName);
}
在使用的IoC容器的DefaultListableBeanFactory中已经建立了Bean的配置信息,并且这些BeanDefinition已经能够被容器使用,他们都在beanDefinitionMap中被检索和使用。容器的作用就是对这些信息进行维护和处理。
IoC容器的依赖注入
依赖注入是用户第一次向Ioc容器索要Bean的时候触发的。除非通过lazy-init控制Bean的记载时机。
从DefaultListableBeanFactory的基类AbstractBeanFactory的getBean方法開始查看实现。
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return doGetBean(name, requiredType, null, false);
}
@Override
public Object getBean(String name, Object... args) throws BeansException {
return doGetBean(name, null, args, false);
}
查看doGetBean方法:
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
//从缓存入手。处理单例bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
//对FactoryBean的处理,获取FactoryBean的相关实例。
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
//减产是否已经存在相应的BeanDefinition对象
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
//依据Bean的名字获取BeanDefinition
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
//获取当前bean的全部依赖bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dependsOnBean : dependsOn) {
if (isDependent(beanName, dependsOnBean)) {
throw new BeanCreationException("Circular depends-on relationship between '" +
beanName + "' and '" + dependsOnBean + "'");
}
registerDependentBean(dependsOnBean, beanName);//注冊依赖bean
getBean(dependsOnBean);//获取bean的递归调用
}
}
// Create bean instance.
//创建单例bean
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {// property bean
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
String scopeName = mbd.getScope();//其它scope
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; " +
"consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
//检查bean的类型。
if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
try {
return getTypeConverter().convertIfNecessary(bean, requiredType);
}
catch (TypeMismatchException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to convert bean '" + name + "' to required type [" +
ClassUtils.getQualifiedName(requiredType) + "]", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}
这种方法就是依赖注入的入口,由于他触发了依赖注入。
虽然能够以最简单的方法来描写叙述Spring IoC容器,即Spring容器就是一个HashMap,通过HashMap来管理BeanDefinition对象。
在getBean()的时候,会触发createBean()来进创建须要的Bean对象。
终于的调用,到AbstractAutowireCapableBeanFactory的createBean()方法,代码例如以下:
/**
* Central method of this class: creates a bean instance,
* populates the bean instance, applies post-processors, etc.
* @see #doCreateBean
*/
@Override
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
if (logger.isDebugEnabled()) {
logger.debug("Creating instance of bean '" + beanName + "'");
}
// Make sure bean class is actually resolved at this point.
//推断须要创建的Bean是否可实例化,这个类是否可通过类装载器来加载
resolveBeanClass(mbd, beanName);
// Prepare method overrides.
try {
mbd.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
//假设Bean配置了BeanPostProcessors,则返回代理对象
Object bean = resolveBeforeInstantiation(beanName, mbd);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
//创建bean
Object beanInstance = doCreateBean(beanName, mbd, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
再查看doCreateBean()方法:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {//假设是单例的。则移除缓存中的同name bean
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {//创建Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class<? > beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
// Initialize the bean instance.初始化bean。通常在此处发生依赖注入
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
与依赖注入相关的两个方法:createBeanInstance和populateBean。
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
// Make sure bean class is actually resolved at this point.
//确认须要创建实例的类能够实例化
Class<?> beanClass = resolveBeanClass(mbd, beanName);
if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}
//工厂方法实例化
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// Shortcut when re-creating the same bean...
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
if (autowireNecessary) {
return autowireConstructor(beanName, mbd, null, null);
}
else {
return instantiateBean(beanName, mbd);
}
}
//构造函数实例化
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// 使用无參构造函数
return instantiateBean(beanName, mbd);
}
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
//使用CGLIB对bean进行实例化。
try {
Object beanInstance;
final BeanFactory parent = this;
if (System.getSecurityManager() != null) {
beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
return getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
}, getAccessControlContext());
}
else {
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
}
}
在Bean实例化后,就是相关的依赖关系。
依赖注入的发生是在BeanWrapper的setPropertyValues中实现。详细实现是在BeanWrapper的子类,BeanWrapperImpl中实现。
以上便是整个IoC过程创建Bean的总体思路。与书中相比,省略了部分代码。
ApplicationContext和Bean的初始化及销毁
ApplicationContext的启动是在AbstractApplicationContext中实现。
相同的销毁操作是在doClose()方法中完毕。
protected void doClose() {
boolean actuallyClose;
synchronized (this.activeMonitor) {
actuallyClose = this.active && !this.closed;
this.closed = true;
}
if (actuallyClose) {
if (logger.isInfoEnabled()) {
logger.info("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
try {
getLifecycleProcessor().onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
// Destroy all cached singletons in the context's BeanFactory.
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
synchronized (this.activeMonitor) {
this.active = false;
}
}
}
Bean的销毁和创建,Spring通过IoC管理Bean的生命周期来实现。
Spring中Bean的生命周期包括:
-1。Bean实例的创建。
-2,为Bean实例设置属性。
-3。调用Bean的初始化方法。
-4,通过IoC获取Bean。
-5,当容器关闭的时候调用bean的销毁方法。
总结
BeanDefinition的定位。对IoC容器来说,它为管理POJO直接的关系提供了帮助,但也要依据Spring的定义规则提供Bean定义信息。在Bean定义方面,Spring为用户提供了非常大的灵活性。在初始化过程中,首先须要定义到这些有效地Bean定义信息。这里Spring使用Resource接口来统一这些信息。而定位由ResourceLoader完毕。
容器的初始化。容器的初始化过程是在refresh()方法中完毕的。这个refresh()相当于容器的初始化函数。在初始化中,比較重要的就是对Bean信息的加载和注冊功能。
《Spring技术内幕》笔记-第二章 IoC容器的实现的更多相关文章
- Mysql技术内幕-笔记-第二章 数据类型
第二章 数据类型 Mysql中尽量不要使用UNSIGNED,因为可能会带来一些意想不到的效果 SHOW CREATE TABLE tablename\G;查看表的创建语句 ZEROFILL会将宽度小于 ...
- Spring技术内幕笔记2--我懒不写了哈哈哈哈。
目录 1.1 关于IOC容器设计的线路区别 1.1.1 BeanFactory 1.1.2 ApplicationContext 2.1 FileSystemXmlApplicationContext ...
- Spring技术内幕:SpringIOC原理学习总结
前一段时候我把Spring技术内幕的关于IOC原理一章看完,感觉代码太多,不好掌握,我特意又各方搜集了一些关于IOC原理的资料,特加深一下印象,以便真正掌握IOC的原理. IOC的思想是:Spring ...
- Java开发工程师(Web方向) - 04.Spring框架 - 第2章.IoC容器
第2章.IoC容器 IoC容器概述 abstract: 介绍IoC和bean的用处和使用 IoC容器处于整个Spring框架中比较核心的位置:Core Container: Beans, Core, ...
- 《spring技术内幕》读书笔记(1)——什么是POJO模式
今天在看<spring技术内幕>,第一章中多次提到了使用POJO来完成开发,就百度了一下,在此保留 1. 什么是POJO POJO的名称有多种,pure old java obje ...
- Spring技术内幕:设计理念和整体架构概述(转)
程序员都很崇拜技术大神,很大一部分是因为他们发现和解决问题的能力,特别是线上出现紧急问题时,总是能够快速定位和解决. 一方面,他们有深厚的技术基础,对应用的技术知其所以然,另一方面,在采坑的过程中不断 ...
- Servlet工作原理解析 《深入分析java web 技术内幕》第九章
参考关于servblet的相关文章 侧重概况:https://blog.csdn.net/levycc/article/details/50728921 ibm的相关:https://www.ibm. ...
- SQL Server技术内幕笔记合集
SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...
- Android群英传笔记——第二章:Android开发工具新接触
Android群英传笔记--第二章:Android开发工具新接触 其实这一章并没什么可讲的,前面的安装Android studio的我们可以直接跳过,如果有兴趣的,可以去看看Google主推-Andr ...
随机推荐
- 关于BUG
1.BUG的理解 2.提高BUG report的技巧
- [原创]Toolbar setNavigationIcon无效
最近在做一个Toolbar,setNavigationIcon()这个方法一直无效,说什么的都有,什么getSupportActionBar().setNavigationIcon()的,说设置sty ...
- C#入门经典 Chapter3 变量和表达式
3.1 C#基本语法 分号结束语句 花括号字符不需要附带分号 缩进 注释:/*....*/,//,/// 区分大小写 3.2 C#控制台应用程序的基本结构 namespace Chapter3 ...
- IIS添加映射配置
这种问题主要出现在使用应用程序级别的地址重写.如果你将一个动态的地址重写成虚拟的其它扩展名或者不带扩展名的地址,通常在IIS5.1和II6.0中,访问这样一个实际不存在的地址,首先会被Web服务器返回 ...
- 详解HashMap数据结构实现
HashMap的设计是由数组加链表的符合数据结构,在这里用自己的语言以及结合源码去总结一下,如果有不对的地方希望评论指正,先拱手谢谢. HashMap是日常中非常常用的一种数据结构,我们要想深入了解学 ...
- Operation Queues 面向对象的封装
Operation Queues An operation queue is the Cocoa equivalent of a concurrent dispatch queue and is im ...
- cstringlist
CStringList类成员 构造 CStringList 构造一个空的CString对象列表 首/尾访问 GetHead 返回此列表(不能是空的)中头部的元素 GetTail 返回此列表(不能是 ...
- kvm virt-install 使用小结
简介: virt-install 能够为KVM.Xen或其它支持libvrit API的hypervisor创建虚拟机并完成GuestOS安装. 此外,它能够基于串行控制台.VNC或SDL支持文本或图 ...
- C++ Primer(第4版)-学习笔记-第1部分:基本语言
第1章 快速入门 每个C++程序都包含一个或多个函数,而且必须有一个命名为main. main函数是唯一被操作系统显式调用的函数,main函数的返回值必须是int或者void(无返回值) 函数体是函 ...
- 爬虫文件存储:txt文档,json文件,csv文件
5.1 文件存储 文件存储形式可以是多种多样的,比如可以保存成 TXT 纯文本形式,也可以保存为 Json 格式.CSV 格式等,本节我们来了解下文本文件的存储方式. 5.1.1 TXT文本存储 将数 ...