美文网首页
spring源码分析四(从refresh方法说起)

spring源码分析四(从refresh方法说起)

作者: 为梦想前进 | 来源:发表于2020-05-19 15:57 被阅读0次

    本篇文章开始,我们就进入了spring的源码步骤分析模块,前几篇文章,我已经说明了,我指定了配置文件,然后通过ClassPathXmlApplicationContext读取配置,我们今天分析的主要逻辑是
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    文件的方式来分析源码的,这样,比较好梳理,废话不多说,我们直接进入今天的主题

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

    以上流程都比较简单,我们就直接调过了,我们要重点关注的就是从refresh方法开始,我之前一直很好奇,为什么spring要定义这个名称,
    refresh,刷新,不应该是初始化或者什么之类的嘛,且听我慢慢道来,其中缘由

    @Override
        public void refresh() throws BeansException, IllegalStateException {
            //使用synchronized作为同步锁,防止第一个容器过程么有初始化成功,第二个有进来了
            synchronized (this.startupShutdownMonitor) {
                //预刷新步骤,记录初始化时间,容器的状态,将active设置为true,closed设置为false
                prepareRefresh();
    
                // 通过obtainFreshBeanFactory获取一个bean工厂
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
                .......
    
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
            //刷新bean工厂的方法
            refreshBeanFactory();
            //获取bean工厂
            return getBeanFactory();
        }
    
    #AbstractRefreshableApplicationContext类下面
    @Override
        protected final void refreshBeanFactory() throws BeansException {
            //查看当前bean工厂是否已经存在,如果存在,就销毁当前所有的容器中的bean,并将当前的beanFactory赋值为null,序列化id赋值为null
            if (hasBeanFactory()) {
                destroyBeans();
                closeBeanFactory();
            }
            try {
                //1.1创建DefaultListableBeanFactory对象
                DefaultListableBeanFactory beanFactory = createBeanFactory();
                //设置序列化,就是设置一个唯一的序列化id,里面存放的就是当前对象在内存中的地址类
                beanFactory.setSerializationId(getId());
                //1.2是否允许bean覆盖,是否允许自动处理循环引用
                customizeBeanFactory(beanFactory);
                //1.3加载bean定义
                loadBeanDefinitions(beanFactory);
                synchronized (this.beanFactoryMonitor) {
                    this.beanFactory = beanFactory;
                }
            }
            catch (IOException ex) {
                throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
            }
        }
    

    1.1:
    创建一个bean工厂对象如果实现了ConfigurableApplicationContext,则返回父上下文的内部bean工厂。否则,返回父上下文本身
    比较简单,就不贴具体的代码了
    DefaultListableBeanFactory beanFactory = createBeanFactory();

    1.2

    customizeBeanFactory(beanFactory);
    protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
            //设置是否允许bean覆盖,默认为true
            if (this.allowBeanDefinitionOverriding != null) {
                beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
            }
            //设置是否允许自动尝试处理bean之间的循环引用,默认值为true
            if (this.allowCircularReferences != null) {
                beanFactory.setAllowCircularReferences(this.allowCircularReferences);
            }
        }
    

    允许bean覆盖的意思是指,spring不允许同一个配置文件中,出现配置的bean名称相同,但是spring是允许不同的配置文件中的命名相同的
    这回导致一个比较严重的问题,当出现同名的bean之后,spring不会报错,而是会直接将之前的创建好的bean给覆盖掉的,我们来演示下这个步骤
    我定义了两个配置文件,分别配置不同的属性值,配置完成后,我们运行下,看看结果

    application-hellospring.xml
     <bean id="helloSpring" class="com.ryx.xiaoxin_distribute.spring.springIocAndAop.IOC.test1.HelloSpringImpl">
             <property name="name" value="张三"/>
     </bean>
    application-hellospring1.xml
     <bean id="helloSpring" class="com.ryx.xiaoxin_distribute.spring.springIocAndAop.IOC.test1.HelloSpringImpl">
             <property name="name" value="李四"/>
     </bean>
    

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application-hellospring.xml","classpath:application-hellospring1.xml");
    先加载HelloSring,在加载HelloSpring1
    最终会发现同名的时候,spring在给bean的属性赋值的时候,会将前面赋值的属性值覆盖掉,怎嘛处理那,咱们这个参数就派上用场了
    我们会看到spring定义的refresh是一个public的,也就是说我们可以调用的,那就是说,如果我们在容器加载完毕后,就可以手动对allowBeanDefinitionOverriding
    这个属性执行赋值操作,然后在调用refresh方法,我们试一试
    修改为如下方式

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application-hellospring.xml","classpath:application-hellospring1.xml");
    
            //是否允许bean覆盖
            ((ClassPathXmlApplicationContext) applicationContext).setAllowBeanDefinitionOverriding(false);
    
            ((ClassPathXmlApplicationContext) applicationContext).refresh();
            System.out.println("applicationContext启动了....");
    
            // 从上下文中获取bean
            HelloSpring helloSpring1 =  applicationContext.getBean(HelloSpringImpl.class);
    
            System.out.println(helloSpring1.sayHello());
    

    再次运行,就会报错,源码这里会有判断,我们设置为false,就会直接在这里抛出异常
    if (!isAllowBeanDefinitionOverriding()) {
    throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
    }
    说道这里,顺便说下,前面提到的一个让我不解的问题,就是spring关于将初始化方法定义为refresh的用意,是说,我们可以无数遍的手动
    刷新这个容器,修改完一些默认参数后,可以调用refresh方法啦!

    1.3
    接下来分析1.3步骤,加载bean定义
    loadBeanDefinitions(beanFactory);

    AbstractXmlApplicationContext
    @Override
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
            // 创建一个XmlBeanDefinitionReader对象
            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
            //设置一些变量属性
            beanDefinitionReader.setEnvironment(this.getEnvironment());
            beanDefinitionReader.setResourceLoader(this);
            beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
            //初始化bean阅读器
            initBeanDefinitionReader(beanDefinitionReader);
            //加载bean定义
            loadBeanDefinitions(beanDefinitionReader);
        }
    

    loadBeanDefinitions(beanDefinitionReader),加载和注册bean的定义

    我们可以看到有两个方法可以获取需要加载的bean,第一个是通过resources,第二个是通过配置路径,这两个都是可以
    获取bean的配置文件的,不过需要注意的是如果需要通过resources加载bean的话,需要我们自己继承ClassPathXmlApplicationContext,后自己实现
    才能加载以如下的方式执行加载

    public class ResourceContext extends ClassPathXmlApplicationContext {
    
        @Override
        protected Resource[] getConfigResources() {
            final ArrayList<Resource> objects = new ArrayList<Resource>(){{
                add(new ClassPathResource("application-hellospring.xml"));
                add(new ClassPathResource("application-hellospring1.xml"));
            }};
            return objects.toArray(new Resource[0]);
        }
    }
    
    ApplicationContext applicationContext = new ResourceContext();
            ((ResourceContext) applicationContext).refresh();
    

    这两个方法我们选择一个吧,我们就分析 getConfigLocations吧getConfigResources就不做分析了.
    因为底层调用都是loadBeanDefinitions(resource)这个方法

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
            Resource[] configResources = getConfigResources();
            if (configResources != null) {
                //重点方法,重点方法,重点方法
                reader.loadBeanDefinitions(configResources);
            }
            //获取配置路径数组,还记得我们在启动的时候的写法吗,getConfigLocations会获取到一下的两个配置
            //new ClassPathXmlApplicationContext("classpath:application-hellospring.xml","classpath:application-hellospring1.xml");
            //这里有一点需要注意下,如果getConfigLocations获取不到,就回去默认路径下找配置文件,所以当我们将配置文件配置在WEB-INF下的时候,
            //spring是会默认取加载的,前提是,我们不手动配置.默认加载的位置是/WEB-INF/applicationContext.xml
            String[] configLocations = getConfigLocations();
            if (configLocations != null) {
                reader.loadBeanDefinitions(configLocations);
            }
        }
    #reader.loadBeanDefinitions(configLocations);
    AbstractBeanDefinitionReader类
    @Override
        public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
            //为空,直接跑出异常
            Assert.notNull(locations, "Location array must not be null");
            //初始化变量
            int count = 0;
            //循环加载bean定义
            for (String location : locations) {
                count += loadBeanDefinitions(location);
            }
            return count;
        }
    
    @Override
        public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
            return loadBeanDefinitions(location, null);
        }
    
        public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
                //获取资源加载器对象
                ResourceLoader resourceLoader = getResourceLoader();
                //如果为空,抛出异常
                if (resourceLoader == null) {
                    throw new BeanDefinitionStoreException(
                            "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
                }
    
                if (resourceLoader instanceof ResourcePatternResolver) {
                    // Resource pattern matching available.
                    try {
                        //从配置文件中获取资源对象数组
                        Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                        //加载bean定义方法,发现没有,这里又去调用了,饶了一大圈,最终还是通过resources去加载的,所以location的方式
                        多走了逻辑,如果使用resources加载bean定义的话,少走一些弯路,哈哈,
    
                        //if (configResources != null) {
                        //          reader.loadBeanDefinitions(configResources);
                        //      }
    
                        //重点方法
                        int count = loadBeanDefinitions(resources);
                        //资源加载完后,将资源添加到actualResources实际资源集合中,else里面都是一样的,就跳过了
                        if (actualResources != null) {
                            Collections.addAll(actualResources, resources);
                        }
                        if (logger.isTraceEnabled()) {
                            logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                        }
                        return count;
                    }
                    catch (IOException ex) {
                        throw new BeanDefinitionStoreException(
                                "Could not resolve bean definition resource pattern [" + location + "]", ex);
                    }
                }
                else {.
                    Resource resource = resourceLoader.getResource(location);
                    int count = loadBeanDefinitions(resource);
                    if (actualResources != null) {
                        actualResources.add(resource);
                    }
                    if (logger.isTraceEnabled()) {
                        logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
                    }
                    return count;
                }
            }
    

    通过以上代码的分析,我们最终发现,真正加载bean定义的方法是loadBeanDefinitions(resources);,一起来看看

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
            Assert.notNull(encodedResource, "EncodedResource must not be null");
            if (logger.isTraceEnabled()) {
                logger.trace("Loading XML bean definitions from " + encodedResource);
            }
            //创建一个set集合存放当前资源,并将当前资源添加到集合中
            Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
            if (currentResources == null) {
                currentResources = new HashSet<>(4);
                this.resourcesCurrentlyBeingLoaded.set(currentResources);
            }
            //如果当前资源已经在集合中,则抛出异常,说明当前资源已经加载过,并放入到了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());
                    }
                    //最最重要的方法出现,就在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开始搞事情,经过了如此多的步骤,才慢慢接近真相,后面咱们会发现,spring中真正做事情的方式名称都会定义为
    do前缀,只要看到了这个单词,那你离真正的核心代码就不远了,我们继续往下

    doLoadBeanDefinitions(inputSource, encodedResource.getResource());

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
    
            try {
                //将资源对象转化为DOM对象,DOM解析是将XML文件全部载入到内存,组装成一颗DOM树,然后通过节点以及节点之间的关系来解析XML文件
                Document doc = doLoadDocument(inputSource, resource);
                //注册bean定义
                int count = registerBeanDefinitions(doc, resource);
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + count + " bean definitions from " + resource);
                }
                return count;
            }
            .......
    
        public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
            //定义bean定义阅读器
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
            //获取bean定义之前的数量
            int countBefore = getRegistry().getBeanDefinitionCount();
            //注册registerBeanDefinitions对象
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
            return getRegistry().getBeanDefinitionCount() - countBefore;
        }
    
        @Override
            public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
                this.readerContext = readerContext;
                doRegisterBeanDefinitions(doc.getDocumentElement());
            }
    
      protected void doRegisterBeanDefinitions(Element root) {
            //这段代码也没有太看懂,大致就是说处理嵌套bean,还有检查xml文件是否匹配,也没能太搞明白
            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);
                    // We cannot use Profiles.of(...) since profile expressions are not supported
                    // in XML config. See SPR-12458 for details.
                    if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                    "] not matching: " + getReaderContext().getResource());
                        }
                        return;
                    }
                }
            }
    
            //解析xml文件之前的处理,这是spring提供给我们的让我们自己自定义的方法,如果我们在加载xml文件之前,需要自定义类型,
            //就继承DefaultBeanDefinitionDocumentReader类就可以执行扩展
            preProcessXml(root);
            //解析bean定义
            parseBeanDefinitions(root, this.delegate);
            //xml定义完之后自定义处理逻辑,如果需要的话
            postProcessXml(root);
    
            this.delegate = parent;
        }
    
        解析配置文件中的内容
        parseBeanDefinitions(root, this.delegate);
    
        以下方法逻辑就是循环解析节点,执行结点解析
        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);
                }
            }
    
            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);
                    }
                    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
                        // recurse
                        doRegisterBeanDefinitions(ele);
                    }
                }
    

    我们选一个平时比较熟悉的processBeanDefinition来看下代码,如果你坚持不懈的看到了这里恭喜你,你马上就拨开云雾见天日了

    DefaultBeanDefinitionDocumentReader

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
            //获取bean定义会话持有者
            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);
                }
                // 发送注册事件
                getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
            }
        }
    
    #BeanDefinitionReaderUtils.registerBeanDefinition
    #BeanDefinitionReaderUtils
     public static void registerBeanDefinition(
                BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
                throws BeanDefinitionStoreException {
    
            // 以bean的当前名称注册bean
            String beanName = definitionHolder.getBeanName();
            registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
            // 如果bean存在别名,再将别名注册进去
            String[] aliases = definitionHolder.getAliases();
            if (aliases != null) {
                for (String alias : aliases) {
                    registry.registerAlias(beanName, alias);
                }
            }
        }
    

    我们看一下正常注册的bean名称的逻辑

    DefaultListableBeanFactory

    @Override
        public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
                throws BeanDefinitionStoreException {
            //校验bean名称和bean定义
            Assert.hasText(beanName, "Bean name must not be empty");
            Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    
            //对beanDefinition执行校验
            if (beanDefinition instanceof AbstractBeanDefinition) {
                try {
                    ((AbstractBeanDefinition) beanDefinition).validate();
                }
                catch (BeanDefinitionValidationException ex) {
                    throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                            "Validation of bean definition failed", ex);
                }
            }
    
            //beanDefinitionMap中获取对应的bean名称是否存在BeanDefinition,如果存在,校验不同的配置文件中定义的同名bean是否可以覆盖
            //如果我们手动定义为false,这里就会抛出异常,并将当前的bean更新到beanDefinitionMap
            BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
            if (existingDefinition != null) {
                if (!isAllowBeanDefinitionOverriding()) {
                    throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
                }
    
                this.beanDefinitionMap.put(beanName, beanDefinition);
            }
            else {
                //bean是否已经被至少创建过一次,如果被创建过,及执行更新逻辑
                if (hasBeanCreationStarted()) {
                    // Cannot modify startup-time collection elements anymore (for stable iteration)
                    synchronized (this.beanDefinitionMap) {
                        //更新当前bean对应的beanDefinition到map中
                        this.beanDefinitionMap.put(beanName, beanDefinition);
                        List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                        //将需要更新的beanDefinitionNames放入集合中
                        updatedDefinitions.addAll(this.beanDefinitionNames);
                        updatedDefinitions.add(beanName);
                        this.beanDefinitionNames = updatedDefinitions;
                        if (this.manualSingletonNames.contains(beanName)) {
                            Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                            updatedSingletons.remove(beanName);
                            this.manualSingletonNames = updatedSingletons;
                        }
                    }
                }
                else {
                    // 说明没有被创建过,将bean名称作为key,beanDefinition作为value,存入map中
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    //将bean的名称存入beanDefinitionNames集合中
                    this.beanDefinitionNames.add(beanName);
                    this.manualSingletonNames.remove(beanName);
                }
                this.frozenBeanDefinitionNames = null;
            }
    
            //如果存在不同配置文件,同样的bean名称,则执行重置操作用于更新,所以当我们将不同的配置文件定义同一个bean名称的时候
            //会在这里执行重置,也就是替换操作,最后只留最后一次注册的bean
            if (existingDefinition != null || containsSingleton(beanName)) {
                resetBeanDefinition(beanName);
            }
        }
    

    走到这里,我们的bean就注册完成了,最终是就是将注册的bean放入map集合中
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    最终的结果就是如下图所示


    image.png

    这样,我们我们就获得了bean工厂对象,里面存储了,已bean名称为key,beanDefiition为value的map集合中
    beanDefiition中存储了class路径等等信息
    终于分析完了第一个模块,源码分析还远没有结束,继续加油!

    相关文章

      网友评论

          本文标题:spring源码分析四(从refresh方法说起)

          本文链接:https://www.haomeiwen.com/subject/guucohtx.html