不管是xml方式启动,还是annotation方式启动,它都会来到refresh()方法,但是这个里面使用了很多委派模式,在xml中ApplicationContext使用了五层结构,而annotation使用了三层结构,他们委派模式的调用,调用的是各自实现的方法,不相同!(因为他们是从底层调用的,根据唯一父类的特性,它只能识别到它所在线路中的方法!)
接着上一篇,写道refresh(),在这个方法里面有14个方法,加载流程,我们一个一个的进去看一看到底发生了什么。
============== XML版本 ========================
在早期,xml方式是相当重要的,所以它给的xml配置内容,标签也是极为丰富,所以它的重点就放在了如何正确从xml中解析出所需要的信息,而后来注解方式的流行,到现在的springboot,可以说,这个复杂的xml解析,可以放弃了。
一,prepareRefresh()刷新前的预处理
1)、initPropertySources()初始化一些属性设置;子类自定义个性化的属性设置方法;
2)、getEnvironment().validateRequiredProperties();检验属性的合法等
3)、earlyApplicationEvents= new LinkedHashSet();保存容器中的一些早期的事件;
参考:https://www.cnblogs.com/feng-jq/p/10282179.html
protected void prepareRefresh() {
//切换到活动状态。
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
//在上下文环境中初始化任何占位符属性源。
initPropertySources(); //【1】
//验证标记为所需的所有属性都是可解析的:
//请参阅配置属性解释器设置所需属性
getEnvironment().validateRequiredProperties();//【2】
if (this.earlyApplicationListeners == null) {
//容器初始化,size=0 【3】
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
//容器初始化 size=0
//一旦有可利用的广播,允许早期的applicationEvents集合被发布
this.earlyApplicationEvents = new LinkedHashSet<>();
}
这个存在于AbstractRefreshableWebApplicationtext中,我们使用xml启动,不会用到该方法。!
protected void initPropertySources() {
ConfigurableEnvironment env = getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
//初始化属性 源
((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig);
}
}
//用真正的servlet context/config 属性源来替换作为占位符存在的 stub 属性源
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
可以再深入这个方法看一下,上面那个参考连接讲的蛮清楚的。这里就不叙述了,总的来说就是初始化占位符属性源,用真正的属性源来替换作为占位符存在的stub属性源。
二,obtainFreshBeanFactory()获取BeanFactory
1)、refreshBeanFactory();刷新【创建】BeanFactory;
创建了一个this.beanFactory = new DefaultListableBeanFactory();
设置id;
2)、getBeanFactory();返回刚才GenericApplicationContext创建的BeanFactory对象;
3)、将创建的BeanFactory【DefaultListableBeanFactory】返回;
可以参考:https://www.jianshu.com/p/144af98965d9
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//第二层类 AbstractApplicationContext作为第一层
refreshBeanFactory();
//返回DefaultListableBeanFactory 也由第二层实现,获得上 方法初始化的结果。
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
return beanFactory;
}
返回的是ConfigurableListableBeanFactory接口类是BeanFactory的子类,它的唯一实现就是DefaultListableBeanFactory,所以返回的就是这个。
DefaultListableBeanFactory beanFactory,是第二层的成员,第一个初始化,第二个返回值。
第二层:AbstractRefreshApplicationContext类实现refreshBeanFactory()方法
protected final void refreshBeanFactory() throws BeansException {
...
//创建IOC容器。
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//对容器进行定制化,
customizeBeanFactory(beanFactory);
//容器已经装备好了,要往里面 放东西了,放什么呢? beanDefinition。
//调用载入bean定义的方法,【委派模式】,调用子类的实现。beanDefinition入场。
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
// createBeanFactory()
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
重点是 loadBeanDefinitions()。深入看一下,上面的方法都是对容器进行一些属性设置。
它也委派模式,实现是在第四层:AbstractXmlApplicationContext中
loadBeanDefinitions 第一次
//创建一个Bean读取器,然后给这个读取器,设置了很多属性。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建一个Bean读取器 reader将beanFactory封装了
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//当Bean读取器 读取bean定义的xml资源时,启动xml的效验机制
initBeanDefinitionReader(beanDefinitionReader);
//bean读取器真正实现加载的方法。---------【重点】
loadBeanDefinitions(beanDefinitionReader);
}
new出了一个Reader,而且是把beanFactory作为参数传进去的,很明显reader把beanfactory封装了,然后传入下一个方法loadBeanDefinitions().这里还给reader配置了一些属性。
【插入】Reader也很重要!
Reader构造方法,注意这里的BeanFactory变成了接口,改名未registry,通过抽象父类保存
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
看一下Reader具有的成员:
注意:在XmlBeanDefinitionReader中,除了大一堆的成员变量的get/set方法外,就是loadBeanDefinitions方法!
【插入完毕】
loadBeanDefinitions 第二次
同样第四层AbstractXmlApplicationContext的loadBeanDefintions(),参数变成reader
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();//调用第五层方法
if (configResources != null) { //为空
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations(); //这个有内容,xml路径组
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations); //【入】
}
}
首先是获取Resource,但是第一次加载还没有,还处于location时代,所以再一次loadBeanDefinitioins(configLocations) 这是第三次load,参数是locations.
loadBeanDefinitions 第三次 位于Reader的父类:AbstractBeanDefinitionReader中
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
loadBeanDefinitions 第四次,还在AbstractBeanDefinitionReader中
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
loadBeanDefinitions 第五次,还在AbstractBeanDefinitionReader中, 感觉马上接近真相了
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
//该对象,是本类保存的字段,同时xml方式启动就是 第五层(记得其中一个顶层接口就是resourceLoader)
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//【入】将location解析成为Resource,一个location可能对应对个xml,而传入还可以传入多个location。
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//这里就 递归回到了之前,调用这个为空时候的状况,现在肯定不是空的了!
int loadCount = loadBeanDefinitions(resources);
...
return loadCount;
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
...
return loadCount;
}
}
上面方法,两个重要内容:
1.将location解析成为resource
2.调用loadBeanDefinitions(resources)
这里需要注意,走的是if()块,它的第一个方法就将location解析称为Resource.
{location-》xml找到resource-》->document-》loader-》resolver.}
判断location是哪种形式,url还是classPath 还是第三种,(我们通过是使用classPath是吧)
location转为Resource的具体方法就是:getResources() 第一次可以深入看一下。入口在AbstractApplicationContext中
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
然后调用pattern,资源匹配器,PathMatchingResourcePatternResolver类中 第二次
public Resource[] getResources(String locationPattern) throws IOException {
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { //classpath*: 多个xml扫描
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else { //走的这里,单个xml
//分离classpath,得到真正的值
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else { //走这里,单一xml
return new Resource[] {getResourceLoader().getResource(locationPattern)}; //【入】DefaultResourceLoader类中
}
}
}
getResource() 第三次:DefaultResourceLoader类中
public Resource getResource(String location) {
//这是一个集合,size=0,直接掠过
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) { //走这里 classpath:
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
...
}
这个new出来的类,ClassPathResource类,包含三个字段,内容简单,就是location的封装。
public class ClassPathResource extends AbstractFileResolvingResource {
private final String path; //xml 路径
@Nullable
private ClassLoader classLoader;
@Nullable
private Class<?> clazz;
。。。。
}
现在我们拿到了Resource。
又回到了loadBeanDefinitions(resources)里面;AbstractBeanDefinitionReader
loadBeanDefinitions 第六次 传入 Resources,它也是一个入口,对数组的切分。
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
int counter = 0;
for (Resource resource : resources) {
counter += loadBeanDefinitions(resource);
}
return counter;
}
loadBeanDefinitions(resource) 第七次:进入XmlBeanDefinitionReader类中:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
将resource再一次封装,成为EncodedResource:
// 对resource 的封装
public class EncodedResource implements InputStreamSource {
private final Resource resource;
@Nullable
private final String encoding;
@Nullable
private final Charset charset;
...
}
loadBeanDefinitions(encodedResource) 第八次 还在XmlBeanDefinitionReade类r中
public int loadBeanDefinitions(EncodedResource encodedResource) {
...
try {
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();
}
}
到这里 loadBeanDefinitions()于结束了,开始的是doLoad…了。!!
首先,获得输入流,读取xml文件,通过ClassPathResource类的getInputStream()方法:三种方式,我们看一种就可以了
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
...
return is;
}
然后就是doLoadBeanDefinitions()方法:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {
try {
//将 resource 转成 Document(xml,html对象)
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource); //【入】
}
...
}
从doLoad 升级成为 registereBeanDefinitions()了,这里进展很快啊。document的获取jdk自带了,这里不讲述。
public int registerBeanDefinitions(Document doc, Resource resource) throws {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//获取容器中注册的bean数量
int countBefore = getRegistry().getBeanDefinitionCount();
//解析入口 【委派】,
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//计算该 resource对应xml 中包含的bean数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
registerBeanDefinitions() 第二次,位于DefaultBeanDefinitionDocumentReader类中
它的第二个参数 XmlReaderContext对象:
// readerContext 读取内容
public class XmlReaderContext extends ReaderContext {
private final XmlBeanDefinitionReader reader;
private final NamespaceHandlerResolver namespaceHandlerResolver;
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//xml的根元数,这个就是java读取xml的内容。树状的模式。
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);//【深入】
}
从load->doLoad->register->doRegister 结束,现在开始从xml的对象模型document的根节点root开始遍历,
protected void doRegisterBeanDefinitions(Element root) {
//具体 的解析过程由,BeanDefinitionoParserDelegate实现。,它的里面,定义了spring bean的各种xml元素。
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
...
//在解析之前,可以增加自定义解析,包括解析之后,这样 用户可以扩展spring的解析。
preProcessXml(root);
parseBeanDefinitions(root, this.delegate); //【重点】--深入,解析spring bean xml的具体过程,(各种子元素)
postProcessXml(root);
this.delegate = parent;
}
从doregister 跳转到了parseBeanDefinitions了,很不错,这里传入的root根节点,这里就开始对document进行解析了,递归方式的,一个一个的解析,很多内容,也是真正执行功能的代码,对标签比如说bean,import,location,alias,等等进行解析,内容太多,这里就不叙述了。
解析完毕之后,得到BeanDefinitionHolder(就是对beanDefinition的封装),注册进入IOC容器,也就是BeanFactory.
此时IOC容器里面存放了bean的全部配置信息。实现了控制反转。