zoukankan      html  css  js  c++  java
  • Spring宏观上简单梳理分析一

    关于Spring框架,说的最多的就是IOC和AOP了,即依赖注入和切面编程。这些也是使用spring过程中要用的最基本的东西了。但是,spring是如何进行运转的呢?这是一个问题呀,我们起码应该要明白spring的工作流程。

    思考:

      spring常用的方式有注解和xml配置两种方式,根据我们的猜测,这两种方式殊路同归。Spring首先要做的事,就是解析xml(找到注解的类),根据解析出来的文档进行各种类的信息组装,最后当需要某个类的实例时,根据反射、利用组装好的class信息,构造出我们想要的实例;

    其实,主体思路确实如我们所想的那样,走的就是这个流程。但是,spring在其中添加了很多的检查工作,来保证安全性。接下来,我们简单分析一下;

    过程分析(Spring4.x):

     1.加载配置文件

          

        String file="spring.xml";
        Resource source=new ClassPathResource(file);
        BeanFactory factory=new XmlBeanFactory(source);

    这是一段最简单的将xml文件加载到spring容器中,并从容器中获得注册Bean的代码了。那我们来看看XmlBeanFactory的代码:

     1 public class XmlBeanFactory extends DefaultListableBeanFactory {
     2 
     3     private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
     4 
     5 
     6     /**
     7      * Create a new XmlBeanFactory with the given resource,
     8      * which must be parsable using DOM.
     9      * @param resource XML resource to load bean definitions from
    10      * @throws BeansException in case of loading or parsing errors
    11      */
    12     public XmlBeanFactory(Resource resource) throws BeansException {
    13         this(resource, null);
    14     }
    15 
    16     /**
    17      * Create a new XmlBeanFactory with the given input stream,
    18      * which must be parsable using DOM.
    19      * @param resource XML resource to load bean definitions from
    20      * @param parentBeanFactory parent bean factory
    21      * @throws BeansException in case of loading or parsing errors
    22      */
    23     public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    24         super(parentBeanFactory);
    25         this.reader.loadBeanDefinitions(resource);
    26     }
    27 
    28 }

    从源码看出,实际的加载xml文件即解析xml文件为Bean的工作是交给XmlBeanDefinitionReader来完成的,那我们再看看这个类的loadBeanDefinitions方法:

    @Override
        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
            return 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 {
                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();
                }
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "IOException parsing XML document from " + encodedResource.getResource(), ex);
            }
            finally {
                currentResources.remove(encodedResource);
                if (currentResources.isEmpty()) {
                    this.resourcesCurrentlyBeingLoaded.remove();
                }
            }
        }

    可以看出,XmlBeanDefinitionReader类会先将Resource封装成EncodedResource类,并调用重载方法来完成解析工作。重载方法里关键部分是一下代码:

          InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
                    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                }

    上述代码就是从Resource对象中获得输入流,即输入的xml文件,并封装成InputSource类,交给doLoadBeanDefinitions方法来进行解析;

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
            try {
                Document doc = doLoadDocument(inputSource, resource);
                return registerBeanDefinitions(doc, resource);
            }
            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);
            }
        }

    这里面要关注的就是:

        Document doc = doLoadDocument(inputSource, resource);
           return registerBeanDefinitions(doc, resource);

    这两句代码,其中第一句就是讲xml文件解析成Ducnment对象,第二句是通过分析Document对象来注册类。这里我们来看第二个方法,第一个知道在这里xml文件被转为Document对象就行了;

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
            int countBefore = getRegistry().getBeanDefinitionCount();
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
            return getRegistry().getBeanDefinitionCount() - countBefore;
        }

    看得出来,在这个方法中又创建了一个BeanDefinitionDocumentReader对象,利用他来解析Document对象内容。其中BeanDefinitionDocumentReader是个接口,他只有一个实现类DefaultBeanDefinitionDocumentReader,所以我们进入这个实现类的registerBeanDefinitions方法中去查看

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
            this.readerContext = readerContext;
            logger.debug("Loading bean definitions");
            Element root = doc.getDocumentElement();
            doRegisterBeanDefinitions(root);
        }

    一般spring里面do打头的方法,就是真正干活的方法了,让我们进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)) {
                        if (logger.isInfoEnabled()) {
                            logger.info("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;
        }

    这里的重点方法是parseBeanDefinitions(root, this.delegate),在这里面有实际解析Document文件的步骤。另外preProcessXml(root)和postProcessXml(root)方法都是空实现,应该是留给用户自定义的。至于前面一段代码,是处理profile标签的内容;

    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;
                        if (delegate.isDefaultNamespace(ele)) {
                            parseDefaultElement(ele, delegate);
                        }
                        else {
                            delegate.parseCustomElement(ele);
                        }
                    }
                }
            }
            else {
                delegate.parseCustomElement(root);
            }
        }

    这个方法,循环解析Document中的每个节点(Bean),并判断是默认标签节点(bean,import,beans,alias)还是用户自定义标签节点(例如aop,tx),然后采用对应的解析方法parseDefaultElement(ele, delegate)和delegate.parseCustomElement(ele)。具体的解析标签,以后再说了。至此,我们大致明白了spring是怎么找到xml文件并将它解析为bean信息的宏观结构流程了。接下来,我们就该讨论一下,spring是怎么去获取这些bean信息创建出来的实例了。

    ps.采用Applicationcontext的分析bean的步骤类似,接下来也接单看看;

    首先我们打开ClassPathXmlApplicationContext的源码,该类变相实现了ApplicationContext接口;

    public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
    ...
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
            this(new String[] {configLocation}, true, null);
        }
    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
                throws BeansException {
    
            super(parent);
            setConfigLocations(configLocations);
            if (refresh) {
                refresh();
            }
        }
    }

    构造函数接受一个string类型的值,用来表示xml配置文件地址。重点是ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)这个方法,其中的setConfigLocations(configLocations)方法是将string解析,并设置为configLocations的值,否则spring会有默认值。实际的重点是refresh()方法,该方法里实现了beanfactory的初始化,即上面描述的加载解析bean信息的过程。另外,还有国际化等其他的操作。refresh方法属于AbstractApplicationContext这个抽象类:

    public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                // Prepare this context for refreshing.
                prepareRefresh();
    
                // 这里面就是初始化beanfactory
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
                // Prepare the bean factory for use in this context.
                prepareBeanFactory(beanFactory);
    
                try {
                    // Allows post-processing of the bean factory in context subclasses.
                    postProcessBeanFactory(beanFactory);
    
                    // Invoke factory processors registered as beans in the context.
                    invokeBeanFactoryPostProcessors(beanFactory);
    
                    // Register bean processors that intercept bean creation.
                    registerBeanPostProcessors(beanFactory);
    
                    // Initialize message source for this context.
                    initMessageSource();
    
                    // Initialize event multicaster for this context.
                    initApplicationEventMulticaster();
    
                    // Initialize other special beans in specific context subclasses.
                    onRefresh();
    
                    // Check for listener beans and register them.
                    registerListeners();
    
                    // Instantiate all remaining (non-lazy-init) singletons.
                    finishBeanFactoryInitialization(beanFactory);
    
                    // Last step: publish corresponding event.
                    finishRefresh();
                }
    
                catch (BeansException ex) {
                    // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();
    
                    // Reset 'active' flag.
                    cancelRefresh(ex);
    
                    // Propagate exception to caller.
                    throw ex;
                }
            }
        }

    上面的ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()方法就是加载beanfactory的方法

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
            refreshBeanFactory();
            ConfigurableListableBeanFactory beanFactory = getBeanFactory();
            if (logger.isDebugEnabled()) {
                logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
            }
            return beanFactory;
        }

    其中refreshBeanFactory()方法是初始化beanfactory地方法,在该类中是抽象方法,由子类AbstractRefreshableApplicationContext实现

    @Override
        protected final void refreshBeanFactory() throws BeansException {
            if (hasBeanFactory()) {
                destroyBeans();
                closeBeanFactory();
            }
            try {
                DefaultListableBeanFactory beanFactory = createBeanFactory();
                beanFactory.setSerializationId(getId());
                customizeBeanFactory(beanFactory);
                loadBeanDefinitions(beanFactory);
                synchronized (this.beanFactoryMonitor) {
                    this.beanFactory = beanFactory;
                }
            }
            catch (IOException ex) {
                throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
            }
        }

    我们直接看loadBeanDefinitions(beanFactory)方法,该方法也是抽象方法,由子类完成。

    @Override
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
            // Create a new XmlBeanDefinitionReader for the given BeanFactory.
            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
            // Configure the bean definition reader with this context's
            // resource loading environment.
            beanDefinitionReader.setEnvironment(getEnvironment());
            beanDefinitionReader.setResourceLoader(this);
            beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
            // Allow a subclass to provide custom initialization of the reader,
            // then proceed with actually loading the bean definitions.
            initBeanDefinitionReader(beanDefinitionReader);
            loadBeanDefinitions(beanDefinitionReader);
        }

    看到这里,就和上面的步骤联系起来了,也是采用XmlBeanDefinitionReader来完成xml文件解析和bean的加载;

     

      

        

  • 相关阅读:
    ftp卡死问题
    Windows 10 安装FileZilla Server
    The last packet successfully received from the server was 39,900 milliseconds ago问题解决
    java.sql.SQLException: Could not retrieve transaction read-only status from server 问题解决
    三主机配置 keepalived VIP高可用
    subprocess.Popen在win10下会有异常
    python 使用内置方法读取注册表
    过年有感
    java.security.InvalidKeyException: Illegal key size 解决办法
    Android Studio 检查Top Activity
  • 原文地址:https://www.cnblogs.com/jkavor/p/7388603.html
Copyright © 2011-2022 走看看