iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >spring初始化方法的执行顺序及其原理分析
  • 937
分享到

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

2024-04-02 19:04:59 937人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录spring中初始化方法的执行顺序首先通过一个例子来看其顺序配置我们进入这个类看我们看到了annotation-config了我们重点看下这行代码我们直接看initializeB

Spring中初始化方法的执行顺序

首先通过一个例子来看其顺序


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 {
    
    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[] {
        "*.js", "*.CSS", "*.jpg", "*.png", "*.svg", "*.woff", "*.eot", "*.ttf",
        "*.less", "/favicon.ico", "/license/lackofresource", "/error"
    };
    
    @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 {
    
    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);
    }

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

--结束END--

本文标题: spring初始化方法的执行顺序及其原理分析

本文链接: https://www.lsjlt.com/news/139060.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作