上一篇LK沿着各位大佬的足迹,探索了一下IOC容器在初始化的时候如何定位到配置文件的路径。
接着上片继续说,实际上IOC在定位容器时使用到了Delegate委派模式,这个模式是怎么回事呢?
委派模式:抽象类和子类都有相同的方法,调用抽象类的方法而在子类中具体去实现他们。
来看看类图:
代码实现:
抽象类Animal
public abstract class Animal {
void eat() {
};
void sleep() {
};
}
Dog:
public class Dog extends Animal{
void eat() {
System.out.println("狗吃骨头了");
}
void sleep() {
System.out.println("狗要睡觉了");
}
}
Cat:
public class Cat extends Animal{
void eat() {
System.out.println("猫吃骨头了");
}
void sleep() {
System.out.println("猫要睡觉了");
}
}
调用实现
public class Test {
public static void main(String[] args) {
Animal animal = null;
/**
* 使用时在抽象类中调用方法,而在其子类去实现。
*/
while(true) {
if(new Random().nextBoolean()) {
animal = new Dog();
animal.eat();
animal.sleep();
}else {
animal = new Cat();
animal.eat();
animal.sleep();
}
}
}
}
结果:
可以看到委派的优势:对内隐藏实现, 易于扩展; 简化调用。定位资源使用的refreshBeanFactory()方法就是在其子类AbstractRefreshableApplicationContext中具体实现的。
相信看完这个对理解源码是不是又容易了一点呢?
- BeanDefinition的载入和解析
接着上一篇IOC定位到资源,就开始解析配置文件内容,将XMl转换为Document对象。
跟着LK的脚步继续往下看
追溯上一篇在AbstractBeanDefinitionReader的loadBeanDefinitions方法中我们得到了定位resource的方法.getResources和getResource
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
Resource resource = resourceLoader.getResource(location);
return loadCount;
}
那么解析方法在哪呢,在它的子类XmlBeanDefinitionReader的loadBeanDefinitions方法中实现
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//此处是始化资源集合,将资源添加到集合中
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
try {
//此处是解析的开始,将定位到的资源解析为IO流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//在此方法中进行IO流和DoM的转换
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
}
看到这就知道这个方法是处理定位到资源的入口,主要干了两件事1.将资源转换成IO流,2.将流解析成Dom。来看看具体解析流的过程
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//将IO流解析成DOm对象
Document doc = doLoadDocument(inputSource, resource);
//将Dom元素注册进bean元素中
return registerBeanDefinitions(doc, resource);
}
}
来看看具体转换实现,此实现是在DefaultDocumentLoader的loadDocument方法中实现的。
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);
}
看到这有没有想起LK的简单版springIoc实现,此方法先创建DocumentBuilderFactory工厂,在工厂中产生DocumentBuilder对象,最后执行parse()方法解析IO流。具体解析过程不在这列出。到这已经得到了Dom对象。
得到DOm对象就开始解析Bean的各种配置,回到XmlBeanDefinitionReader的doLoadBeanDefinitions方法中的 registerBeanDefinitions注册bean定义的方法中。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//从xml文档读取bean的定义
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//配置文档环境
documentReader.setEnvironment(getEnvironment());
//得到容器中定义的bean 数量
int countBefore = getRegistry().getBeanDefinitionCount();
//从给定的DOM文档中读取bean定义,将解析到的属性注册到bean中。
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//返回解析注册的bean数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
这个方法包含dom解析的方法入口registerBeanDefinitions。进来看看它都有什么,在此我只想说兄弟挺住真的有点多。
在它的实现类DefaultBeanDefinitionDocumentReader中具体实现registerBeanDefinitions。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//得到DOM根节点元素
Element root = doc.getDocumentElement();
//解析DOM元素
doRegisterBeanDefinitions(root);
}
具体解析类:
protected void doRegisterBeanDefinitions(Element root) {
//递归解析<beans>
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
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)) {
return;
}
}
}
//处理XML中之前自定义的元素
preProcessXml(root);
//从Document的根元素开始进行Bean定义的Document对象 解析
parseBeanDefinitions(root, this.delegate);
//处理XML中之后自定义的元素
postProcessXml(root);
this.delegate = parent;
}
//该方法--从Document的根元素开始进行Bean定义的Document对象 解析
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);
//匹配是Element元素
if (node instanceof Element) {
//将其转换成dom元素
Element ele = (Element) node;
//如果是默认的命名空间
if (delegate.isDefaultNamespace(ele)) {
//解析默认元素
parseDefaultElement(ele, delegate);
}
else {
//Document的根节点没有使用Spring默认的命名空间,则使用用户自定义的解析规则解析Document根节点
delegate.parseCustomElement(ele);
}
}
}
}
else {
//Document的根节点没有使用Spring默认的命名空间,则使用用户自定义的解析规则解析Document根节点
delegate.parseCustomElement(root);
}
}
上面方法开始DOM根元素开始解析bean,匹配到spring默认的命名规则。下面我们看一个Bean的匹配
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//节点名称是bean
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
//处理给定的bean元素,分析bean定义,并在注册处注册。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//BeanDefinitionParserDelegate解析document中bean元素属性
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//处理自定义的属性和嵌套的元素
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 向Spring IoC容器注册解析得到的Bean定义
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 发送注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
//document文档中bean元素属性的解析
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
//得到bean id 和 命名空间
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//创建别名集合
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
//如果<Bean>元素中没有配置id属性时,将别名中的第一个值赋值给beanName
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");
}
}
//检查<Bean>元素所配置的id或者name的唯一性,
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
//详细对<Bean>元素中配置的Bean定义进行解析的地方【property,description,parent等】
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
//如果<Bean>元素中没有配置id、别名或者name,且没有包含子
//<Bean>元素,为解析的Bean生成一个唯一beanName并注册
//可以看到它是先去寻找ParentName,如果有,命名为ParentName+$child"
//否则去找FactoryBeanName,命名为FactoryBeanName+$created
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
//如果<Bean>元素中没有配置id、别名或者name,但包含了子
//<Bean>元素,为解析的Bean使用别名向IoC容器注册
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;
}
看到这大家终于知道我们在配置文件中定义的bean id 和 name 都是在什么时候解析的了吧,我只想说妈呀,终于看到一点希望。
但这还没完,找到bean了,那它的属性标签property等都是在哪解析的呢?
来看看上文提到的parseBeanDefinitionElement这个方法
//解析Docunment文档中bean元素的其他属性标签,不考虑名称或别名
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
try {
String parent = null;
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT))
parseMetaElements(ele, bd);
//解析LookupOverrideSub
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析ReplacedMethod
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析ConstructorArg
parseConstructorArgElements(ele, bd);
//解析Property
parsePropertyElements(ele, bd);
//解析Qualifier
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
return null;
}
//来看看它是如何解析Property的,其它都大同小异
//解析每一个propetry元素
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);
}
}
}
//解析具体的一个property属性
public void parsePropertyElement(Element ele, BeanDefinition bd) {
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
if (!StringUtils.hasLength(propertyName)) {
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
//创建PropertyEntry对象放入栈中
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对象
PropertyValue pv = new PropertyValue(propertyName, val);
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
//属性解析完之后出栈
this.parseState.pop();
}
}
//获取具体的属性key,value属性值,这里是“meta”
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
Element metaElement = (Element) node;
String key = metaElement.getAttribute(KEY_ATTRIBUTE);
String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
attribute.setSource(extractSource(metaElement));
attributeAccessor.addMetadataAttribute(attribute);
}
}
}
到此DOM解析告一段落,属性解析流程和bean解析相同,一层一层解析。画个图来梳理一下整个解析过程
还有最后一块IOC初始化就大功告成了,老铁们加油
- BeanDefinition的注册
需要注意的是这是将bean的定义注入IOC容器并不是依赖注入的实现。
再来看看DefaultBeanDefinitionDocumentReader中的processBeanDefinition方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 最终修饰的实例
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));
}
}
//在bean工厂中注册bean
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// .beanName作为key来保存bean实例
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 用别名注册
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
再来看看注册完如何使用,具体实现在其子类DefaultListableBeanFactory(有三个我们只看其中的一个)
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
BeanDefinition oldBeanDefinition;
//从Map中通过beanName获取bean实例
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
}
else {
//beanName不为空,加到beanDefinitionNames集合中
this.beanDefinitionNames.add(beanName);
//用完删除
this.manualSingletonNames.remove(beanName);
//在缓存中移除
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
至此整个注册过程完成。主要完成一下几点
- 在map集合中以beanName存放beanDifinition.
- 在子类中取用。
- 在缓存和set集合中移除使用过的beanDifinition
有没有一种释怀的感觉,哈哈那你就错了,LK还要带你梳理一下整个流程
- 首先定位Resource,具体看getResources和getResource方法实现。
- 解析Dom。
- 注册beanDifinition。
我们见证了bean的变化
最后自此感谢大佬 田小波,I,Frankenstein等