spring初始化方法的执行顺序及其原理分析
spring中初始化方法的执行顺序
首先通过一个例子来看其顺序
/** * 调用顺序 init2(postconstruct注解) --> afterpropertiesset(initializingbean接口) --> init3(init-method配置) */ public class test implements initializingbean { public void init3(){ system.out.println("init3"); } @postconstruct public void init2(){ system.out.println("init2"); } @override public void afterpropertiesset() throws exception { system.out.println("afterpropertiesset"); } }
配置
<context:annotation-config/> <bean class="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
我们进入这个类看
public class contextnamespacehandler extends namespacehandlersupport { @override public void init() { registerbeandefinitionparser("property-placeholder", new propertyplaceholderbeandefinitionparser()); registerbeandefinitionparser("property-override", new propertyoverridebeandefinitionparser()); registerbeandefinitionparser("annotation-config", new annotationconfigbeandefinitionparser()); registerbeandefinitionparser("component-scan", new componentscanbeandefinitionparser()); registerbeandefinitionparser("load-time-weaver", new loadtimeweaverbeandefinitionparser()); registerbeandefinitionparser("spring-configured", new springconfiguredbeandefinitionparser()); registerbeandefinitionparser("mbean-export", new mbeanexportbeandefinitionparser()); registerbeandefinitionparser("mbean-server", new mbeanserverbeandefinitionparser()); } }
我们看到了annotation-config了
我们只关心这个标签,那我们就进入annotationconfigbeandefinitionparser类中,看它的parse方法
public beandefinition parse(element element, parsercontext parsercontext) { object source = parsercontext.extractsource(element); // obtain bean definitions for all relevant beanpostprocessors. set<beandefinitionholder> processordefinitions = annotationconfigutils.registerannotationconfigprocessors(parsercontext.getregistry(), source); // register component for the surrounding <context:annotation-config> element. compositecomponentdefinition compdefinition = new compositecomponentdefinition(element.gettagname(), source); parsercontext.pushcontainingcomponent(compdefinition); // nest the concrete beans in the surrounding component. for (beandefinitionholder processordefinition : processordefinitions) { parsercontext.registercomponent(new beancomponentdefinition(processordefinition)); } // finally register the composite component. parsercontext.popandregistercontainingcomponent(); return null; }
我们重点看下这行代码
set<beandefinitionholder> processordefinitions = annotationconfigutils.registerannotationconfigprocessors(parsercontext.getregistry(), source);
我们追踪进去(其中省略了一些我们不关心的代码)
public static set<beandefinitionholder> registerannotationconfigprocessors( beandefinitionregistry registry, object source) { ... // check for jsr-250 support, and if present add the commonannotationbeanpostprocessor. if (jsr250present && !registry.containsbeandefinition(common_annotation_processor_bean_name)) { rootbeandefinition def = new rootbeandefinition(commonannotationbeanpostprocessor.class); def.setsource(source); beandefs.add(registerpostprocessor(registry, def, common_annotation_processor_bean_name)); } ... }
在这个方法其中注册了一个commonannotationbeanpostprocessor类,这个类是我们@postconstruct这个注解发挥作用的基础。
在bean实例化的过程中,会调用abstractautowirecapablebeanfactory类的docreatebean方法,在这个方法中会有一个调用initializebean方法的地方,
我们直接看initializebean这个方法
protected object initializebean(final string beanname, final object bean, rootbeandefinition mbd) { if (system.getsecuritymanager() != null) { accesscontroller.doprivileged(new privilegedaction<object>() { @override public object run() { invokeawaremethods(beanname, bean); return null; } }, getaccesscontrolcontext()); } else { invokeawaremethods(beanname, bean); } object wrappedbean = bean; if (mbd == null || !mbd.issynthetic()) { // 调用@postconstruct方法注解的地方 wrappedbean = applybeanpostprocessorsbeforeinitialization(wrappedbean, beanname);//① } try { // 调用afterpropertiesset和init-method地方 invokeinitmethods(beanname, wrappedbean, mbd);// ② } catch (throwable ex) { throw new beancreationexception( (mbd != null ? mbd.getresourcedescription() : null), beanname, "invocation of init method failed", ex); } if (mbd == null || !mbd.issynthetic()) { wrappedbean = applybeanpostprocessorsafterinitialization(wrappedbean, beanname); } return wrappedbean; }
先看①这行,进入applybeanpostprocessorsbeforeinitialization方法
public object applybeanpostprocessorsbeforeinitialization(object existingbean, string beanname) throws beansexception { object result = existingbean; for (beanpostprocessor beanprocessor : getbeanpostprocessors()) { result = beanprocessor.postprocessbeforeinitialization(result, beanname); if (result == null) { return result; } } return result; }
我们还记得前面注册的一个类commonannotationbeanpostprocessor,其中这个类间接的实现了beanpostprocessor接口,所以此处会调用commonannotationbeanpostprocessor类的postprocessbeforeinitialization方法,它本身并没有实现这个方法,但他的父类initdestroyannotationbeanpostprocessor实现了postprocessbeforeinitialization的方法,其中这个方法就实现调用目标类上有@postconstruct注解的方法
// 获取目标类上有@postconstruct注解的方法并调用 public object postprocessbeforeinitialization(object bean, string beanname) throws beansexception { lifecyclemetadata metadata = findlifecyclemetadata(bean.getclass()); try { metadata.invokeinitmethods(bean, beanname); } catch (invocationtargetexception ex) { throw new beancreationexception(beanname, "invocation of init method failed", ex.gettargetexception()); } catch (throwable ex) { throw new beancreationexception(beanname, "failed to invoke init method", ex); } return bean; }
然后接着看initializebean方法中②这一行代码,首先判断目标类有没有实现initializingbean,如果实现了就调用目标类的afterpropertiesset方法,然后如果有配置init-method就调用其方法
protected void invokeinitmethods(string beanname, final object bean, rootbeandefinition mbd) throws throwable { // 1、调用afterpropertiesset方法 boolean isinitializingbean = (bean instanceof initializingbean); if (isinitializingbean && (mbd == null || !mbd.isexternallymanagedinitmethod("afterpropertiesset"))) { if (logger.isdebugenabled()) { logger.debug("invoking afterpropertiesset() on bean with name '" + beanname + "'"); } if (system.getsecuritymanager() != null) { try { accesscontroller.doprivileged(new privilegedexceptionaction<object>() { @override public object run() throws exception { ((initializingbean) bean).afterpropertiesset(); return null; } }, getaccesscontrolcontext()); } catch (privilegedactionexception pae) { throw pae.getexception(); } } else { ((initializingbean) bean).afterpropertiesset(); } } // 2、调用init-method方法 if (mbd != null) { string initmethodname = mbd.getinitmethodname(); if (initmethodname != null && !(isinitializingbean && "afterpropertiesset".equals(initmethodname)) && !mbd.isexternallymanagedinitmethod(initmethodname)) { invokecustominitmethod(beanname, bean, mbd); } } }
至此spring的初始化方法调用顺序的解析就已经完了。
spring加载顺序典例
借用log4j2,向数据库中新增一条记录,对于特殊的字段需要借助线程的环境变量。其中某个字段需要在数据库中查询到具体信息后插入,在借助spring mvc的dao层时遇到了加载顺序问题。
解决方案
log4j2插入数据库的方案参考文章:
<column name="user_info" pattern="%x{user_info}" isunicode="false" />
需要执行日志插入操作(比如绑定到一个级别为insert、logger.insert())的线程中有环境变量user_info。
解决环境变量的方法:
拦截器:
@component public class loginterceptor implements handlerinterceptor { /** * 需要记录在log中的参数 */ public static final string user_info= "user_info"; @override public boolean prehandle(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, object arg) throws exception { string username = logincontext.getcurrentusername(); threadcontext.put(user_info, getuserinfo()); } @override public void aftercompletion(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, object arg, exception exception) throws exception { threadcontext.remove(user_info); }
需要拦截的url配置:
@configuration public class logconfigurer implements webmvcconfigurer { string[] logurl = new string[] { "/**", }; string[] excludeurl = new string[] { "/**/*.js", "/**/*.css", "/**/*.jpg", "/**/*.png", "/**/*.svg", "/**/*.woff", "/**/*.eot", "/**/*.ttf", "/**/*.less", "/favicon.ico", "/license/lackofresource", "/error" }; /** * 注册一个拦截器 * * @return hpcloginterceptor */ @bean public loginterceptor setlogbean() { return new loginterceptor(); } @override public void addinterceptors(interceptorregistry reg) { // 拦截的对象会进入这个类中进行判断 interceptorregistration registration = reg.addinterceptor(setlogbean()); // 添加要拦截的路径与不用拦截的路径 registration.addpathpatterns(logurl).excludepathpatterns(excludeurl); } }
如下待优化:
问题就出在如何获取信息这个步骤,原本的方案是:
通过dao userdao从数据库查询信息,然后填充进去。
出现的问题是:userdao无法通过@autowired方式注入。
原因:
调用处springboot未完成初始化,导致dao层在调用时每次都是null。
因此最后采用的方式如下:
@component public class loginterceptor implements handlerinterceptor { /** * 需要记录在log中的参数 */ public static final string user_info= "user_info"; @resource(name = "jdbctemplate") private jdbctemplate jdbctemplate; @override public boolean prehandle(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, object arg) throws exception { string username = logincontext.getcurrentusername(); threadcontext.put(user_info, getuserinfo()); } @override public void aftercompletion(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, object arg, exception exception) throws exception { threadcontext.remove(user_info); } public string getuserinfo(string username) { string sqltemplate = "select user_info from test.test_user where user_name = ?"; list<string> userinfo= new arraylist<>(); userinfo= jdbctemplate.query(sqltemplate, preparedstatement -> { preparedstatement.setstring(1, username); }, new securityroledtomapper()); if (userinfo.size() == 0) { return constants.hpc_normal_user; } return userinfo.get(0); }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
推荐阅读
-
Spring中如何获取request的方法汇总及其线程安全性分析
-
Spring中如何获取request的方法汇总及其线程安全性分析
-
关于静态语句块、非静态语句块,成员变量初始化、构造方法在父子类执行的顺序:
-
spring初始化bean时执行某些方法完成特定的初始化操作
-
关于静态语句块、非静态语句块,成员变量初始化、构造方法在父子类执行的顺序:
-
spring初始化方法的执行顺序及其原理分析
-
类加载机制,类初始化时各个成员执行的顺序,静态代码块,静态方法,静态成员变量
-
Spring中Bean初始化和销毁方法执行的优先级
-
spring控制bean生命周期(spring控制的bean初始化与销毁的执行方法)
-
spring初始化bean时执行某些方法完成特定的初始化操作