• 【Spring】BeanFactory解析bean详解


    本文是Spring源码分析中的一篇,来讲讲Spring框架中BeanFactory解析bean的过程,先来看一个在Spring中一个基本的bean定义与使用。(也可以来公号查看)
    package bean;
    public class TestBean {
        private String beanName = "beanName";
        public String getBeanName() {
            return beanName;
        }
        public void setBeanName(String beanName) {
            this.beanName = beanName;
        }
    }
    Spring配置文件root.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-4.1.xsd">
    
        <bean id="testBean" class="bean.TestBean">
    </beans>
    下面使用XmlBeanFactory来获取该bean:
    public class BeanTest {
    
        private static final java.util.logging.Logger logger = LoggerFactory.getLogger(BeanTest.class);
    
        @Test
        public void getBeanTest() {
            BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
            TestBean bean = factory.getBean("testBean");
            logger.info(bean.getBeanName);
        }
    }
    这个单元测试运行结果就是输出beanName,上面就是Spring最基本的bean的获取操作,这里我用BeanFactory作为容器来获取bean的操作并不多见,在企业开发中一般是使用功能更完善的ApplicationContext,这里先不讨论这个,下面重点讲解使用BeanFactory获取bean的过程。
     
    现在就来分析下上面的测试代码,看看Spring到底为我们做了什么工作,上面代码完成功能的流程不外乎如此:
    1. 读取Spring配置文件root.xml;
    2. 根据root.xml中的bean配置找到对应的类的配置,并实例化;
    3. 调用实例化后的对象输出结果。
     
    先来看看XmlBeanFactory源码:
    public class XmlBeanFactory extends DefaultListableBeanFactory {
    
       private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    
       public XmlBeanFactory(Resource resource) throws BeansException {
           this(resource, null);
       }
    
       public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
           super(parentBeanFactory);
           this.reader.loadBeanDefinitions(resource);
       }
    }
    从上面可以看出XmlBeanFactory继承了DefaultListableBeanFactory,DefaultListableBeanFactory是Spring注册加载bean的默认实现,它是整个bean加载的核心部分,XmlBeanFactory与它的不同点就是XmlBeanFactory使用了自定义的XML读取器XmlBeanDefinitionReader,实现了自己的BeanDefinitionReader读取。
    XmlBeanFactory加载bean的关键就在于XmlBeanDefinitionReader,下面看看XmlBeanDefinitionReader的源码(只列出部分):
    public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    
        private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
    
        private ProblemReporter problemReporter = new FailFastProblemReporter();
    
        private ReaderEventListener eventListener = new EmptyReaderEventListener();
    
        private SourceExtractor sourceExtractor = new NullSourceExtractor();
    
        private NamespaceHandlerResolver namespaceHandlerResolver;
    
        private DocumentLoader documentLoader = new DefaultDocumentLoader();
    
        private EntityResolver entityResolver;
    
        private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
    }
    XmlBeanDefinitionReader继承自AbstractBeanDefinitionReader,下面是AbstractBeanDefinitionReader的源码(只列出部分):
    public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {
    
        protected final Log logger = LogFactory.getLog(getClass());
    
        private final BeanDefinitionRegistry registry;
    
        private ResourceLoader resourceLoader;
    
        private ClassLoader beanClassLoader;
    
        private Environment environment;
    
        private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
    }
    XmlBeanDefinitionReader主要通过以下三步来加载Spring配置文件中的bean:
    1. 通过继承自AbstractBeanDefinitionReader中的方法,使用ResourLoader将资源文件(root.xml)路径转换为对应的Resource文件;
    2. 通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Ducument文件;
    3. 通过DefaultBeanDefinitionDocumentReader类对Document进行解析,最后再对解析后的Element进行解析。
     
    了解以上基础后,接下来详细分析下一开始例子中的代码:
    BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
    先看看下面XmlBeanFactory初始化的时序图来进一步了解这段代码的执行,
    在这里可以看出BeanTest测试类通过向ClassPathResource的构造方法传入spring的配置文件构造一个Resource资源文件的实例对象,再通过这个Resource资源文件来构造我们想要的XmlBeanFactory实例。在前面XmlBeanFactory源码中的构造方法可以看出,
    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.loadBeanDefinition(resource)就是资源加载真正的实现,时序图中XmlBeanDefinitionReader加载数据就是在这里完成的。
     
    接下来跟进this.reader.loadBeanDefinition(resource)方法里面,
    public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    
        @Override
        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
            return loadBeanDefinitions(new EncodedResource(resource));
        }
    }
    在loadBeanDefinition(resource)方法里对资源文件resource使用EncodedResource进行编码处理后继续传入loadBeanDefinitions方法,继续跟进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.getResource());
        }
    
        // 通过属性记录已加载的资源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            // 从resource中获取对应的InputStream,用于下面构造InputSource
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                // 调用doLoadBeanDefinitions方法
                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(inputSource, encodedResource.getResource())方法,这是整个bean加载过程的核心方法,在这个方法执行bean的加载。
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            Document doc = doLoadDocument(inputSource, resource);
            return registerBeanDefinitions(doc, resource);
        }
        /* 省略一堆catch */
    }
    跟进doLoadDocument(inputSource, resource)源码:
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }
    在doLoadDocument(inputSource, resource)方法里就使用到了前面讲的documentLoader加载Document,这里DocumentLoader是个接口,真正调用的是其实现类DefaultDocumentLoader的loadDocument方法,跟进源码:
    public class DefaultDocumentLoader implements DocumentLoader {
    
        @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);
        }
    }
    从源码可以看出这里先创建DocumentBuilderFactory,再用它创建DocumentBuilder,进而解析inputSource来返回Document对象。得到Document对象后就可以准备注册我们的Bean信息了。
     
    在上面的doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法中拿到Document对象后下面就是执行registerBeanDefinitions(doc, resource)方法了,看源码:
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        documentReader.setEnvironment(getEnvironment());
        // 还没注册bean前的BeanDefinition加载个数
        int countBefore = getRegistry().getBeanDefinitionCount();
        // 加载注册bean
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        // 本次加载注册的BeanDefinition个数
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
    这里的doc就是上面的loadDocument方法加载转换来的,从上面可以看出主要工作是交给BeanDefinitionDocumentReader的registerBeanDefinitions()方法实现的,这里BeanDefinitionDocumentReader是个接口,注册bean功能在默认实现类DefaultBeanDefinitionDocumentReader的该方法实现,跟进它的源码:
    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }
    到这里通过doc.getDocumentElement()获得Element对象后,交给doRegisterBeanDefinitions()方法后就是真正执行XML文档的解析了,跟进doRegisterBeanDefinitions()方法源码:
    protected void doRegisterBeanDefinitions(Element root) {
        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;
                }
            }
        }
    
        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);
    
        this.delegate = parent;
    }
    到这里处理流程就很清晰了,先是对profile进行处理,之后就通过parseBeanDefinitions()方法进行文档的解析操作,跟进parseBeanDefinitions()方法源码:
    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;
                    // 下面对bean进行处理
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }
    上面if-else语句块中的parseDefaultElement(ele, delegate)和delegate.parseCustomElement(ele)就是对Spring配置文件中的默认命名空间和自定义命名空间进行解析用的。在Spring的XML配置中,默认Bean声明就如前面定义的:
    <bean id="testBean" class="bean.TestBean">
    自定义的Bean声明如:
    <tx:annotation-driven />

    XmlBeanFactory加载bean的整个过程基本就讲解到这里了。

     
    作者注:原文发表在公号(点击查看),定期分享IT互联网、金融等工作经验心得、人生感悟,欢迎订阅交流,目前就职阿里-移动事业部,需要大厂内推的也可到公号砸简历。(公众号ID:weknow619)
     
  • 相关阅读:
    cocos2d3.8.1 使用prebuild提升发布android速度
    AS3条件编译
    Box2d FilterData
    旋转关节(Revolute Joint)
    线关节(Line Joint)
    平移关节(Prismatic Joint)
    滑轮关节(b2PulleyJoint)
    PAT Basic 1043 输出PATest (20 分)
    PAT Basic 1042 字符统计 (20 分)
    PAT Basic 1039 到底买不买 (20 分)
  • 原文地址:https://www.cnblogs.com/weknow619/p/9612710.html
Copyright © 2020-2023  润新知