• Java进击框架:Spring-Bean初始化过程(五)


    前言

    本章节主要介绍,面试中经常问到的Spring-Bean初始化过程。

    源码

    接下来会通过以下代码,进行debug调试,讲解(spring版本为5.3.23,源码太长会转换为图片,节省字数,提高阅读性)。

    public class Test {
    
        public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Test bean = applicationContext.getBean(Test.class);
            bean.a();
            applicationContext.close();
            /** Output:
             *  初始化
             *  before aspect
             *  方法调用
             *  销毁
             */
        }
    
        public void a(){ System.out.println("方法调用"); }
        public void init(){ System.out.println("初始化"); }
        public void destroy(){ System.out.println("销毁"); }
    }
    public class MyAspect {
        public void before() {
            System.out.println("before aspect");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd">
        <bean id="test" class="com.example.Test" init-method="init" destroy-method="destroy">bean>
        <bean id="myAspect" class="com.example.MyAspect">bean>
        <aop:config>
            <aop:aspect ref="myAspect">
                <aop:before method="before" pointcut="execution(* com.example.Test.a(..))">aop:before>
            aop:aspect>
        aop:config>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    初始化配置

    首先进入构造方法,会设置默认值

        public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
            this(new String[]{configLocation}, true, (ApplicationContext)null);
        }
        public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
            super(parent);
            this.setConfigLocations(configLocations);
            if (refresh) {
                this.refresh();
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    紧接着调用,super()方法,最终调用AbstractApplicationContext类的构造方法,进行初始化配置,创建标准环境占位符、系统参数)等。

    在这里插入图片描述

    然后进入核心refresh()方法

        public void refresh() throws BeansException, IllegalStateException {
            synchronized(this.startupShutdownMonitor) {
            	//启动步骤
                StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
                this.prepareRefresh();
                ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
                this.prepareBeanFactory(beanFactory);
    			//省略部分代码... ...
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    调用prepareRefresh()方法,准备刷新

    在这里插入图片描述

    执行第一个else代码块中逻辑,打印了第一条日志,然后初始化监听器、事件
    在这里插入图片描述
    然后进入obtainFreshBeanFactory()方法

        protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
            this.refreshBeanFactory();
            return this.getBeanFactory();
        }
    
    • 1
    • 2
    • 3
    • 4

    先调用AbstractRefreshableApplicationContext.refreshBeanFactory()方法,

        protected final void refreshBeanFactory() throws BeansException {
        	//省略部分代码... ...
            try {
                DefaultListableBeanFactory beanFactory = this.createBeanFactory();
                beanFactory.setSerializationId(this.getId());
                //省略部分代码... ...
            } catch (IOException var2) {
                throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var2);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行 createBeanFactory()方法创建bean工厂,往下走初始化bean的一些容器。

    在这里插入图片描述
    初始化bean容器完成。

    加载Bean

    加载Bean对象

    bean工厂创建完毕后,继续往下走,调用AbstractBeanDefinitionReader.loadBeanDefinitions()方法

        protected final void refreshBeanFactory() throws BeansException {
            try {
                DefaultListableBeanFactory beanFactory = this.createBeanFactory();
                beanFactory.setSerializationId(this.getId());
                this.customizeBeanFactory(beanFactory);
                this.loadBeanDefinitions(beanFactory);
                this.beanFactory = beanFactory;
            } catch (IOException var2) {
                throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var2);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过getResources()方法,获取上下文的资源Resource对象包含的参数很多,比如jar包、类路径啥的)

    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
            ResourceLoader resourceLoader = this.getResourceLoader();
            if (resourceLoader == null) {
                throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
            } else {
                int count;
                if (resourceLoader instanceof ResourcePatternResolver) {
                    try {
                    	//获取上下文资源,包含path和上下文资源
                        Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                        count = this.loadBeanDefinitions(resources);
                        if (actualResources != null) {
                            Collections.addAll(actualResources, resources);
                        }
    
                        if (this.logger.isTraceEnabled()) {
                            this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                        }
    
                        return count;
                    } catch (IOException var6) {
                        throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6);
                    }
                } 
                //省略部分代码...
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    然后继续往下走,调用XmlBeanDefinitionReader.loadBeanDefinitions()方法

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
            //省略部分代码...
            else {
                int var6;
                try {
                    InputStream inputStream = encodedResource.getResource().getInputStream();
                    Throwable var4 = null;
    
                    try {
                        InputSource inputSource = new InputSource(inputStream);
                        if (encodedResource.getEncoding() != null) {
                            inputSource.setEncoding(encodedResource.getEncoding());
                        }
    
                        var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                    } catch (Throwable var24) {
                        var4 = var24;
                        throw var24;
                    } 
    			//省略部分代码... ...
                return var6;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    我们可以看下是如何获取InputStream的,我们可以找到子类ClassPathResource

        public InputStream getInputStream() throws IOException {
            InputStream is;
            if (this.clazz != null) {
                is = this.clazz.getResourceAsStream(this.path);
            } else if (this.classLoader != null) {
                is = this.classLoader.getResourceAsStream(this.path);
            } else {
                is = ClassLoader.getSystemResourceAsStream(this.path);
            }
    
            if (is == null) {
                throw new FileNotFoundException(this.getDescription() + " cannot be opened because it does not exist");
            } else {
                return is;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    也就是说通过path参数从ClassLoader中寻找文件,有点类似于File获取文件。

    实际点开InputSource可以发现,寻找的路径为target编译文件中。

    在这里插入图片描述
    获取文件流后,进入doLoadBeanDefinitions()方法,

        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
            try {
                Document doc = this.doLoadDocument(inputSource, resource);
                int count = this.registerBeanDefinitions(doc, resource);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Loaded " + count + " bean definitions from " + resource);
                }
    
                return count;
            } 
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    加载bean信息(这里就不细讲过程了,直接贴图),可以看到把xml里面的属性和值都解析出来了(可以看下fNodeName对象和fNodeValue对象,此时BeanDefinition中的数量依旧是0)
    在这里插入图片描述
    接着往下走进入DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element)方法

        protected void doRegisterBeanDefinitions(Element root) {
            BeanDefinitionParserDelegate parent = this.delegate;
            this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
            if (this.delegate.isDefaultNamespace(root)) {
                String profileSpec = root.getAttribute("profile");
                if (StringUtils.hasText(profileSpec)) {
                    String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
                    if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                        }
    
                        return;
                    }
                }
            }
    
            this.preProcessXml(root);
            this.parseBeanDefinitions(root, this.delegate);
            this.postProcessXml(root);
            this.delegate = parent;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    介绍下root元素包含的一些属性(attributes属性和firstChild属性,结合xml文件看更方便理解),如图:

    在这里插入图片描述
    调用createDelegate()方法,创建一个解析器委托(不详细讲解),往下走DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(Element , BeanDefinitionParserDelegate)方法先判断是不是默认命名空间,返回true,从根节点元素开始,获取根节点的子节点列表,进入循环,第一次是空格,第二次返回一个bean,判断是元素,是默认命名空间往下走,进入parseDefaultElement()方法

    在这里插入图片描述

    在这里插入图片描述

    判断解析的元素是那种类型,这里进入bean判断,往下走最后进入BeanDefinitionParserDelegate .parseBeanDefinitionElement(Element,BeanDefinition)方法

        private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
            if (delegate.nodeNameEquals(ele, "import")) {
                this.importBeanDefinitionResource(ele);
            } else if (delegate.nodeNameEquals(ele, "alias")) {
                this.processAliasRegistration(ele);
            } else if (delegate.nodeNameEquals(ele, "bean")) {
                this.processBeanDefinition(ele, delegate);
            } else if (delegate.nodeNameEquals(ele, "beans")) {
                this.doRegisterBeanDefinitions(ele);
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    详细解析看注释

    在这里插入图片描述
    最后调用parseBeanDefinitionElement(Element, String, @Nullable BeanDefinition containingBean)重载方法,详细解析看注释

    在这里插入图片描述

    特别讲解下上图中createBeanDefinition()方法,生成抽象AbstractBeanDefinition类(一个标签对应所有元素的类), 设置相对应的类路径。

        public static AbstractBeanDefinition createBeanDefinition(@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
            GenericBeanDefinition bd = new GenericBeanDefinition();
            bd.setParentName(parentName);
            if (className != null) {
                if (classLoader != null) {
                    bd.setBeanClass(ClassUtils.forName(className, classLoader));
                } else {
                	//设置bean的类名
                    bd.setBeanClassName(className);
                }
            }
    
            return bd;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    创建好AbstractBeanDefinition类,往下走调用parseBeanDefinitionAttributes()方法,开始解析bean标签的属性,进行属性设置,具体参考代码注释。

    在这里插入图片描述

    解析完毕,回到上层parseBeanDefinitionElement(Element, String, @Nullable BeanDefinition containingBean)
    (解析注入方法),再回到最上层parseBeanDefinitionElement(Element,BeanDefinition)方法,可以看到相应的属性以代码进行赋值。
    在这里插入图片描述

    最终new BeanDefinitionHolder()设置bean容器持有对象,赋值名称,别名。

    在这里插入图片描述
    返回到上一层processBeanDefinition()方法,BeanDefinitionHolder元素解析完毕,不为空,进入if判断

        protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
            BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
            if (bdHolder != null) {
            	//修饰后重新赋值
                bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    
                try {
                	//循环bean的所有元素,进行修饰
                    BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
                } catch (BeanDefinitionStoreException var5) {
                    this.getReaderContext().error("Failed to register bean definition with name '" 
                    + bdHolder.getBeanName() + "'", ele, var5);
                }
    
                this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    调用DefaultListableBeanFactory.registerBeanDefinition()方法,进入else代码块,给beanDefinitionMapbeanDefinitionNames插入数据
    在这里插入图片描述
    到此第一个bean实例化的操作已经完成,紧接着第二个bean:myAspect,按照上述逻辑进行注册。

    加载Bean切面

    进入循环下一个标签,如图所示,进行切面的实例化。
    在这里插入图片描述
    依旧是按照原有实例化bean的逻辑进行。

    循环下一个标签,判断不是bean的空间,进入else代码块,解析自定元素,如图所示
    在这里插入图片描述

    进入parseCustomElement()方法,先获取命名空间URL,不为空进入else代码块,
    在这里插入图片描述
    进入resolve()方法,通过命名空间的URL找到对应的类
    在这里插入图片描述
    NamespaceHandler不为空进入else代码块,调用parse()方法,最终进入ConfigBeanDefinitionParser.parse()方法解析,调用configureAutoProxyCreator()方法,配置自动代理创建器
    在这里插入图片描述
    进入方法,往下一直走调用AopConfigUtils.registerOrEscalateApcAsRequired()方法

        public static void registerAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {
        	//注册自动代理
            BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));
            //必要时使用类代理
            useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
            //注册组件
            registerComponentIfNecessary(beanDefinition, parserContext);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    先判断org.springframework.aop.config.internalAutoProxyCreator是否创建,进入else代码块,注册切面代理bean
    在这里插入图片描述

    先调用registerAutoProxyCreatorIfNecessary()方法,创建代理bean第三个bean

    在这里插入图片描述
    回到上层,再调用registerComponentIfNecessary()方法,将标签注册到代理的bean

        private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {
            if (beanDefinition != null) {
                parserContext.registerComponent(new BeanComponentDefinition(beanDefinition, "org.springframework.aop.config.internalAutoProxyCreator"));
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    回到ConfigBeanDefinitionParser.parse()方法,调用DomUtils.getChildElements()方法,解析标签的内容,得到所有子元素,循环遍历,获取每个元素的localName,进入对应的代码块,这里进入parseAspect()方法

    在这里插入图片描述
    解析标签,切面类的属性,循环标签的所有子标签

    在这里插入图片描述
    调用parseAdvice()方法,解析AOP的通知以及指向bean,得到RootBeanDefinition根级别的 Bean 定义,调用registerWithGeneratedName()方法,生成名称寄存器

    在这里插入图片描述

        public String registerWithGeneratedName(BeanDefinition beanDefinition) {
        	//生成唯一bean名称
            String generatedName = this.generateBeanName(beanDefinition);
            //注册bean
            this.getRegistry().registerBeanDefinition(generatedName, beanDefinition);
            return generatedName;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    生成的bean名称是org.springframework.aop.aspectj.AspectJPointcutAdvisor,进入else代码块生成,调用uniqueBeanName()方法,名称改为org.springframework.aop.aspectj.AspectJPointcutAdvisor#0
    在这里插入图片描述
    回到上层,调用registerBeanDefinition()方法注册bean(这里不再详细讲解,参考前面示例),回到最外层XmlBeanDefinitionReader.doLoadBeanDefinitions()方法,计算出bean的总数4

        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
            try {
                Document doc = this.doLoadDocument(inputSource, resource);
                int count = this.registerBeanDefinitions(doc, resource);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Loaded " + count + " bean definitions from " + resource);
                }
    
                return count;
            }
            //省略部分代码... ...
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    打印第二行日志

    在这里插入图片描述
    回到AbstractApplicationContext.refresh()方法,往下走调用prepareBeanFactory()方法

        public void refresh() throws BeansException, IllegalStateException {
            synchronized(this.startupShutdownMonitor) {
                StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
                this.prepareRefresh();
                ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
                this.prepareBeanFactory(beanFactory);
    
                try {
                    this.postProcessBeanFactory(beanFactory);
                    StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                    this.invokeBeanFactoryPostProcessors(beanFactory);
                    this.registerBeanPostProcessors(beanFactory);
                    beanPostProcess.end();
                    this.initMessageSource();
                    this.initApplicationEventMulticaster();
                    this.onRefresh();
                    this.registerListeners();
                    this.finishBeanFactoryInitialization(beanFactory);
                    this.finishRefresh();
                } catch (BeansException var10) {
                    if (this.logger.isWarnEnabled()) {
                        this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
                    }
    
                    this.destroyBeans();
                    this.cancelRefresh(var10);
                    throw var10;
                } finally {
                    this.resetCommonCaches();
                    contextRefresh.end();
                }
    
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    将生成好的bean工厂传入prepareBeanFactory()方法,注册所需依赖接口

    在这里插入图片描述
    至此基本容器配置初始化已经完成。

    Bean工厂后置处理器

    Spring 框架中,Bean 后置处理器(BeanPostProcessor)是一种特殊类型的Bean,它允许在容器实例化和配置其他所有Bean之前以及之后对Bean进行自定义处理。

    回到refresh()方法,往下调用invokeBeanFactoryPostProcessors()方法

        protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        	//里面没做什么处理,不讲解
            PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors());
            if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean("loadTimeWeaver")) {
                beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
                beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注册Bean后置处理器

    回到refresh()方法,往下调用registerBeanPostProcessors()方法

        protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
            PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
        }
    
    • 1
    • 2
    • 3

    进入registerBeanPostProcessors()方法, 注册Bean后置处理器,先调用getBeanNamesForType()方法,获取需要处理的名称
    在这里插入图片描述
    通过循环后置处理器,调用while(){}代码中的getBean()方法,将name转换为bean
    在这里插入图片描述
    最终进入到AbstractBeanFactory.doGetBean()方法,判断bean是单例,进入if判断,调用getSingleton()方法,获取单例bean实例
    在这里插入图片描述
    singletonObjects等于null,进入第二个if代码块,打印第三行日志,创建第一bean
    在这里插入图片描述
    详细的流程,如图
    在这里插入图片描述
    然后进入createBean()方法,调用doCreateBean()方法创建bean,进入第二if代码块,创建bean的包装实例,然后从实例中获取bean对象返回
    在这里插入图片描述
    自此注册Bean后置处理器结束

    初始化消息源

    initMessageSource()Spring框架中的一个方法,用于初始化消息源(MessageSource)。消息源是Spring国际化(i18n)功能的核心组件之一,它用于加载和解析不同语言的消息资源文件,以便应用程序可以根据不同地区和语言提供本地化的文本消息。

    回到refresh()方法,往下调用initMessageSource()方法,注册beanNamemessageSource的单例Bean(不做详细讲解)

    在这里插入图片描述

    初始化应用程序事件多播器

    Spring 框架中,应用程序事件多播器用于发送和接收应用程序中的事件,实现了观察者模式。

    回到refresh()方法,往下调用initApplicationEventMulticaster()方法,注册beanNameapplicationEventMulticaster的单例Bean(不做详细讲解)

    在这里插入图片描述

    注册监听器

    registerListeners()AbstractApplicationContext 类中的一个方法,用于注册监听器(Listener)到应用程序上下文(ApplicationContext)。通过调用 registerListeners() 方法,我们可以将自定义的监听器添加到应用程序上下文中,以便监听和响应相应的事件。

    回到refresh()方法,往下调用registerListeners()方法(不做详细讲解)

    在这里插入图片描述

    完成Bean工厂初始化

    当应用程序上下文刷新完成后,Spring 容器会调用 finishBeanFactoryInitialization() 方法来完成 Bean 工厂的初始化过程。这个方法会遍历应用程序上下文中所有的 Bean 定义,并根据定义的配置信息创建相应的 Bean 实例。

    回到refresh()方法,往下调用finishBeanFactoryInitialization()方法,然后调用preInstantiateSingletons()方法,用于提前实例化所有单例 bean

    在这里插入图片描述

    进入到preInstantiateSingletons()方法,实例化单例,先将所有的beanName转换为一个List

    在这里插入图片描述
    然后进入嵌套循环,获取第一个beanName为test,获取根级别的 Bean 定义,判断bean定义不是抽象类,结束循环,调用getBean()方法获取,创建的bean对象

    在这里插入图片描述
    进入到getBean()方法,会发现实际上也是调用了doGetBean()方法(在注册Bean后置处理器中有讲解到),演示打印的日志过程

        public Object getBean(String name) throws BeansException {
            return this.doGetBean(name, (Class)null, (Object[])null, false);
        }
    
    • 1
    • 2
    • 3

    调用getSingleton()方法,获取单例bean实例

    在这里插入图片描述
    进入getSingleton()方法,打印第四行日志

    在这里插入图片描述
    获取singletonObject对象时,进行第三bean的实例org.springframework.aop.aspectj.AspectJPointcutAdvisor#0(通常是因为在bean之间存在依赖关系)

    在这里插入图片描述
    然后按照原逻辑,打印第五行日志

    在这里插入图片描述
    然后进入createBean()方法,调用doCreateBean()方法创建bean

    getSingleton()方法和createBean()方法的调用顺序并不是单纯的先后顺序,并且可能因为应用程序的复杂性而有所变化。这个现象并不影响Spring容器正常的初始化和运行流程

    在这里插入图片描述
    beanNameorg.springframework.aop.aspectj.AspectJPointcutAdvisor#0时,会先解析切面的表达式,进行一系列处理(这里不详细介绍)
    在这里插入图片描述
    beanNametest时,由于配置了init-method属性,进入doCreateBean()方法,调用initializeBean()方法中的invokeInitMethods()方法,示例代码如下:
    在这里插入图片描述
    进入invokeInitMethods()方法,获取初始化方法名,进入if代码块,调用invokeCustomInitMethod()方法,找到类对应的方法名

    在这里插入图片描述

    然后调用invoke(bean)方法(动态代理),进入指定初始化方法,打印第6行日志。

    在这里插入图片描述

    test初始化完成之后,进行myAspect的初始化流程,打印第7行日志

    在这里插入图片描述

    Bean初始化完成刷新

    finishRefresh()Spring框架中的一个核心方法,它在Spring容器刷新时被调用,并负责将容器中的所有Bean初始化完成。该方法是Spring中整个容器初始化过程的最后一步。其作用是确保所有Bean的初始化工作都已经完成。

        protected void finishRefresh() {
        	//清除资源缓存:为了提高性能和效率,会对一些常用的资源进行缓存
            this.clearResourceCaches();
            //初始化生命周期处理器
            this.initLifecycleProcessor();
            //刷新生命周期处理器,确保应用程序处于正确的生命周期状态。
            this.getLifecycleProcessor().onRefresh();
            //发布一个ContextRefreshedEvent事件,以通知已注册的事件监听器执行相应的操作。
            this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));
            if (!NativeDetector.inNativeImage()) {
            	//将当前的应用程序上下文注册到Spring Actuator的LiveBeansView中,以便在运行时查看应用程序中的Bean信息。
                LiveBeansView.registerApplicationContext(this);
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    应用上下文创建完成

    所有准备工作完成后,调用getBean()方法,获取BeanName为test的实例,调用a()方法,在此之前先进入aop,声明了切入点

    在这里插入图片描述

    所以先进入before()方法,打印第8行日志

    在这里插入图片描述
    然后在进入自定义方法,打印第9行日志
    在这里插入图片描述
    然后调用applicationContext.close()方法关闭容器,打印上下文关闭时间,再进入自定义销毁方法
    在这里插入图片描述
    整个bean的初始化过程结束。

    Bean初始化和销毁的不同顺序

    我们先讲解Bean初始化的顺序优先级:构造方法、注解、实现类、指定初始化方法,示例代码如下:

    public class Test implements InitializingBean {
    
        public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Test bean = applicationContext.getBean(Test.class);
            /** Output:
             *  构造方法初始化
             *  注解初始化
             *  实现类初始化
             *  指定初始化方法
             */
        }
    
        public Test() {
            System.out.println("构造方法初始化");
        }
    
        public void init(){
            System.out.println("指定初始化方法");
        }
    
        @PostConstruct
        public void init2(){
            System.out.println("注解初始化");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("实现类初始化");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    由此可以得出初始化优先级:构造方法>注解>实现类>指定初始化。

    然后再来讲解Bean销毁的顺序优先级:注解、实现类、指定初始化方法,示例代码如下:

    public class Test implements DisposableBean {
    
        public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Test bean = applicationContext.getBean(Test.class);
            applicationContext.close();
            /** Output:
             *  注解销毁
             *  实现类销毁
             *  指定销毁方法
             */
        }
    
        public void destroy1(){
            System.out.println("指定销毁方法");
        }
    
        @PreDestroy
        public void destroy2(){
            System.out.println("注解销毁");
        }
    
        @Override
        public void destroy() throws Exception {
            System.out.println("实现类销毁");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    由此可以得出销毁优先级:注解>实现类>指定初始化。

    启动容器的区别

    在之前的介绍中我们知道容器常用的启动有两种:AnnotationConfigApplicationContextClassPathXmlApplicationContext

    1. AnnotationConfigApplicationContext:基于Java配置类的容器启动方式,通常用于基于注解的配置。
    2. ClassPathXmlApplicationContext基于XML配置文件的容器启动方式,通常用于基于XML的配置。

    经过本章节的介绍相信对ClassPathXmlApplicationContext的流程已经不陌生了,下面简单介绍AnnotationConfigApplicationContext的启动流程:

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.example");
    
    • 1

    当我们定义了包名访问路径,再其构造方法中,会调用scan(basePackages)方法,扫描包路径下的所有类,然后再去调用公用的refresh()方法。

        public AnnotationConfigApplicationContext(String... basePackages) {
            this();
            this.scan(basePackages);
            this.refresh();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    doScan()方法中,将扫描的类转换成BeanDefinition,示例代码如下:
    在这里插入图片描述

    总体来说,两种方式启动的作用是相同的,都是启动Spring容器并管理Bean。只是配置的来源不同,一个是基于Java配置类,一个是基于XML配置文件。

    面试题:Spring有几级缓存,他们负责什么?

    在这里插入图片描述

    1. 一级缓存

    一级缓存是 Spring 默认开启的缓存,它是基于 SpringBeanFactory 实现的。当我们在应用程序中获取一个 bean 时,Spring 会首先查找一级缓存,如果在缓存中找到该 bean,则直接返回缓存中的实例,否则创建新的实例,并将其放入缓存中。

    一级缓存是基于 Spring 容器的,因此对于每个容器,都有它自己的一级缓存。当容器关闭时,所有的一级缓存也会被销毁。

    1. 二级缓存

    二级缓存是在 Spring 中为了加快 bean 的创建而引入的。它是基于 ApplicationContext 实现的,可以被多个容器共享。当多个容器同时需要同一个 bean 时,二级缓存可以避免重复创建实例,提高应用程序的性能。

    Spring 的二级缓存默认是关闭的,需要手动开启。可以通过在 Spring 配置文件中添加 或者在 Java Config 中使用 @EnableCaching 注解来开启缓存功能。

    1. 三级缓存

    三级缓存是指在 Spring AOP 中使用的缓存机制。当使用 Spring AOP 进行方法拦截时,Spring 会将拦截后的结果存储在三级缓存中。如果下次使用同样的参数调用相同的方法,则 Spring 会直接从缓存中获取结果,避免重复执行拦截逻辑。

    需要注意的是,三级缓存仅适用于基于代理的 AOP,不适用于基于 AspectJAOP

    面试题:解决循环依赖为什么要用二级缓存或三级缓存?

    对象在三级缓存中的创建流程:

    A依赖B,B依赖A

    1、A创建过程中需要B,于是先将A放到三级缓存,去实例化B。

    2、B实例化的过程中发现需要A,于是B先查一级缓存寻找A,如果没有,再查二级缓存,如果还没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。

    3、B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中的状态)。然后回来接着创建A,此时B已经创建结束,可以直接从一级缓存里面拿到B,去完成A的创建,并将A放到一级缓存。

    只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。

    二三级缓存就是为了解决循环依赖,且之所以是二三级缓存而不是二级缓存,主要是可以解决循环依赖对象需要提前被aop代理(三级缓存,就是给AOP动态代理用的)。

  • 相关阅读:
    JUC学习笔记
    CSS阴影优化气泡框样式
    python自动化测试全栈之ApiFox批量运行脚本生成报告
    二叉树的遍历 中序线索二叉树
    R 语言 基于关联规则与聚类分析的消费行为统计
    构建镜像开源工具 buildah
    手机喊话应用实现思路
    IOT物联网系统架构
    Map集合(超详解)
    比尔·盖茨晒48年前简历:“没你们的好看”
  • 原文地址:https://blog.csdn.net/qq_39940674/article/details/130862297