Spring Bean 초기 화 및 소각 다양한 실현 방식

이 글 은 주로 Spring Bean 을 초기 화하 고 소각 하 는 여러 가지 실현 방식 을 소개 하 였 으 며,예시 코드 를 통 해 매우 상세 하 게 소개 하 였 으 며,여러분 의 학습 이나 업무 에 대해 어느 정도 참고 학습 가 치 를 가지 고 있 으 므 로 필요 한 분 들 은 참고 하 시기 바 랍 니 다.
머리말
일상적인 개발 과정 은 응용 프로그램 이 시 작 된 후에 일부 자원 을 불 러 오 거나 응용 프로그램 이 닫 히 기 전에 자원 을 방출 해 야 할 때 가 있다.Spring 프레임 워 크 는 관련 기능 을 제공 합 니 다.Spring Bean 의 생명 주 기 를 중심 으로 Bean 생 성 과정 에서 자원 을 초기 화하 고 Bean 과정 에서 자원 을 방출 할 수 있 습 니 다.Spring 은 다양한 방식 으로 Bean 을 초기 화/소각 합 니 다.이 몇 가지 방식 을 동시에 사용 하면 Spring 은 이 몇 가지 순 서 를 어떻게 처리 합 니까?
2.자세 분석
우선 Spring 초기 화/Bean 소각 방식 을 살 펴 보 자.
  • init-method/destroy-method
  • InitializingBean/DisposableBean
  • @PostConstruct/@PreDestroy
  • ContextStartedEvent/ContextClosedEvent
  • PS:사실 스프링 라 이 프 사이클 인 터 페 이 스 를 계승 하 는 방법 도 있어 요.그러나 이런 방식 은 비교적 번 거 로 워 서 여 기 는 더 이상 분석 하지 않 는 다.
    2.1、init-method/destroy-method
    이 방식 은 설정 파일 에서 초기 화/소각 방법 을 지정 합 니 다.XML 설정 은 다음 과 같 습 니 다.
    
    <bean id="demoService" class="com.dubbo.example.provider.DemoServiceImpl" destroy-method="close" init-method="initMethod"/>
    또는 주석 으로 설정 할 수도 있 습 니 다:
    
    @Configurable
    public class AppConfig {
    
      @Bean(initMethod = "init", destroyMethod = "destroy")
      public HelloService hello() {
        return new HelloService();
      }
    }
    스프링 프레임 워 크 를 처음 접 했 을 때 사용 하 는 것 이 이런 방식 이 었 던 것 으로 기억 된다.
    2.2、InitializingBean/DisposableBean
    이 방식 은 Spring 인터페이스 InitializingBean/DisposableBean 을 계승 해 야 합 니 다.그 중에서 InitializingBean 은 동작 을 초기 화 하 는 데 사용 되 고 DisposableBean 은 제거 하기 전에 동작 을 정리 하 는 데 사 용 됩 니 다.사용 방식 은 다음 과 같 습 니 다.
    
    @Service
    public class HelloService implements InitializingBean, DisposableBean {
      
      @Override
      public void destroy() throws Exception {
        System.out.println("hello destroy...");
      }
    
      @Override
      public void afterPropertiesSet() throws Exception {
        System.out.println("hello init....");
      }
    }
    2.3、@PostConstruct/@PreDestroy
    이런 방식 은 위의 두 가지 방식 에 비해 사용 방식 이 가장 간단 하고 해당 하 는 방법 에 주 해 를 사용 하면 된다.사용 방식 은 다음 과 같 습 니 다.
    
    @Service
    public class HelloService {
      @PostConstruct
      public void init() {
        System.out.println("hello @PostConstruct");
      }
    
      @PreDestroy
      public void PreDestroy() {
        System.out.println("hello @PreDestroy");
      }
    }
    여기에 구 덩이 를 밟 았 습 니 다.JDK 9 이후 버 전 을 사용 하면@PostConstruct/@PreDestroy 는 Maven 을 사용 하여 javax.annotation-api 를 단독으로 도입 해 야 합 니 다.여부 자 설명 은 유효 하지 않 습 니 다.
    2.4、ContextStartedEvent/ContextClosedEvent
    이런 방식 은 Spring 이벤트 메커니즘 을 사용 하 는데 일상적인 업무 개발 이 비교적 드 물고 프레임 워 크 와 통합 되 는 데 자주 사용 된다.Spring 이 시작 되면 ContextStarted Event 이 벤트 를 보 내 고 닫 기 전에 ContextClosed Event 이 벤트 를 보 냅 니 다.우 리 는 Spring Application Listener 를 계승 해 야 상기 두 가지 사건 을 감청 할 수 있다.
    
    @Service
    public class HelloListener implements ApplicationListener {
    
      @Override
      public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ContextClosedEvent){
          System.out.println("hello ContextClosedEvent");
        }else if(event instanceof ContextStartedEvent){
          System.out.println("hello ContextStartedEvent");
        }
    
      }
    }
    @EventListener 주 해 를 사용 할 수도 있 습 니 다.사용 방식 은 다음 과 같 습 니 다.
    
    public class HelloListenerV2 {
      
      @EventListener(value = {ContextClosedEvent.class, ContextStartedEvent.class})
      public void receiveEvents(ApplicationEvent event) {
        if (event instanceof ContextClosedEvent) {
          System.out.println("hello ContextClosedEvent");
        } else if (event instanceof ContextStartedEvent) {
          System.out.println("hello ContextStartedEvent");
        }
      }
    }
    PS:Application Context\#start 를 호출 해야만 ContextStarted Event 를 보 낼 수 있 습 니 다.이렇게 귀 찮 게 하고 싶 지 않 으 면 ContextRefreshedEvent 사건 을 감청 해서 대체 할 수 있다.스프링 용기 초기 화가 완료 되면 ContextRefreshedEvent 를 발송 합 니 다.
    3.종합 적 으로 사용
    위의 몇 가지 방식 을 돌 이 켜 보면 위의 네 가지 방식 을 종합 적 으로 사용 하여 Spring 내부 의 처리 순 서 를 살 펴 보 자.결 과 를 보기 전에 독자 여러분 은 이 몇 가지 방식 의 집행 순 서 를 추측 할 수 있 습 니 다.
    
    public class HelloService implements InitializingBean, DisposableBean {
    
    
      @PostConstruct
      public void init() {
        System.out.println("hello @PostConstruct");
      }
    
      @PreDestroy
      public void PreDestroy() {
        System.out.println("hello @PreDestroy");
      }
    
      @Override
      public void destroy() throws Exception {
        System.out.println("bye DisposableBean...");
      }
    
      @Override
      public void afterPropertiesSet() throws Exception {
        System.out.println("hello InitializingBean....");
      }
    
      public void xmlinit(){
        System.out.println("hello xml-init...");
      }
    
      public void xmlDestory(){
        System.out.println("bye xmlDestory...");
      }
    
      @EventListener(value = {ContextClosedEvent.class, ContextStartedEvent.class})
      public void receiveEvents(ApplicationEvent event) {
        if (event instanceof ContextClosedEvent) {
          System.out.println("bye ContextClosedEvent");
        } else if (event instanceof ContextStartedEvent) {
          System.out.println("hello ContextStartedEvent");
        }
      }
    
    }
    xml 설정 방식 은 다음 과 같 습 니 다.
    
      <context:annotation-config />
      <context:component-scan base-package="com.dubbo.example.demo"/>
      
      <bean class="com.dubbo.example.demo.HelloService" init-method="xmlinit" destroy-method="xmlDestory"/>
    시작 방법 은 다음 과 같 습 니 다.
    
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
    context.start();
    context.close();
    프로그램 출력 결 과 는 다음 과 같 습 니 다.

    마지막 으로 그림 설명 으로 상기 결 과 를 요약 한다.

    소스 코드 분석
    독자 여러분 이 이 몇 가지 방식 의 집행 순 서 를 맞 혔 는 지 모 르 겠 지만,다음은 스프링 내부 처리 순 서 를 소스 코드 각도 에서 분석 하 겠 습 니 다.
    4.1 초기 화 과정
    ClassPathXmlApplication Context 를 사용 하여 Spring 용 기 를 시작 하면 refresh 방법 으로 용 기 를 초기 화 합 니 다.초기 화 과정 에서 Bean 이 생 성 됩 니 다.마지막 으로 모든 준비 가 완료 되면 ContextRefreshedEvent 를 보 냅 니 다.용기 초기 화가 완료 되면 context.start()를 호출 하면 ContextStarted Event 이 벤트 를 보 냅 니 다.
    refresh 방법 원본 코드 는 다음 과 같 습 니 다.
    
    public void refresh() throws BeansException, IllegalStateException {
      synchronized (this.startupShutdownMonitor) {
          //...       
    
          //              Bean
          finishBeanFactoryInitialization(beanFactory);
    
          //    ContextRefreshedEvent
          finishRefresh();
    
          //...       
      }
    }
    AbstractAutowireCapableBeanFactory\#initializeBean 까지 finishBeanFactory 초기 화 원본 코드 를 추적 합 니 다.원본 코드 는 다음 과 같 습 니 다.
    
    protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
      Object wrappedBean = bean;
      if (mbd == null || !mbd.isSynthetic()) {
        //    BeanPostProcessor#postProcessBeforeInitialization   
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
      }
    
      try {
        //     Bean
        invokeInitMethods(beanName, wrappedBean, mbd);
      }
      catch (Throwable ex) {
        throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
      }
    }
    BeanPostProcessor 는 차단기 역할 을 하 며,Bean 이 조건 에 맞 으 면 일부 처 리 를 수행 합 니 다.여기에@PostConstruct 주석 이 있 는 Bean 은 CommonAnnotationBeanPostProcessor 류 에 의 해 차단 되 고 내부 에@PostConstruct 레이 블 방법 이 실 행 됩 니 다.
    이어서 invokeInitMethods 를 실행 합 니 다.방법 은 다음 과 같 습 니 다.
    
    protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
        throws Throwable {
    
      boolean isInitializingBean = (bean instanceof InitializingBean);
      if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        //       
        //     Bean    InitializingBean,     afterPropertiesSet   
        ((InitializingBean) bean).afterPropertiesSet();
      }
    
      if (mbd != null) {
        String initMethodName = mbd.getInitMethodName();
        if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
          //    XML    init-method
          invokeCustomInitMethod(beanName, bean, mbd);
        }
      }
    }
    빈 이 InitialingBean 인 터 페 이 스 를 계승 하면 after PropertiesSet 방법 을 실행 합 니 다.또한 XML 에 init-method 를 지정 하면 트리거 됩 니 다.
    위의 소스 코드 는 빈 을 중심 으로 만 드 는 과정 입 니 다.모든 빈 이 만 든 후에 context\#start 를 호출 하면 ContextStarted Event 를 보 냅 니 다.여기 소스 코드 는 비교적 간단 합 니 다.다음 과 같 습 니 다.
    
    public void start() {
      getLifecycleProcessor().start();
      publishEvent(new ContextStartedEvent(this));
    }
    4.2 소각 과정
    ClassPathXmlApplication Context\#close 방법 을 호출 하면 용 기 를 닫 고 구체 적 인 논 리 는 doClose 방법 에서 실 행 됩 니 다.
    doClose 이 방법 은 먼저 ContextClosed Event 를 보 낸 다음 에 Bean 을 소각 하기 시작 합 니 다.
    영혼 고문:우리 가 위의 두 순 서 를 뒤 바 꾸 면 결 과 는 같 을 까?
    doClose 소스 코드 는 다음 과 같 습 니 다.
    
    protected void doClose() {
      if (this.active.get() && this.closed.compareAndSet(false, true)) {
        //       
    
        try {
          // Publish shutdown event.
          publishEvent(new ContextClosedEvent(this));
        }
        catch (Throwable ex) {
          logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
        }
    
    
        //    Bean
        destroyBeans();
    
        //       
      }
    }
    destroyBeans 는 최종 적 으로 DisposableBeanAdapter\#destroy,@PreDestroy,DisposableBean,destroy-method 세 가지 정의 방법 을 내부 에서 실행 합 니 다.
    우선 Destruction AwareBeanPostProcessor\#postprocessBeforeDestruction 을 실행 합 니 다.이 방법 은 위의 BeanPostProcessor 와 유사 합 니 다.
    @PreDestroy 주 해 는 CommonAnnotationBeanPostProcessor 에 의 해 차단 되 며,이 클래스 는 Destruction Aware BeanPostProcessor 를 계승 합 니 다.
    마지막 으로 빈 이 Disposable Bean 의 하위 클래스 라면 destroy 방법 을 실행 하고 xml 에서 destroy-method 방법 을 정의 하면 이 방법 도 실 행 됩 니 다.
    
    public void destroy() {
      if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
        for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
          processor.postProcessBeforeDestruction(this.bean, this.beanName);
        }
      }
    
      if (this.invokeDisposableBean) {
        //       
        //    Bean    DisposableBean,   destroy   
        ((DisposableBean) bean).destroy();
        
      }
    
      if (this.destroyMethod != null) {
        //    xml     destroy-method   
        invokeCustomDestroyMethod(this.destroyMethod);
      }
      else if (this.destroyMethodName != null) {
        Method methodToCall = determineDestroyMethod();
        if (methodToCall != null) {
          invokeCustomDestroyMethod(methodToCall);
        }
      }
    }
    총화
    init-method/destroy-method 는 XML 설정 파일 이나 단독 주석 설정 클래스 를 사용 해 야 하기 때문에 상대 적 으로 번거롭다.InitialingBean/DisposableBean 은 Spring 의 인 터 페 이 스 를 단독으로 계승 하여 관련 방법 을 실현 해 야 합 니 다.@PostConstruct/@PreDestroy 라 는 주해 방식 은 사용 방식 이 간단 하고 코드 가 뚜렷 하 므 로 이런 방식 을 사용 하 는 것 을 추천 합 니 다.
    또한 ContextStarted Event/ContextClosed Event 라 는 방식 은 일부 통합 프레임 워 크 에서 사용 하기에 비교적 적합 하 다.예 를 들 어 Dubbo 2.6.X 우아 한 정지 기 는 바로 메커니즘 을 바 꾸 는 것 이다.
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기