欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring初始化方法的执行顺序及其原理分析

程序员文章站 2022-06-10 08:17:51
目录spring中初始化方法的执行顺序首先通过一个例子来看其顺序配置我们进入这个类看我们看到了annotation-config了我们重点看下这行代码我们直接看initializebean这个方法sp...

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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。