bean是Spring中最核心的东西
bean本无特殊之处。Spring的目的就是让bean成为一个纯粹的POJO。
spring framework中使用xml配置bean-------bean的声明方式
配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="test.Student"/>
</beans>
测试
public static void main(String[] args) {
XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));
Student bean = (Student) bf.getBean("student");
System.out.println(bean);
}
虽然日常是使用ApplicationContext
这里使用Bean Factory
得知整个bean的实例化大概需要3个大逻辑
读取和验证配置文件
根据配置文件中的配置进行反射实例化
完成整个逻辑串联
源码
实现上面功能的是org.Springframework.bean.jar
Core包也是必需的
beans包的层级结构
src/main/java 用于展现Spring的主要逻辑
src/main/resources 用于存放系统的配置文件
src/test/java 主要逻辑进行单元测试
src/test/resources 存放测试用的配置文件
核心类
ioc容器之始祖
XmlBeanFactory 继承自DefaultListableBeanFactory
DefaultListableBeanFactory为整个bean加载的核心部分。是Spring注册及加载bean的默认实现
XmlBeanFactory 和DefaultListableBeanFactory 不同之处在于 XmlBeanFactory 使用了自定义XML读取器 XmlBeanDefinitionReader,实现了个性化BeanDefinitionReader读取。
DefaultListableBeanFactory继承了AbstractBeanDefinitionReader并实现了 ConfigurableListableBeanFactory
XML配置读取是Spring中重要的功能。因为大部分功能都是以配置作为切入点
容器的基础XmlBeanFactory
XmlBeanFactory初始化时序图
按照时序图看运行的类
配置文件封装 Spring的配置文件读取是通过ClassPathResource进行封装的
Java中,将不停来源的资源抽象成URL。通过注册不同的handler(URLStreamHandler)出来不同来源的资源的读取逻辑。
一般handler的类型使用不同前缀(协议,Protocol)识别,如“file","http:"等。
但是URL没有默认定义相对的Classpath或ServletContext等资源的handler
可注册自己的URLStreamHandler解析特点URL前缀(协议),如"classpath:",但是需要了解URL的实现机制,但URL没提供基本的方法。如检查资源是否存在。检查当前资源是否可读。
Spring所以使用到了自己的抽象结构:Resource接口封装底层资源。
在 org.springframework.core.io中
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
InputStreamSource 封装任何能返回InputStream的类,如FIle,Classpath下的资源和ByteArray等。
只要一个方法定义:getInputStream()。其返回一个新的InputStream对象。
Resouce接口抽象了所有Spring内部使用到的底层资源:File,URL,Classpath等。3个判定资源状态的方法。
还提供了不同资源到URL,URI,FIle类型的转换。以及获取lastModified 和getFIlename
还提供了基于当前足以创建一个相对资源的方法,createRelative()。
在错误处理中需要详细地打印出错的资源文件,getDescription(),用于在错误处理中打印信息。
对于不同来源的资源文件都有相应的Resource实现
- FileSystemResource
- ClasspathResource
- URLResource
- InputStreamResource
- ByteArrayResource
当通过Resource相关类完成了对配置文件进行封装后配置问读取工作全权交给XmlBeanDefinitionReader处理
Spring中将配置文件封装为Resource类型的实例方法后
接下来是XmlBeanFactory的初始化过程。其初始化有很多方法,Spring提供了很多构造函数。
public XmlBeanFactory(Resource resource) throws BeansException {
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的构造函数中
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
ignoreDependencyInterface方法。目的是忽略给定接口的自动装配功能
既忽略DI注入
加载Bean
loadBeanDefinitions 时序图
时序图分析:
- 封装资源文件。进入XmlBeanDefinitionReader后对参数resource使用EncodedResource类进行封装
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreExeception{
return loadBeanDefinitions(new EncodedResource(resource)):
}
- 获取输入流。从Resource中获取对应InputStream并构造InputSource
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions
*EncodedResource作用:对资源文件的编码进行处理。设置好了编码属性的时候Spring会使用相应的编码作为输入流的编码。
构造了一个有编码(encoding)的INputStreamReader。当构造好encodedResource对象后,再次传入了可复用方法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);
}
//通过属性记录已经加载的资源
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 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();
}
}
}
进入了doLoadBeanDefinitions后。因为之前对resource参数封装,其考虑到了Resource可能存在编码要求。接着,通过SAX读取XML文件的方式准备InputSource对象
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);
}
}
此代码重点
- 获取对XML文件的验证模式
- 加载XML文件,得到想对象的Document
- 根据返回的Document注册Bean信息
获取XML的验证模式
两种验证模式DTD 和XSD
DTD,Document Type Definition 。文档类型定义。是一种XML约束类型语言,XML文件验证机制。属于XML文件组成的一部分。DTD保证XML文档格式有效性。
DTD需要在XML文件的头部声明
XSD,XML Schemas Definition。XML Schema描述了XML文档的结构。保证XML文档的结构
一个声明在DOCTYPE
一个声明在内部
通过getValidationModeForResource(resource)获取对应的资源的验证模式,其包含在doLoadDocument函数里头
XmlBeanDefinitionReader.class
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
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;
}
按照注释,现在已经少用DTD了
接着根据里面的detectValidationMode(resource)读取验证
获取Document
验证模式准备后交给Document加载。委托给了DocumentLoader(接口)。主调用为DefaultDocumentLoader
DocumentLoader.class 接口
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
DefaultDocumentLoader.class 实现
@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);
}
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
loadDocument其中参数:EntityResolver。SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动注册一个实例。
eg:解析一个XML,SAX首先读取该XML文档上的生命。根据声明寻找相应的DTD定义,以便对文档进行一个验证。缺省的寻找规则(通过网络,实现声明的DTD的URI地址,下载相应的DTD声明,并进行认证)。因为网络如果不可用时,会不可用,DTD声明未找到。
EntityResolver作用为提供一个如何寻找DTD声明的方法。即由程序来实现寻找DTD声明的过程。如将DTD文件放到项目的某处,实现时,直接返回此文档到SAX。避免了网络寻找相应的声明。
entityResolver接口方法声明
InputSource resolverEntity(String publicId,String systemId)
XSD 和 DTD 两个格式分别获取的两个Id不同
Spring使用了DelegatingEntityResolver类为EntityResolver的实现类
DelegatingEntityResolver.class
@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable 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);
}
}
// Fall back to the parser's default behavior.
return null;
}
可得对于不同验证模式,Spring使用了不同解析器解析
加载DTD类型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找
加载XSD的PluggableSchemaResolver类的resolveEntity是默认到META-INF/spring.schemas找到systemid所对应得XSD文件并加载
解析及注册BeanDefinitions
文件转换为Document后,接下来提取及注册bean。
程序此时已经拥有XML文档文件得Document实例对象
DefaultBeanDefinitionDocumentReader.class
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//记录统计之前BeanDefinition的加载个数
//在实例化BeanDefinitionReader时候会将BeanDefinitionRegistry传入,,默认使用继承自DefaultListableBeanFactory的子类
int countBefore = getRegistry().getBeanDefinitionCount();
//机制及注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//记录本次加载的BeanDefinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
提取root,以root作为参数继续BeanDefinition的注册
之前在XML加载解析的准备阶段
现在开始使用doRegisterBeanDefinitions解析
DefaultBeanDefinitionDocumentReader.class
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
//处理profile属性,标注dev,prod。环境
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;
}
}
}
//解析前处理,留给子类实现
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
//解析后处理,留给子类实现
postProcessXml(root);
this.delegate = parent;
}
专门处理解析
DefaultBeanDefinitionDocumentReader.class
protected BeanDefinitionParserDelegate createDelegate(
XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
delegate.initDefaults(root, parentDelegate);
return delegate;
}
划重点
一个类要么是面向继承的设计的,要么就用final修饰
此用了模板方法
解析,并注册BeanDefinition
DefaultBeanDefinitionDocumentReader.class
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);
}
}
XML配置里面有两大Bean声明,缺省
bean id=“test” class=“test.TestBean”
自定义
tx:annotation-driven
对于根节点or子节点如果是默认命名空间的化采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析。
判断是否默认or自定义是使用node.getNamespaceURI()获取命名空间,与Spring中固定命名空间http://www.springframework.org/schema/beans 进行比对