spring的整体架构:
DefaultListableBeanFactory是整个bean加载的核心部分,它是Spring注册及加载bean的默认实现。
test:
bean.xml中有很多bean的定义:
<!-- bean 标签演示 -->
<bean id="m416" class="com.muse.springdemo.entity.Gun">
<property name="name" value="M416"/>
<property name="bulletNums" value="45"/>
<property name="desc" value="非常好用的一把枪"/>
</bean>
private static BeanFactory beanFactory;
private static ApplicationContext applicationContext;
static {
beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
}
(代码块1)
XmlBeanFactory继承自DefaultListableBeanFactory,不同的地方是在XmlBeanFactory中使用了自定 义的XML读取器XmlBeanDefinitionReader,实现了个性化的读取。
XmlBeanFactory():
@Deprecated
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader;
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, (BeanFactory)null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
}
}
(代码块2)
在代码块1中调用的【new XmlBeanFactory(new ClassPathResource("bean.xml"))】是XmlBeanFactory类中的第一个构造方法,该构造方法的实现是第二个构造方法,第二个构造方法的super指代的是DefaulListableBeanFactory类。
reader是用来读xml相关配置文件的,将xml中定义的bean注入到spring里。
XML配置文件的读取是Spring中重要的功能,而XmlBeanDefinitionReader可以实现该功能。这个类的继承关系:
XmlBeanDefinitionReader 和 AbstractBeanDefinitionReader就是将配置文件转换为definition。
类名 | 解释 |
ResourceLoader | 定义资源加载器,主要应用于根据给定的资源文件地址返回对应的 Resource |
DocumentLoader | 定义从资源文件加载到转换为Document的功能 |
BeanDefinitionDocumentReader | 定义读取Document并注册BeanDefinition功能 |
BeanDefinitionParserDelegate | 定义解析Element的各种方法 |
EnvironmentCapable | 定义获取Environment方法 |
BeanDefinitionReader | 主要定义资源文件读取并转换为BeanDefinition的各个功能 |
AbstractBeanDefinitionReader | 对EnvironmentCapable、BeanDefinitionReader类定义的功能进行 实现 |
创建 XmlBeanFactory
当我们希望从Spring中获取到bean时,可以先传入配置文件名称——bean.xml,创建 XmlBeanFactory实例对象,然后再调用getBean方法获得相应的bean实例。即:
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
Gun gun = beanFactory.getBean("m416", Gun.class);
在获得BeanFactory实例对象的过程中,其实大体是经历了如下的几个步骤:
对资源文件进行加载 — — Resource
可以看到,第一步是获得了ClassPathResource。那么,Resource是Spring用于封装 底层资源的——File、URL、Classpath等。对于不同来源的资源文件都有相应的Resource 实现。
① 针对文件资源:FileSystemResource
② 针对Classpath资源:ClassPathResource
③ 针对URL资源:UrlResource
④ 针对InputStream资源:InputStreamResource
⑤ 针对Byte数组资源:ByteArrayResource ……
我们在日常开发工作中,如果需要对资源文件进行加载,也可以直接使用Spring提供的 XxxResource类。比如,我们要加载bean.xml文件,则可以将其先封装为Resource,然后 再调用getInputStream(),就可以获得到输入流了。那么针对于输入流的后续操作,与我们以往的处理方式是一样的。当然,除了能从Resource中获得InputStream之外,还可以 获得File、URI和URL等。
利用 loadBeanDefinitions(resource) 加载配置:
loadBeanDefinitions(encodeResource) 执 行 步 骤 :
本方法就是针对EncodedResource实例对象进行bean的加载。具体执行如下3个步骤:
① 将入参encodedResource保存到currentResources中,用于记录当前被加载的资源。 如果发现已经存在了,则抛异常,终止资源加载。
② 从encodedResource中获得输入流InputStream,并创建inputSource实例对象。如 果在encodedResource中配置了编码(encoding),则为inputSource配置该编码。
③ 调用doLoadBeanDefinitions(...)方法从资源中加载bean。
需要注意的一点是,InputSource不是Spring提供的类,它的全路径名是 org.xml.sax.InputSource,用于通过SAX读取XML文件的方式来创建InputSource对象
classPathResource()类:
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();
}
XmlBeanFactory类中:this.reader.loadBeanDefinitions(resource);
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}
EncodedResource只是做了对字符集和编码的支持:
public EncodedResource(Resource resource) {
this(resource, (String)null, (Charset)null);
}
private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.encoding = encoding;
this.charset = charset;
}
loadBeanDefinitions(Resource resource):
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) { // {@code true} if this set did not already contain the specified element 如果集合不包含该元素,返回true,如果已经包含了,返回false,加上!,则返回true,抛出异常;如果不包含,则把2资源加入set,并在!的作用下返回false,不抛异常。
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var6;
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
Throwable var4 = null;
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
return var6;
}
}
doLoadBeanDefinitons(…)执行:真正地实现加载的一些行为:
在这个方法中,主要做了 两件事件:
① 加载XML配置文件,然后将其封装为Document实例对象。(Xml --> Resource--> Document --> BeanDefinition)
② 根据Document实例和Resource实例,执行Bean的注册。
this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
Document doc = this.doLoadDocument(inputSource, resource);
int count = this.registerBeanDefinitions(doc, resource);
return count;
this.doLoadDocument(inputSource, resource);
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// this.isNamespaceAware() 默认是false
return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
}
this.getValidationModeForResource(resource):
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = this.getValidationMode(); // 1
if (validationModeToUse != 1) {
return validationModeToUse;
} else {
int detectedMode = this.detectValidationMode(resource); // resource包含的是bean.xml文件
return detectedMode != 1 ? detectedMode : 3;
}
}
detectValidationMode(Resource resource):
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.");
} else {
InputStream inputStream;
IOException ex;
try {
inputStream = resource.getInputStream();
}
try {
return this.validationModeDetector.detectValidationMode(inputStream);
}
}
}
detectValidationMode(inputStream)
public int detectValidationMode(InputStream inputStream) throws IOException {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
Throwable var3 = null;
int var6;
try {
boolean isDtdValidated = false;
String content;
// 一行一行遍历bean.xml文件
while((content = reader.readLine()) != null) { // 配置文件的每一行:例如第一行:<?xml version="1.0" encoding="UTF-8"?>
content = this.consumeCommentTokens(content);
if (!this.inComment && StringUtils.hasText(content)) {
if (this.hasDoctype(content)) { // return content.contains("DOCTYPE");
isDtdValidated = true;
break;
}
if (this.hasOpeningTag(content)) {
break;
}
}
}
var6 = isDtdValidated ? 2 : 3;
}
return var6;
}
}
xml 配 置 文 件 模 式 — — ValidationMode
DTD(Document Type Definition):它是一种XML约束模式语言,要使用DTD验证模式 的时候需要在XML文件的头部声明,并且它引用的是后缀名为.dtd的文 件。如下所示:
XSD(XML Schemas Definition):用于描述XML文档的结构。它引用的是后缀名为.xsd 的文件。如下所示:
doLoadDocument(InputSource inputSource, Resource resource) 中的.getEntityResolver():
protected EntityResolver getEntityResolver() {
// entityResolver 默认为空
if (this.entityResolver == null) {
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader != null) {
// 内部依然是将resourceLoader传给DelegatingEntityResolver去创建
this.entityResolver = new ResourceEntityResolver(resourceLoader);
} else {
this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
}
}
return this.entityResolver;
}
最终都是要通过调用DelegatingEntityResolver的构造方法,其内部实现:
① BeansDtdResolver:是直接截取systemId最后的xx.dtd,然后去当前路径下寻找。
② PluggableSchemaResolver:是默认到META-INF/spring.schemas文件中找到 systemId所对应的XSD文件并加载。
DelegatingEntityResolver():
public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
this.dtdResolver = new BeansDtdResolver();
this.schemaResolver = new PluggableSchemaResolver(classLoader);
}
BeansDtdResolver():加载spring-beans.dtd
private static final String DTD_EXTENSION = ".dtd";
private static final String DTD_NAME = "spring-beans";
private static final Log logger = LogFactory.getLog(BeansDtdResolver.class);
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
if (systemId != null && systemId.endsWith(".dtd")) {
int lastPathSeparator = systemId.lastIndexOf(47);
int dtdNameStart = systemId.indexOf("spring-beans", lastPathSeparator);
if (dtdNameStart != -1) {
String dtdFile = "spring-beans.dtd";
try {
Resource resource = new ClassPathResource(dtdFile, this.getClass());
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
return source;
}
}
}
return null;
}
public和systemId是什么:
在接口EntityResolver中,只声明了一个方法resolveEntity(String publicId, String systemId),如果是下图中的XSD的配置文件,那么:
publicId=null
systemId=http://www.springframework.org/schema/beans/spring-beans.xsd
如果是下图中的DTD的配置文件,那么:
publicId=-//SPRING//DTD BEAN 2.0//EN
systemId=https://www.springframework.org/dtd/spring-beans-2.0.dtd
PluggableSchemaResolver(classLoader):
public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
this.schemaMappingsLocation = "META-INF/spring.schemas";
}
doLoadDocument(InputSource inputSource, Resource resource) 的 .loadDocument()
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
Document doc = this.doLoadDocument(inputSource, resource);
int count = this.registerBeanDefinitions(doc, resource);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
registerBeanDefinitions(doc, resource):返回新加载了几个bean的定义
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader(); // 创建DefaultBeanDefinitionDocumentReader对象
int countBefore = this.getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource)); // 解析并注册bean
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
由于BeanDefinitionDocumentReader只是接口,所以通过createBeanDefinitionDocumentReader()方法其实创建的是它的实现类—— DefaultBeanDefinitionDocumentReader。
.createBeanDefinitionDocumentReader():
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return (BeanDefinitionDocumentReader)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) {
NoSuchMethodException ex = var3;
Constructor<T> ctor = findPrimaryConstructor(clazz);
if (ctor != null) {
return instantiateClass(ctor);
} else {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
} catch (LinkageError var4) {
LinkageError err = var4;
throw new BeanInstantiationException(clazz, "Unresolvable class definition", err);
}
}
}
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
.registerBeanDefinitions(doc, this.createReaderContext(resource)):
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.doRegisterBeanDefinitions(doc.getDocumentElement());
}
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate; // 默认为空
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile"); // 默认为空
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
this.preProcessXml(root); // 默认为空
this.parseBeanDefinitions(root, this.delegate);
this.postProcessXml(root); // 默认为空
this.delegate = parent;
}
profile最主要的目的就是可以区分不同的环境,进而对不同环境进行配置。例如在dev、 test、preonline、online等环境有各自的配置文件,我们就可以通过设置profile来在特定 的环境下读取对应的配置文件。使用方式如下所示:
delegate为空,创建一个:
protected BeanDefinitionParserDelegate createDelegate(XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
delegate.initDefaults(root, parentDelegate);
return delegate;
}
public void initDefaults(Element root, @Nullable BeanDefinitionParserDelegate parent) {
this.populateDefaults(this.defaults, parent != null ? parent.defaults : null, root);
this.readerContext.fireDefaultsRegistered(this.defaults);
}
this.delegate.isDefaultNamespace(root):判断是不是默认的命名空间
public boolean isDefaultNamespace(@Nullable String namespaceUri) {
return !StringUtils.hasLength(namespaceUri) || "http://www.springframework.org/schema/beans".equals(namespaceUri);
}
this.parseBeanDefinitions(root, this.delegate):
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes(); // 获得bean的列表?
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)) {
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
标签解析操作:
要执行xml解析的关键方法parseBeanDefinitions(root, this.delegate),它会根据表空间 来判断,是进行默认标签解析还是自定义标签解析。相关源码如下所示:
如果命名空间URI等于 "http://www.springframework.o rg/schema/beans"或者没有配置命名空间URI,则表示是默认表 空间,否则是自定义表空间
默认标签:<bean id = "m416" class = "com.muse.springbootdemo.entity.Gun">
自定义标签:<tx:annotation-driven>