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

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

  • 前言
    • 源码
    • 初始化配置
    • 加载Bean
      • 加载Bean对象
      • 加载Bean切面
    • Bean工厂后置处理器
    • 注册Bean后置处理器
    • 初始化消息源
    • 初始化应用程序事件多播器
    • 注册监听器
    • 完成Bean工厂初始化
    • Bean初始化完成刷新
    • 应用上下文创建完成
    • 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");}
}
<?xml version="1.0" encoding="UTF-8"?>
<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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://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>

初始化配置

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

    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();}}

紧接着调用,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);//省略部分代码... ...}}

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

在这里插入图片描述

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

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {this.refreshBeanFactory();return this.getBeanFactory();}

先调用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);}}

执行 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);}}

通过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);}} //省略部分代码...}}

然后继续往下走,调用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;}}

我们可以看下是如何获取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;}}

在这里插入图片描述

也就是说通过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;} }

加载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;}

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

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

详细解析看注释

在这里插入图片描述
最后调用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;}

创建好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));}}

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

加载Bean切面

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

循环下一个<aop:config>标签,判断不是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);}

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

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

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

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

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

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

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

在这里插入图片描述

    public String registerWithGeneratedName(BeanDefinition beanDefinition) {//生成唯一bean名称String generatedName = this.generateBeanName(beanDefinition);//注册beanthis.getRegistry().registerBeanDefinition(generatedName, beanDefinition);return generatedName;}

生成的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;}//省略部分代码... ...}

打印第二行日志

在这里插入图片描述
回到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();}}}

将生成好的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()));}}

注册Bean后置处理器

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

    protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);}

进入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);}

调用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);}}

应用上下文创建完成

所有准备工作完成后,调用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("指定初始化方法");}@PostConstructpublic void init2(){System.out.println("注解初始化");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("实现类初始化");}}

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

然后再来讲解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("指定销毁方法");}@PreDestroypublic void destroy2(){System.out.println("注解销毁");}@Overridepublic void destroy() throws Exception {System.out.println("实现类销毁");}
}

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

启动容器的区别

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

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

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

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

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

    public AnnotationConfigApplicationContext(String... basePackages) {this();this.scan(basePackages);this.refresh();}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/155234.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VSCODE+PHP8.2配置踩坑记录

VSCODEPHP8.2配置踩坑记录 – WhiteNights Site 我配置过的最恶心的环境之一&#xff1a;windows上的php。另一个是我centos服务器上的php。 进不了断点 端口配置和xdebug的安装 这个应该是最常见的问题了。从网上下载完php并解压到本地&#xff0c;打开vscode&#xff0c;安装…

【网路安全 --- Linux,window常用命令】网络安全领域,Linux和Windows常用命令,记住这些就够了,收藏起来学习吧!!

一&#xff0c;Linux 1-1 重要文件目录 1-1-1 系统运行级别 /etc/inittab 1-1-2 开机启动配置文件 /etc/rc.local /etc/rc.d/rc[0~6].d## 当我们需要开机启动自己的脚本时&#xff0c;只需要将可执行脚本丢在 /etc/init.d 目录下&#xff0c;然后在 /etc/rc.d/rc*.d 中建…

XLSX.utils.sheet_to_json() 数字格式转为字符串格式

raw 默认为true&#xff0c;设置为false就可以了 XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {raw:false})

Maven NetBeans

目录 在 NetBeans 里打开一个 Maven 项目 在 NetBeans 里构建一个 Maven 项目 在 NetBeans 里运行应用程序 NetBeans 6.7 及更新的版本已经内置了 Maven。对于以前的版本&#xff0c;可在插件管理中心获取 Maven 插件。此例中我们使用的是 NetBeans 6.9。 关于 NetBeans 的一…

竹云筑基,量子加密| 竹云携手国盾量子构建量子身份安全防护体系

9月23日-24日&#xff0c;2023量子产业大会在安徽合肥举行。作为量子科技领域行业盛会&#xff0c;2023年量子产业大会以“协同创新 量点未来”为主题&#xff0c;展示了前沿的量子信息技术、产业创新成果&#xff0c;并举办主旨论坛、量子科普讲座等系列专项活动。量子信息作为…

PDMS二次开发(二十一)——关于Pipeline工具生成螺栓材料表的计算思路

目录 1.简述2.螺栓元件的数据2.1 A1A等级中螺栓元件库2.2 A1A等级中法兰元件库2.3 A1A要指定螺栓等级2.4 螺栓等级数据解析 3.问题解释 1.简述 因为有好几个网友问到螺栓材料表生成报错的问题&#xff0c;我初步分析可能还是因为螺栓元件库的问题&#xff0c;我这里对Pipeline…

Spark入门

Spark 1.Spark概述 2.Spark特点 3.RDD概述 1. Spark概述 什么是Spark 回顾&#xff1a;Hadoop主要解决&#xff0c;海量数据的存储和海量数据的分析计算。 Spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。 Hadoop与Spark历史 Hadoop与Spark框架对比 Dr…

内网渗透面试问题

文章目录 1、熟悉哪些域渗透的手段2、详细说明哈希传递的攻击原理NTLM认证流程哈希传递 3、聊一下黄金票据和白银票据4、shiro反序列化漏洞的形成原因&#xff0c;尝试使用burp抓包查看返回包内容安装环境漏洞验证 5、log4j组件的命令执行漏洞是如何造成的6、画图描述Kerberos协…

9-AJAX-上-原理详解

一、定义 1、什么是Ajax Ajax&#xff1a;即异步 JavaScript 和XML。Ajax是一种用于创建快速动态网页的技术。通过在后台与进行少量数据交换&#xff0c;Ajax可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下&#xff0c;对网页的某部分进行更新。而传统的…

时代风口中的Web3.0基建平台,重新定义Web3.0!

近年来&#xff0c;Web3.0概念的广泛兴起&#xff0c;给加密行业带来了崭新的叙事方式&#xff0c;同时也为加密行业提供了更加具有想象力的应用场景与商业空间&#xff0c;并让越来越多的行业从业者们意识到只有更大众化的市场共性需求才能推动加密市场的持续繁荣。当前围绕这…

【物联网】Arduino+ESP8266物联网开发(一):开发环境搭建 安装Arduino和驱动

ESP8266物联网开发 【物联网】ArduinoESP8266开发环境配置 1.开发环境安装 开发软件下载地址&#xff1a; 链接: https://pan.baidu.com/s/1BaOY7kWTvh4Obobj64OHyA?pwd3qv8 提取码: 3qv8 1.1 安装驱动 将ESP8266连接到电脑上&#xff0c;安装ESP8266驱动CP210x 安装成功后&…

【快速入门】JVM之类加载机制与Native

感慨&#xff1a; 如何定义一个合格的Java程序员&#xff0c;Java程序员要了解掌握哪些知识点&#xff0c;网上的面试题太多了&#xff0c;后端需要了解掌握的知识点太多太多了&#xff0c;Java基础、数据结构、异常、多线程、Spring、Spring boot、事务、算法、数据库&#xf…

asp.net网上销售系统VS开发mysql数据库web结构c#编程Microsoft Visual Studio计算机毕业设计

一、源码特点 asp.net 网上销售系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为mysql&#xff0c;使用c#语言开发 aspnet 网上销售系统1 二、功能介绍 前台功能…

Spring Boot:自定义注解--annotation

目录 自定义注解的定义和作用范围如何创建自定义注解创建注解接口 如何使用自定义注解进行数据验证创建注解处理器控制器中使用注解 如何为字段添加注解 自定义注解的定义和作用范围 自定义注解可以作用在类、方法、属性、参数、异常、字段或其他注解上。 如何创建自定义注解…

国际教材概念基础

各种区别 缩写 A-LEVEL&#xff08;大学预科&#xff09;&#xff1a;General Certificate of Education Advanced Level AP&#xff1a;Advanced Placement&#xff08;美国地区&#xff1a;美高AP&#xff09; GCSE&#xff1a;General Certificate of Secondary Educati…

leetcode:217. 存在重复元素(python3解法)

难度&#xff1a;简单 给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 &#xff0c;返回 true &#xff1b;如果数组中每个元素互不相同&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3,1] 输出&#xff1a;true 示例 2&#xff1a; 输…

睿趣科技:抖音店铺怎么取名受欢迎

抖音作为国内最大的短视频平台&#xff0c;其商业价值不容忽视。许多商家和创作者都在抖音上开设了自己的店铺&#xff0c;而一个富有创意和吸引力的店铺名字&#xff0c;往往能带来更多的客流量。那么&#xff0c;如何为抖音店铺取个好名字呢?以下是一些有用的建议。 明确定位…

数据结构(2-5~2-8)

2-5编写算法&#xff0c;在单链表中查找第一值为x的结点&#xff0c;并输出其前驱和后继的存储位置 #include<stdio.h> #include<stdlib.h>typedef int DataType; struct Node {DataType data; struct Node* next; }; typedef struct Node *PNode; …

虹科科技 | 探索CAN通信世界:PCAN-Explorer 6软件的功能与应用

CAN&#xff08;Controller Area Network&#xff09;总线是一种广泛应用于汽车和工业领域的通信协议&#xff0c;用于实时数据传输和设备之间的通信。而虹科的PCAN-Explorer 6软件是一款功能强大的CAN总线分析工具&#xff0c;为开发人员提供了丰富的功能和灵活性。本文将重点…

Java解析E文件工具类

import lombok.extern.slf4j.Slf4j;import java.io.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List;/*** Description E文件工具类*/ Slf4j public class EFileUtils {/*** 读字符串* param text …