这篇文章主要讲解了“spring初始化方法的执行顺序及其原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“spring初始化方法的执行顺序及其原理是什么”吧!

Spring中初始化方法的执行顺序首先通过一个例子来看其顺序

/***调用顺序init2(PostConstruct注解)-->afterPropertiesSet(InitializingBean接口)-->init3(init-method配置)*/publicclassTestimplementsInitializingBean{publicvoidinit3(){System.out.println("init3");}@PostConstructpublicvoidinit2(){System.out.println("init2");}@OverridepublicvoidafterPropertiesSet()throwsException{System.out.println("afterPropertiesSet");}}配置

<context:annotation-config/><beanclass="com.cyy.spring.lifecycle.Test"id="test"init-method="init3"/>

通过运行,我们得出其执行顺序为init2(PostConstruct注解) --> afterPropertiesSet(InitializingBean接口) --> init3(init-method配置)。但是为什么是这个顺序呢?我们可以通过分析其源码得出结论。

首先在解析配置文件的时候,碰到context:annotation-config/自定义标签会调用其自定义解析器,这个自定义解析器在哪儿呢?在spring-context的spring.handlers中有配置

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler我们进入这个类看

publicclassContextNamespaceHandlerextendsNamespaceHandlerSupport{@Overridepublicvoidinit(){registerBeanDefinitionParser("property-placeholder",newPropertyPlaceholderBeanDefinitionParser());registerBeanDefinitionParser("property-override",newPropertyOverrideBeanDefinitionParser());registerBeanDefinitionParser("annotation-config",newAnnotationConfigBeanDefinitionParser());registerBeanDefinitionParser("component-scan",newComponentScanBeanDefinitionParser());registerBeanDefinitionParser("load-time-weaver",newLoadTimeWeaverBeanDefinitionParser());registerBeanDefinitionParser("spring-configured",newSpringConfiguredBeanDefinitionParser());registerBeanDefinitionParser("mbean-export",newMBeanExportBeanDefinitionParser());registerBeanDefinitionParser("mbean-server",newMBeanServerBeanDefinitionParser());}}我们看到了annotation-config了

我们只关心这个标签,那我们就进入AnnotationConfigBeanDefinitionParser类中,看它的parse方法

publicBeanDefinitionparse(Elementelement,ParserContextparserContext){Objectsource=parserContext.extractSource(element);//ObtainbeandefinitionsforallrelevantBeanPostProcessors.Set<BeanDefinitionHolder>processorDefinitions=AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(),source);//Registercomponentforthesurrounding<context:annotation-config>element.CompositeComponentDefinitioncompDefinition=newCompositeComponentDefinition(element.getTagName(),source);parserContext.pushContainingComponent(compDefinition);//Nesttheconcretebeansinthesurroundingcomponent.for(BeanDefinitionHolderprocessorDefinition:processorDefinitions){parserContext.registerComponent(newBeanComponentDefinition(processorDefinition));}//Finallyregisterthecompositecomponent.parserContext.popAndRegisterContainingComponent();returnnull;}我们重点看下这行代码

Set<BeanDefinitionHolder>processorDefinitions=AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(),source);

我们追踪进去(其中省略了一些我们不关心的代码)

publicstaticSet<BeanDefinitionHolder>registerAnnotationConfigProcessors(BeanDefinitionRegistryregistry,Objectsource){...//CheckforJSR-250support,andifpresentaddtheCommonAnnotationBeanPostProcessor.if(jsr250Present&&!registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)){RootBeanDefinitiondef=newRootBeanDefinition(CommonAnnotationBeanPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry,def,COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));}...}

在这个方法其中注册了一个CommonAnnotationBeanPostProcessor类,这个类是我们@PostConstruct这个注解发挥作用的基础。

在bean实例化的过程中,会调用AbstractAutowireCapableBeanFactory类的doCreateBean方法,在这个方法中会有一个调用initializeBean方法的地方,

我们直接看initializeBean这个方法

protectedObjectinitializeBean(finalStringbeanName,finalObjectbean,RootBeanDefinitionmbd){if(System.getSecurityManager()!=null){AccessController.doPrivileged(newPrivilegedAction<Object>(){@OverridepublicObjectrun(){invokeAwareMethods(beanName,bean);returnnull;}},getAccessControlContext());}else{invokeAwareMethods(beanName,bean);}ObjectwrappedBean=bean;if(mbd==null||!mbd.isSynthetic()){//调用@PostConstruct方法注解的地方wrappedBean=applyBeanPostProcessorsBeforeInitialization(wrappedBean,beanName);//①}try{//调用afterPropertiesSet和init-method地方invokeInitMethods(beanName,wrappedBean,mbd);//②}catch(Throwableex){thrownewBeanCreationException((mbd!=null?mbd.getResourceDescription():null),beanName,"Invocationofinitmethodfailed",ex);}if(mbd==null||!mbd.isSynthetic()){wrappedBean=applyBeanPostProcessorsAfterInitialization(wrappedBean,beanName);}returnwrappedBean;}

先看①这行,进入applyBeanPostProcessorsBeforeInitialization方法

publicObjectapplyBeanPostProcessorsBeforeInitialization(ObjectexistingBean,StringbeanName)throwsBeansException{Objectresult=existingBean;for(BeanPostProcessorbeanProcessor:getBeanPostProcessors()){result=beanProcessor.postProcessBeforeInitialization(result,beanName);if(result==null){returnresult;}}returnresult;}

我们还记得前面注册的一个类CommonAnnotationBeanPostProcessor,其中这个类间接的实现了BeanPostProcessor接口,所以此处会调用CommonAnnotationBeanPostProcessor类的postProcessBeforeInitialization方法,它本身并没有实现这个方法,但他的父类InitDestroyAnnotationBeanPostProcessor实现了postProcessBeforeInitialization的方法,其中这个方法就实现调用目标类上有@PostConstruct注解的方法

//获取目标类上有@PostConstruct注解的方法并调用publicObjectpostProcessBeforeInitialization(Objectbean,StringbeanName)throwsBeansException{LifecycleMetadatametadata=findLifecycleMetadata(bean.getClass());try{metadata.invokeInitMethods(bean,beanName);}catch(InvocationTargetExceptionex){thrownewBeanCreationException(beanName,"Invocationofinitmethodfailed",ex.getTargetException());}catch(Throwableex){thrownewBeanCreationException(beanName,"Failedtoinvokeinitmethod",ex);}returnbean;}

然后接着看initializeBean方法中②这一行代码,首先判断目标类有没有实现InitializingBean,如果实现了就调用目标类的afterPropertiesSet方法,然后如果有配置init-method就调用其方法

protectedvoidinvokeInitMethods(StringbeanName,finalObjectbean,RootBeanDefinitionmbd)throwsThrowable{//1、调用afterPropertiesSet方法booleanisInitializingBean=(beaninstanceofInitializingBean);if(isInitializingBean&&(mbd==null||!mbd.isExternallyManagedInitMethod("afterPropertiesSet"))){if(logger.isDebugEnabled()){logger.debug("InvokingafterPropertiesSet()onbeanwithname'"+beanName+"'");}if(System.getSecurityManager()!=null){try{AccessController.doPrivileged(newPrivilegedExceptionAction<Object>(){@OverridepublicObjectrun()throwsException{((InitializingBean)bean).afterPropertiesSet();returnnull;}},getAccessControlContext());}catch(PrivilegedActionExceptionpae){throwpae.getException();}}else{((InitializingBean)bean).afterPropertiesSet();}}//2、调用init-method方法if(mbd!=null){StringinitMethodName=mbd.getInitMethodName();if(initMethodName!=null&&!(isInitializingBean&&"afterPropertiesSet".equals(initMethodName))&&!mbd.isExternallyManagedInitMethod(initMethodName)){invokeCustomInitMethod(beanName,bean,mbd);}}}

至此Spring的初始化方法调用顺序的解析就已经完了。

spring加载顺序典例

借用log4j2,向数据库中新增一条记录,对于特殊的字段需要借助线程的环境变量。其中某个字段需要在数据库中查询到具体信息后插入,在借助Spring MVC的Dao层时遇到了加载顺序问题。

解决方案

log4j2插入数据库的方案参考文章:

<Columnname="user_info"pattern="%X{user_info}"isUnicode="false"/>

需要执行日志插入操作(比如绑定到一个级别为insert、logger.insert())的线程中有环境变量user_info。

解决环境变量的方法:

拦截器:

@ComponentpublicclassLogInterceptorimplementsHandlerInterceptor{/***需要记录在log中的参数*/publicstaticfinalStringUSER_INFO="user_info";@OverridepublicbooleanpreHandle(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objectarg)throwsException{StringuserName=LoginContext.getCurrentUsername();ThreadContext.put(USER_INFO,getUserInfo());}@OverridepublicvoidafterCompletion(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objectarg,Exceptionexception)throwsException{ThreadContext.remove(USER_INFO);}

需要拦截的URL配置:

@ConfigurationpublicclassLogConfigurerimplementsWebMvcConfigurer{String[]logUrl=newString[]{"/**",};String[]excludeUrl=newString[]{"/**/*.js","/**/*.css","/**/*.jpg","/**/*.png","/**/*.svg","/**/*.woff","/**/*.eot","/**/*.ttf","/**/*.less","/favicon.ico","/license/lackofresource","/error"};/***注册一个拦截器**@returnHpcLogInterceptor*/@BeanpublicLogInterceptorsetLogBean(){returnnewLogInterceptor();}@OverridepublicvoidaddInterceptors(InterceptorRegistryreg){//拦截的对象会进入这个类中进行判断InterceptorRegistrationregistration=reg.addInterceptor(setLogBean());//添加要拦截的路径与不用拦截的路径registration.addPathPatterns(logUrl).excludePathPatterns(excludeUrl);}}

如下待优化:

问题就出在如何获取信息这个步骤,原本的方案是:

通过Dao userDao从数据库查询信息,然后填充进去。

出现的问题是:userDao无法通过@Autowired方式注入。

原因:

调用处SpringBoot未完成初始化,导致dao层在调用时每次都是null。

因此最后采用的方式如下:

@ComponentpublicclassLogInterceptorimplementsHandlerInterceptor{/***需要记录在log中的参数*/publicstaticfinalStringUSER_INFO="user_info";@Resource(name="jdbcTemplate")privateJdbcTemplatejdbcTemplate;@OverridepublicbooleanpreHandle(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objectarg)throwsException{StringuserName=LoginContext.getCurrentUsername();ThreadContext.put(USER_INFO,getUserInfo());}@OverridepublicvoidafterCompletion(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,Objectarg,Exceptionexception)throwsException{ThreadContext.remove(USER_INFO);}publicStringgetUserInfo(StringuserName){StringsqlTemplate="selectuser_infofromTest.test_userwhereuser_name=?";List<String>userInfo=newArrayList<>();userInfo=jdbcTemplate.query(sqlTemplate,preparedStatement->{preparedStatement.setString(1,userName);},newSecurityRoleDtoMapper());if(userInfo.size()==0){returnConstants.HPC_NORMAL_USER;}returnuserInfo.get(0);}

感谢各位的阅读,以上就是“spring初始化方法的执行顺序及其原理是什么”的内容了,经过本文的学习后,相信大家对spring初始化方法的执行顺序及其原理是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!