我们都知道spring两大核心原理:IOC与AOP.
IOC:(全称:Inverse Of Control )控制反转,容器主动将资源推送给它所管理的组件,组件所做的是选择一种合理的方式接受资源。
通俗点讲:就是一个容器工厂,里面加工了各种资源对象,当我们需要的时候直接从里面取出,正是工厂模式的一个良好的展现。
IOC容器大致分为两种: BeanFactory 容器与ApplicationContext 容器,前者目前用的很少,基本已经弃用了,因为后者已经包含了后者的所有功能,另外,它增加了企业所需要的功能,比如,提供了支持国际化的文本消息 ,统一的资源文件读取方式 ,已在监听器中注册的bean的事件。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。所以下面直接从ApplicationContext容器入手。
首先我们来看下ApplicationContext 容器下面的一些实现类,
常用的是:
FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径
ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
比较来看,ClassPathXmlApplicationContext用起来更加方便快捷,所以下面就使用ClassPathXmlApplicationContext来加载bean。
ApplicationContext接口源码:
/**
* ApplicationContext是spring中较高级的容器。和BeanFactory类似,它可以加载配置文件中定义的bean,将所有的bean集中在一起,当有请求的
* 时候分配bean。 另外,它增加了企业所需要的功能,比如,从属性文件从解析文本信息和将事件传递给所指定的监听器。这个容器在org.springframework.
* context.ApplicationContext接口中定义。ApplicationContext包含BeanFactory所有的功能,一般情况下,相对于BeanFactory,ApplicationContext
* 会被推荐使用。但BeanFactory仍然可以在轻量级应用中使用,比如移动设备或者基于applet的应用程序。
*
* ApplicationContext接口关系
* 1.支持不同的信息源。扩展了MessageSource接口,这个接口为ApplicationContext提供了很多信息源的扩展功能,比如:国际化的实现为多语言版本的应用提供服务。
* 2.访问资源。这一特性主要体现在ResourcePatternResolver接口上,对Resource和ResourceLoader的支持,这样我们可以从不同地方得到Bean定义资源。
* 这种抽象使用户程序可以灵活地定义Bean定义信息,尤其是从不同的IO途径得到Bean定义信息。这在接口上看不出来,不过一般来说,具体ApplicationContext都是
* 继承了DefaultResourceLoader的子类。因为DefaultResourceLoader是AbstractApplicationContext的基类,关于Resource后面会有更详细的介绍。
* 3.支持应用事件。继承了接口ApplicationEventPublisher,为应用环境引入了事件机制,这些事件和Bean的生命周期的结合为Bean的管理提供了便利。
* 4.附件服务。EnvironmentCapable里的服务让基本的Ioc功能更加丰富。
* 5.ListableBeanFactory和HierarchicalBeanFactory是继承的主要容器。
*
* 最常被使用的ApplicationContext接口实现类:
* 1,FileSystemXmlApplicationContext:该容器从XML文件中加载已被定义的bean。在这里,你需要提供给构造器XML文件的完整路径。
* 2,ClassPathXmlApplicationContext:该容器从XML文件中加载已被定义的bean。在这里,你不需要提供XML文件的完整路径,只需正确配置CLASSPATH
* 环境变量即可,因为,容器会从CLASSPATH中搜索bean配置文件。
* 3,WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在XML文件中
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
public interface ApplicationContext
extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
/**
* 得到这个应用环境的独一无二ID
*
* @return 得到这个应用独一无二的ID,或者返回一个空值
*/
@Nullable
String getId();
/**
* 返回此上下文所属的已部署应用程序的名称。
*
* @return 得到这个应用程序的名称或者空字符串
*/
String getApplicationName();
/**
* 返回这个应用环境的显示名
*
* @return 这个应用环境的显示名,或者返回一个空值
*/
String getDisplayName();
/**
* 返回第一次加载这个应用程序的时间戳。
*
* @return 得到这个时间戳
*/
long getStartupDate();
/**
* 返回父上下文,如果没有父上下文,则返回空 上下文层次结构的根。
*
* @return
*/
@Nullable
ApplicationContext getParent();
/**
* 为该上下文公开AutowireCapableBeanFactory功能。 这通常不被应用程序代码使用,除非用于
初始化位于应用程序上下文之外的bean实例
* 注意:从4.2开始,该方法将始终抛出IllegalStateException 已关闭应用程序上下文。在当前
Spring框架版本中, 只有可刷新的应用程序上下文才会这样;由4.2起,所有申请上下文实现将被要
求遵守该规则
*/
AutowireCapableBeanFactory getAutowireCapableBeanFactory()
throws IllegalStateException;
}
例子:
新建bean类:
package MyBean;
public class Person {
private String name="mariao";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
配置spring-bean.xml
测试类:
public class TestBean {
static ApplicationContext app;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
app=new ClassPathXmlApplicationContext("spring-bean.xml");//创建了一个IOC容器
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
((AbstractApplicationContext) app).destroy();
}
@Test
public void test() {
Person p=(Person) app.getBean("person");//获取容器中的对象
System.out.println(p.getName());
}
}
当然上面的不是重点,只是为了做好准备工作,如何加载xml文件获取容器中的对象的才是我们想要了解的。
首先先查看 ClassPathXmlApplicationContext类,源码如下
/**
*独立的XML应用程序上下文,获取上下文定义文件
*从类路径中,将普通路径解释为类路径资源名
*包括包路径(例如“mypackage / myresource.txt”)。对测试套接字以及嵌入在jar中的应用程序上下文有用。
配置位置的默认值可以通过{@link #getConfigLocations}重写,配置位置可以表示像“/myfiles/context.xml”这样的具体文件。
或者像“/myfiles/*-context”这样的ant样式的模式。xml”(请参阅{@link org.springframework.util。模式细节)。
*注意:在多个配置位置的情况下,后面的bean定义将覆盖先前加载文件中定义的。这是可以通过额外的XML文件覆盖特定的bean定义。
这是一个简单的,一站式的便利应用程序上下文。
*考虑组合使用{@link GenericApplicationContext}类,使用{@link org.springframework.bean .factory.xml. xmlbeandefinitionreader}
*用于更灵活的上下文设置。
*/
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
@Nullable
private Resource[] configResources;
public ClassPathXmlApplicationContext() {
}
public ClassPathXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent)
throws BeansException {
this(configLocations, true, parent);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
-------------------------------------------------------------------------------------
/**
*通过传递的参数层层调用,最后调用的是最底层的方法
*/
public ClassPathXmlApplicationContext(String path, Class> clazz) throws BeansException {
this(new String[] {path}, clazz);
}
public ClassPathXmlApplicationContext(String[] paths, Class> clazz) throws BeansException {
this(paths, clazz, null);
}
public ClassPathXmlApplicationContext(String[] paths, Class> clazz, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
Assert.notNull(paths, "Path array must not be null");
Assert.notNull(clazz, "Class argument must not be null");
this.configResources = new Resource[paths.length];
for (int i = 0; i < paths.length; i++) {
this.configResources[i] = new ClassPathResource(paths[i], clazz);
}
refresh();
}
@Override
@Nullable
protected Resource[] getConfigResources() {
return this.configResources;
}
}
ApplicationContext app=new ClassPathXmlApplicationContext("spring-bean.xml");//源码程序入口
下面通过debug模式一步步看下是如何实现该过程的:
1.首先进入的是这个构造函数方法
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
通过this调用以下方法
/**
* 使用给定的父类创建一个新的ClassPathXmlApplicationContext,
* 从给定的XML文件加载定义。
* @param 给定的xml文件数组
* @param 是否自动刷新上下文,
* 加载所有的定义或者创建所有的单例
* 或者,在进一步配置上下文之后手动调用refresh。refresh默认是true
* @param 该程序的父类,允许为空
* @throws 如果在上下文加载失败的时候,那么抛出BeansException
* @see #refresh()
*/
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
//调用父类及以上类的构造方法
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
2 解析传入xml文件的位置参数
setConfigLocations
/**
* 设置此应用程序的路径位置
*/
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
//如果多个的话一个个进行位置解析
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
resolvePath
/**
解析给定的路径,用对应的占位符替换
*环境属性值(如有必要)。应用于配置位置。
* @param路径原始文件路径
* @返回解析后的文件路径
* @see org.springframework.core.env.Environment # resolveRequiredPlaceholders(字符串)
*/
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
这里出现了getEnvironment()方法,点进去
/**
*返回的{@code环境}在可配置的应用程序上下文形式,从而允许自定义配置环境。
* 如果没有指定,默认环境将通过 {@link # createEnvironment ()}创建
*/
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
/**创建并返回一个新的应用环境{@link StandardEnvironment}。
*子类可以覆盖此方法来提供一个自定义的环境配置接口{@link ConfigurableEnvironment}实现
*/
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
使用系统默认的StandardEnvironment的环境
/**{@link Environment}实现,适用于“标准”(即非web) *应用程序。
除了一个{@link ConfigurableEnvironment}的常用函数,
例如 *属性解析和与概要文件相关的操作,此实现配置两个 *默认属性源
*/
public class StandardEnvironment extends AbstractEnvironment {
/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
/**
*使用适合任何标准的属性源自定义属性源集
* Java环境:
* {@ value # SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME},{@ value #
* SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}
属性出现在{@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME}将优先于{@value
#SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}中的值。
*(也就是说系统环境属性源名优先于JVM虚拟机环境属性名)
* @see AbstractEnvironment # customizePropertySources (MutablePropertySources)
* @see # getSystemProperties ()
* @see # getSystemEnvironment ()
*/
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
关于Environment:可以参考这篇博客https://blog.csdn.net/andy_zhang2007/article/details/78511815
获取系统参数后,resolveRequiredPlaceholders(path);
核心代码:创建一个PlaceholderResolver解析器,然后解析其xml文件的具体属性
比如:
Properties p = System.getProperties();
p.setProperty("name", "spring-");
app=new ClassPathXmlApplicationContext("${name}bean.xml")
这种形式的,源码需要对其进行解析拼接才能获取其spring-bean.xml的具体路径
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet<>());
}
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
//1.获取路径中占位符前缀的索引
int startIndex = value.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
//2.获取最后的占位符}的索引位置
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
//3.截取${name}中的字符串name
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
//4.如果[name]集合中没有name,抛出循环占位符引用的异常
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
//5. 递归调用,继续解析placeholder(这一次是对name进行解析)
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
//6.获取placeholder的属性值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
//7.将获取到的name的属性值替换${name}
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
//8.再次获取${的索引值
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
//9.将已经解析的originalPlaceholder的值移除
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
3.通过refresh()加载beanFactory
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//加载xml文件的准备工作
//1.初始化上下文环境中的任何占位符属性源
//2. 验证所有标记为必需的属性都是可解析的
prepareRefresh();
//告诉子类刷新内部bean工厂。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//配置bean工厂,以便在此上下文中使用。例如上下文的类加载器以及后处理程序
prepareBeanFactory(beanFactory);
try {
//允许在程序子类中对bean工厂进行后期处理。
postProcessBeanFactory(beanFactory);
//调用在程序中注册为bean的工厂处理器。
invokeBeanFactoryPostProcessors(beanFactory);
// 注册bean工厂的拦截器.
registerBeanPostProcessors(beanFactory);
// 初始化国际化组件MessageSource,起支持国际化文件通常使用xml或者properties文件
initMessageSource();
// 初始化事件监听多路广播器
initApplicationEventMulticaster();
//模板方法,可被重写以添加特定于上下文的刷新工作。
//在实例化单例之前,在初始化特殊bean时调用。
onRefresh();
//检查并注册监听器。
registerListeners();
//实例化所有剩余的(非懒加载)单例。
finishBeanFactoryInitialization(beanFactory);
//最后一步:发布相应的事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
//销毁已经创建的单例,以避免挂起资源。
destroyBeans();
//重置'active'标志。
cancelRefresh(ex);
//向调用者抛出异常
throw ex;
}
finally {
//重置在spring核心缓存,因为我们
//可能再也不需要单例bean的元数据
resetCommonCaches();
}
}
}
这里的主要涉及首先验证xml文件是否正确,然后创建一个beanFactory,将解析xml文件的结果放入beanFactory,最后配置beanFatocy,对其作后续处理 。那么来具体了解下源码是如何实现xml验证与解析并获取结果的呢?
首先我们来了解下xml常用的两种验证模式:DTD以及XSD.
DTD:文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。
可以在xml的文件上方添加以下声明
XSD:以下是截取文档上的一段话
具体的内容可以查看源码spring-bean下dtd和xsd文件
好了,我们了解了xml与dtd的具体概念,以下是spring源码验证模式的具体代码
/**
* 确定指定的{@link Resource}的验证模式。
* 如果没有自定义
* @param resource
* @return
*/
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;
}
detectValidationMode(resource)代码如下:
/**
* 检测要对标识的XML文件执行哪种验证
*通过提供的{@link资源}。如果文件有{@code DOCTYPE}定义,然后使用DTD验证,否则假设XSD验证。
* @param resource
* @return
*/
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);
}
}
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;
}
//判断是否包含了DOCTYPE
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
//读取到<符号,验证模式一定是从次符号开始的
if(hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
//如果包含DOCTYPE则是DTD,否则就是XSD
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();
}
}
说明一点:这个验证不是在prepareRefresh()阶段验证的,而是在ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();这个阶段进行验证的,这个阶段核心代码如下所示:
@Override
protected final void refreshBeanFactory() throws BeansException {
//1.判断当前是否有未关闭的beanFactory
if (hasBeanFactory()) {
destroyBeans();//清除factory中的bean
closeBeanFactory();//关闭beanFactory
}
try {
//2.创建一个默认的beanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
//3.为这个bean工厂创建一个独一无二的id
beanFactory.setSerializationId(getId());
//4.自定义此上下文使用的内部bean工厂。
customizeBeanFactory(beanFactory);
//5.创建一个xml解析阅读器
(验证xml文件的正确性)
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
验证后是读取Document,其他代码这里就不一一描述了,这里核心的方法是loadDocument,
@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);
}
/*创建此bean定义阅读器的JAXP DocumentBuilder
将用于解析XML文档。可以在子类中重写,
*/
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
throws ParserConfigurationException {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
这段代码无非就是通过SAX解析xml文档,首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象,这里重点的是EntityResolver这个类,之前在配置路径的时候也出现过,那么什么是EntityResolver呢?
官方解释:如果SAX需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。也就是说对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。即通过网络(实际上就是DTD的URL地址)来下载相应的DTD声明,并进行认证。这时候需要联网,否则会报错。
点开EntityResolver,发现这个接口只有一个方法
public abstract InputSource resolveEntity (String publicId,String systemId)
throws SAXException, IOException;
子类:
public InputSource resolveEntity(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);
}
}
return null;
}
这里接受两个参数,返回的是InputSource,结合具体例子:
(1)spring-beans.xml中的头文件为xsd模式:
publicId:null
systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
(2)spring-beans.xml中的头文件为DTD模式:
publicId:-//SPRING//DTD BEAN 2.0//EN
systemId:http://www.springframework.org/dtd/spring-beans.dtd
通过上面的源码,我们知道spring对于不同的验证模式,使用了不同的解析器解析。
DTD:
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Trying to resolve XML entity with public ID [" + publicId +
"] and system ID [" + systemId + "]");
}
if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
/**
* 1.截取获取systemId的最后的spring-bean.dtd
*/
int lastPathSeparator = systemId.lastIndexOf('/');
int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
if (dtdNameStart != -1) {
String dtdFile = DTD_NAME + DTD_EXTENSION;
if (logger.isTraceEnabled()) {
logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
}
try {
/**
* 2.在当前路径下去获取.dtd的文件
*/
Resource resource = new ClassPathResource(dtdFile, getClass());
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isTraceEnabled()) {
logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
}
return source;
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
}
}
}
}
// Use the default behavior -> download from website or wherever.
return null;
}
XSD:
PluggableSchemaResolver类的resolveEntity
public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException {
if (logger.isTraceEnabled()) {
logger.trace("Trying to resolve XML entity with public id [" + publicId +
"] and system id [" + systemId + "]");
}
if (systemId != null) {
//默认META-INF下的spring.schemas找到对应systemId的映射位置
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isTraceEnabled()) {
logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
}
catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}
上一步我们获取document后,接下来我们提取并注册bean的环节
Spring 使用 XmlBeanDefinitionReader 来读取并解析 xml 文件,XmlBeanDefinitionReader 是 BeanDefinitionReader 接口的实现。
BeanDefinitionReader 定义了 Spring 读取 Bean 定义的一个接口,这个接口中有一些 loadBeanDefinitions 方法, 用于读取 Bean 配置。
BeanDefinitionReader 接口有两个具体的实现,其中之一就是从 Xml 文件中读取配置的 XmlBeanDefinitionReader,另一个则是从 Java Properties 文件中读取配置的PropertiesBeanDefinitionReader
/**
* 实际从指定的XML文件加载bean定义。
要从中读取的SAX inputSource
* @param resource XML文件的资源描述符
* @返回找到的bean定义的数量
* @在加载或解析错误时抛出BeanDefinitionStoreException
* @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);
}
}
registerBeanDefinitions:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//1.使用DefaultBeanDefinitionDocumentReader实例化createBeanDefinitionDocumentReader()
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//2.记录统计前加载BeanDefinition的个数
int countBefore = getRegistry().getBeanDefinitionCount();
//3.加载及注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//4.记录本次加载的BeanDefinition的个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//获取根节点,以便继续将此节点作为根节点进行BeanDefinition注册
doRegisterBeanDefinitions(doc.getDocumentElement());
}
核心部分来了,这里对根节点以下的节点进行处理
/**
* 在给定的根{@code }元素中注册每个bean定义。
*/
@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
/*
* 任何嵌套的元素都会在这个方法中引起递归。在 为了正确地传播和保存 default-*属性,
* 跟踪当前(父)委托,它可能为空。创建新的(子)委托,该委托引用父委托作为备用,
* 然后最终将this.delegate重置回它的原始(父)引用。
* 这种行为模拟了一堆委托,而实际上并不需要委托。
*/
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
//处理profile属性,如果定义了profile属性则需要到环境变量中寻找
//所以这里的环境不能为空
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
(!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;
}
这里有个profiles属性,或许我们工作中不常用到,让我们来了解下这个属性
官方文档:
集成web环境中,我们可以在web.xml中增加如下代码:
Spring.profiles.active
dev
有了这个特性我们可以在配置文件中部署两套配置部署生产环境与开发环境,最常用的是更换不同的数据库
解析profile是后,对xml中的数据进行读取。核心代码parseBeanDefinitions(root, this.delegate);
/**
* 解析文档根级元素::
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对beans进行处理
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 {
//对自定义的bean进行处理
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
我们通过源码可以看到对bean的处理有两中情况:
一种是默认的一种是自定义的,先来看看默认的。
默认的就是我们spring-bean.xml中所写的:
源码解析:
根据节点选择对应的方法
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//import
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//alias
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//bean
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//beans
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
/**
*解析提供{@code }元素。可能返回{@code null}
*如果在解析过程中出现错误。将错误报告给
* {@link org.springframework.beans.factory.parsing.ProblemReporter}。
*/
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
//获取id的属性
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取name的属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//新建一个存储别名的集合
List aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
//将字符串name解析成多个字符串集合
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.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
//确认指定的bean名称和别名尚未使用
checkNameUniqueness(beanName, aliases, ele);
}
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
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);
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("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;
}
parseBeanDefinitionElement:
/**
* Parse the bean definition itself, without regard to name or aliases. May return
* {@code null} if problems occurred during the parsing of the bean definition.
*/
/**
解析bean定义本身,而不考虑名称或别名。可能会返回
* {@code null}如果bean定义解析过程中出现问题。
*/
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
//解析class的属性
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
//解析parent属性
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
//创建用于承载属性AbstractBeanDefinition类型的GenericBeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//解析默认bean的各种属性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//设置description
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析元数据
parseMetaElements(ele, bd);
//解析lookup-method属性
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析replaced-method属性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析构造函数属性
parseConstructorArgElements(ele, bd);
//解析property子元素
parsePropertyElements(ele, bd);
//解析qualifier子元素
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;
}
另一种就是自定义的,如:
这两种读取及解析差别是非常大的,如果采用Spring默认配置,spring当然知道怎么做,如果是自定义的,那么就需要用户实现一些接口及配置。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement,而判断是否是默认命名空间还是自定义命名空间的方法其实是使用isDefaultNamespace(ele)获取命名空间,并与spring中固定的命名空间http://www.springframework.org/schema/beans,如果一致则认为是默认否则自定义