`
y806839048
  • 浏览: 1126231 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

spring生命周期及衍生出的优雅关机实现原理

阅读更多

spring生命周期及衍生出的优雅关机实现原理

总结:

 

   spring的生命周期过程:

 

      形成bean,set属性、注入,init,destory

 

 

 

     原理:用处

          @PostConstruct  spring初始化完成之后调用每个实例中的这个注解的方法   对应配置中的init方法 或者接口中BeanFactoryAware的init

 

  @PreDestroy     spring容器关闭之后掉哟给每个实例中的这个注解方法     对应配置中的destroy方法 或者接口中BeanFactoryAware的destory(即可实现hook优雅关机)

 

每个bean实例化过程都会把有@postConstruct,@PreDestory注解的方法打包成method对象存在内存list中,在上述时机遍历执行,用反射的机制调用执行

 

 

 

 

 

 

 

hook的主要原理就是用了join---主线程等在加入的子线程执行完才继续

 

   加入hook的方法线程依次进入join

 

 

在springboot启动的时候就注册了hook  ---AbstractApplicationContext

 

 

 

Terminator 中Signal.handle() 处理中断信号的方法--会通过sh回调Shutdown.exit(),从而出发sequence(),sequence()中调用runhooks方法,

就会调用ApplicationShutdownHooks 中的runHooks方法(遍历所有有@predestory的线程方法)----加入hook的方法线程依次进入join,执行每个有@destory的方法之后执行主线程

 

 

详解:

 

一、引言

在开发中我们如果要在关闭spring容器后释放一些资源,通常的做法有如下几种:
1.在方法上加上@PreDestroy注解
2.实现DisposableBean接口,实现其destroy方法

比较常用的是第一种实现,因为其足够简便。下面就来分析一下它的实现原理,看它是在哪一个环节被触发的。

二、开始分析

我们先移步到CommonAnnotationBeanPostProcessor这个类中,看如下一段代码:

public CommonAnnotationBeanPostProcessor() {
        setOrder(Ordered.LOWEST_PRECEDENCE - 3);
        setInitAnnotationType(PostConstruct.class);
        setDestroyAnnotationType(PreDestroy.class);
        ignoreResourceType("javax.xml.ws.WebServiceContext");
}

可见在CommonAnnotationBeanPostProcessor的无参构造函数中设置了一个默认的DestroyAnnotationType,即PreDestroy.在它的上方我们也看到了经常使用的PostConstruct,其实原理是一致的,只是spring帮我们控制了调用的顺序而已。
而CommonAnnotationBeanPostProcessor是实现了BeanFactoryAwareBeanFactoryAware的,也就是spring在启动的时候会找到这些扩展接口的子类型进行实例化。从而实现一些个性化的功能,例如:注解、配置注入、初始化、关闭操作等等。由于本文的重点在PreDestroy,所以不会过多的讲spring的加载过程。
接下来深入看看在哪里使用到了PreDestroy:

{
            if (destroyAnnotationType != null) {
                        if (method.getAnnotation(destroyAnnotationType) != null) {
                            currDestroyMethods.add(new LifecycleElement(method));
                            if (debug) {
                                logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);
                            }
                        }
                    }
                }
            });

            initMethods.addAll(0, currInitMethods);
            destroyMethods.addAll(currDestroyMethods);
            targetClass = targetClass.getSuperclass();
        }
        while (targetClass != null && targetClass != Object.class);

        return new LifecycleMetadata(clazz, initMethods, destroyMethods);
    }

看这一段代码,首先判断destroyAnnotationType它是否为空,显然这里不为空,这里指定了PreDestroy,然后再判断这个注解上是否有@PreDestroy注解,如果有,就将该Method包装成一个LifecycleElement添加到一个List中,然后将其添加到了另外一个集合destroyMethods中。接下来看一下哪里在使用这个destroyMethods集合。
移步到InitDestroyAnnotationBeanPostProcessor中,看到如下代码:

public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
        try {
            metadata.invokeDestroyMethods(bean, beanName);
        }
        catch (InvocationTargetException ex) {
            String msg = "Invocation of destroy method failed on bean with name '" + beanName + "'";
            if (logger.isDebugEnabled()) {
                logger.warn(msg, ex.getTargetException());
            }
            else {
                logger.warn(msg + ": " + ex.getTargetException());
            }
        }
        catch (Throwable ex) {
            logger.error("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
        }
    }

这个findLifecycleMetadata方法通过调用buildLifecycleMetadata方法最终调用到了最上面的那段代码,该metadata也就持有了所有加了PreDestroy注解的方法列表。接下来就是利用反射invoke目标类即可实现。

public void invokeDestroyMethods(Object target, String beanName) throws Throwable {
            Collection<LifecycleElement> destroyMethodsToUse =
                    (this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods);
            if (!destroyMethodsToUse.isEmpty()) {
                boolean debug = logger.isDebugEnabled();
                for (LifecycleElement element : destroyMethodsToUse) {
                    if (debug) {
                        logger.debug("Invoking destroy method on bean '" + beanName + "': " + element.getMethod());
                    }
                    element.invoke(target);
                }
            }
        }
三、再深入一点

如果想要优雅的退出,@PreDestroy能否满足要求呢?因为我们常用的做法就是注册一个钩子程序,当我们kill进程时(非 kill -9).jvm会收到操作系统一个终端,来做一些资源收尾的操作。

    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                logger.info("shutdown hook run.");
                try {
                   
                } catch (Exception e) {
                }
            }
        }));

如果spring想要优雅退出,必要要借助于hook,不然是没法影响中断的。接下来看一眼spring是怎么实现的。其实在AbstractApplicationContext中有这样一个方法:

 public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            this.shutdownHook = new Thread() {
                public void run() {
                    synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
                        AbstractApplicationContext.this.doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }

    }

springboot会通过启动main函数时调用refreshContext来注册钩子程序:

private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }

所以要想实现优雅关闭资源,使用@PreDestroy注解即可。

四、钩子程序的实现原理
class ApplicationShutdownHooks {
    /* The set of registered hooks */
    private static IdentityHashMap<Thread, Thread> hooks;
    static {
        try {
            Shutdown.add(1 /* shutdown hook invocation order */,
                false /* not registered if shutdown in progress */,
                new Runnable() {
                    public void run() {
                        //被sequence方法调用
                        runHooks();
                    }
                }
            );
            hooks = new IdentityHashMap<>();
        } catch (IllegalStateException e) {
            // application shutdown hooks cannot be added if
            // shutdown is in progress.
            hooks = null;
        }
    }
}

看一下runHooks()方法:

static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;
        }

        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            while (true) {
                try {
                    hook.join();
                    break;
                } catch (InterruptedException ignored) {
                }
            }
        }
    }
}

其实就是把hooks里的线程全部拿到然后启动,并且等待执行结束。添加hook即把线程存放在IdentityHashMap中。当调用Shutdown.add()的时候其实是将该线程存放在Shutdown的成员变量数组中。

static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
        synchronized (lock) {
            if (hooks[slot] != null)
                throw new InternalError("Shutdown hook at slot " + slot + " already registered");

            if (!registerShutdownInProgress) {
                if (state > RUNNING)
                    throw new IllegalStateException("Shutdown in progress");
            } else {
                if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
                    throw new IllegalStateException("Shutdown in progress");
            }

            hooks[slot] = hook;
        }
    }

接下来看一下Shutdown的sequence方法:

 private static void sequence() {
        synchronized (lock) {
            /* Guard against the possibility of a daemon thread invoking exit
             * after DestroyJavaVM initiates the shutdown sequence
             */
            if (state != HOOKS) return;
        }
        runHooks();
        boolean rfoe;
        synchronized (lock) {
            state = FINALIZERS;
            rfoe = runFinalizersOnExit;
        }
        if (rfoe) runAllFinalizers();
    }

它最终会调用runHooks方法,然后启动在上面静态块中添加的Runnable线程,最终启动所有已注册的钩子程序。

 private static void runHooks() {
        for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
            try {
                Runnable hook;
                synchronized (lock) {
                    currentRunningHook = i;
                    hook = hooks[i];
                }
                //该run方法最终执行静态块中的runHooks()方法。
                if (hook != null) hook.run();
            } catch(Throwable t) {
                if (t instanceof ThreadDeath) {
                    ThreadDeath td = (ThreadDeath)t;
                    throw td;
                }
            }
        }
    }

接下来找到Terminator中:

class Terminator {

    private static SignalHandler handler = null;

    /* Invocations of setup and teardown are already synchronized
     * on the shutdown lock, so no further synchronization is needed here
     */

    static void setup() {
        if (handler != null) return;
        SignalHandler sh = new SignalHandler() {
            public void handle(Signal sig) {
                Shutdown.exit(sig.getNumber() + 0200);
            }
        };
        handler = sh;

        // When -Xrs is specified the user is responsible for
        // ensuring that shutdown hooks are run by calling
        // System.exit()

       //这就是响应中断的方法
        try {
            Signal.handle(new Signal("INT"), sh);
        } catch (IllegalArgumentException e) {
        }
        try {
            //TERM对应15,即kill -9
            Signal.handle(new Signal("TERM"), sh);
        } catch (IllegalArgumentException e) {
        }
    }

其中Signal.handle()就是处理中断信号的方法,最终会通过sh回调Shutdown.exit()方法。最终触发sequence()方法被调用,然后调用所有注册的钩子程序。

五、最后

初步介绍了一下PreDestroy的原理和钩子程序的一些细节,由于标题只是讲PreDestroy,所以其中省略了不少spring的实现细节。谢谢大家~

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    详解Spring中Bean的生命周期和作用域及实现方式

    Spring中Bean的生命周期和作用域及实现方式 Spring是一个非常流行的Java应用程序框架,它提供了一个灵活的机制来管理Bean的生命周期和作用域。Bean的生命周期和作用域是Spring框架中两个非常重要的概念,它们决定了...

    spring bean的生命周期

    Spring Bean的生命周期是Spring框架中的核心概念,它涵盖了Bean从创建到销毁的全过程。了解这一过程对于优化应用程序的性能和管理资源至关重要。在Spring中,Bean的生命周期主要分为以下几个阶段: 1. **初始化阶段...

    Spring bean生命周期demo

    在Spring框架中,Bean的生命周期是指从创建到销毁的整个过程。这个过程包含了初始化、正常使用以及最终的销毁几个阶段。了解并掌握Spring Bean的生命周期对于优化应用性能和资源管理至关重要。接下来,我们将深入...

    springIoc实现原理

    **Spring Ioc 实现原理详解** Spring Ioc(Inversion of Control,控制反转)是Spring框架的核心特性之一,它改变了传统应用程序中对象的创建和管理方式。在传统的软件设计中,对象的创建和依赖关系的维护通常由...

    Spring Bean生命周期.pdf

    整个Spring Bean的生命周期流程是:如果有Bean实现了InstantiationAwareBeanPostProcessor接口,则会在Bean实例化前后被调用。Spring容器首先对Bean进行实例化,默认情况下Bean是单例的。实例化完成后,如果有Bean...

    spring bean的生命周期测试代码

    在Spring框架中,Bean的生命周期管理是其核心特性之一,它允许开发者控制Bean从创建到销毁的整个过程。本资源提供了在Spring 4.2环境下关于Bean生命周期的测试代码,帮助我们深入理解这一关键概念。 首先,让我们...

    Spring生命周期.png

    spring的创建详解图片,仅供参考

    对Spring中注解怎么实现的一些基本原理

    本文将深入探讨Spring注解的基本原理,包括它们如何被解析、处理以及如何影响应用程序的生命周期。 首先,我们需要了解注解在Java语言中的本质。注解是一种元数据,允许程序员在源代码中嵌入信息,这些信息可以被...

    Spring生命周期.vsdx

    Spring生命周期.vsdx

    spring中service生命周期(xml/annotation)

    本文将探讨Spring中Service的生命周期,以及如何通过XML配置和注解来管理这些组件。 首先,Spring Service的生命周期通常包括实例化、初始化、正常运行、销毁四个阶段。在XML配置中,我们可以通过`&lt;bean&gt;`标签来...

    Spring中Bean的生命周期 applicationcontext的应用(实现国际化,事件的传递)

    在Spring框架中,Bean的生命周期管理和ApplicationContext的应用是两个核心概念,它们对于理解Spring如何管理和协调应用中的对象至关重要。本文将深入探讨这两个主题,并结合国际化(i18n)和事件传递来阐述它们在...

    spring aop实现原理

    NULL 博文链接:https://zhang-yingjie-qq-com.iteye.com/blog/319927

    spring中注解的实现原理

    本文将深入探讨Spring中注解的实现原理,帮助你更好地理解和运用这些核心概念。 首先,让我们从注解的基础知识开始。注解在Java中是一种元数据,它提供了在代码中嵌入信息的方式,而这些信息可以被编译器或运行时...

    Spring框架系列(8) - Spring IOC实现原理详解之Bean实例化(生命周期,循环依赖等).doc

    Spring 框架系列(8)- Spring IOC 实现原理详解之 Bean 实例化(生命周期、循环依赖等) 本文主要研究 Spring 框架中 Bean 的实例化过程,包括 Bean 的生命周期和循环依赖问题的解决方案。在 Spring 框架中,Bean ...

    SpringBean的生命周期.mdj

    SpringBean的生命周期.mdj

    第二十章 Spring 应用上下文生命周期(ApplicationContext Lifecycle)1

    总结全文,强调Spring应用上下文生命周期的重要性以及理解其工作原理对于开发和维护Spring应用的意义。 了解Spring应用上下文的生命周期有助于我们更好地管理和控制Bean的行为,以及优化应用的性能和稳定性。在...

    Spring技术内幕:深入解析Spring架构与设计原理.pdf

    1. Dependency Injection:依赖注入,通过容器管理Bean的生命周期和依赖关系。 2. Inversion of Control:控制反转,通过容器管理应用程序的控制流程。 3. Aspect-Oriented Programming:面向方面编程,通过动态代理...

    谈谈我对Spring Bean 生命周期的理解

    五、 Spring Bean 生命周期的实现 在 Spring 中,Bean 的生命周期是通过容器来管理的。容器会在 Bean 的整个生命周期中执行不同的方法,包括实例化、设置属性、依赖注入、初始化、使用和销毁等阶段。 六、 小结 ...

    Spring技术内幕:深入解析 Spring架构与设计原理.pdf

    你不仅能从本书中参透Spring框架的优秀架构和设计思想,还能从Spring优雅的实现源码中一窥Java语言的精髓。本书在开篇之前对Spring的设计理念和整体架构进行了全面的介绍,能让读者从宏观上厘清Spring各个功能模块...

    spring依赖注入的实现原理

    Spring依赖注入(Dependency Injection,简称DI)是Java应用开发中常用的设计模式,它极大地提高了代码的可测试性和可...同时,Spring的AOP代理和生命周期管理进一步丰富了框架的功能,使得在实际开发中更加得心应手。

Global site tag (gtag.js) - Google Analytics