首頁 > 軟體

Java中Spring技巧之擴充套件點的應用

2022-04-21 13:01:17

前言:

最近在看公司專案和中介軟體的時候,看到一些Spring擴充套件點的使用,寫篇文章學習下,對大家之後看原始碼都有幫助

首先先介紹下Bean的生命週期:

我們知道Bean的生命週期分為幾個主幹流程

  • Bean(單例非懶載入)的範例化階段
  • Bean的屬性注入階段
  • Bean的初始化階段
  • Bean的銷燬階段

下面是整個Spring容器的啟動流程,可以看到除了上述幾個主幹流程外,Spring還提供了很多擴充套件點

下面詳細介紹下Spring的常見的擴充套件點

Spring常見擴充套件點

「BeanFactoryPostProcessor#postProcessBeanFactory」

有時候整個專案工程中bean的數量有上百個,而大部分單測依賴都是整個工程的xml,導致單測執行時需要很長時間(大部分時間耗費在xml中數百個單例非懶載入的bean的範例化及初始化過程)

解決方法:利用Spring提供的擴充套件點將xml中的bean設定為懶載入模式,省去了Bean的範例化與初始化時間

public class LazyBeanFactoryProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        DefaultListableBeanFactory fac = (DefaultListableBeanFactory) beanFactory;
        Map<String, AbstractBeanDefinition> map = (Map<String, AbstractBeanDefinition>) ReflectionTestUtils.getField(fac, "beanDefinitionMap");
        for (Map.Entry<String, AbstractBeanDefinition> entry : map.entrySet()) {
            //設定為懶載入
            entry.getValue().setLazyInit(true);
        }
    }
}

「InstantiationAwareBeanPostProcessor#postProcessPropertyValues」

非常規的設定項比如

<context:component-scan base-package="com.zhou" />

Spring提供了與之對應的特殊解析器

正是通過這些特殊的解析器才使得對應的設定項能夠生效

而針對這個特殊設定的解析器為 ComponentScanBeanDefinitionParser

在這個解析器的解析方法中,註冊了很多特殊的Bean

public BeanDefinition parse(Element element, ParserContext parserContext) {
  //...
  registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    //...
  return null;
}
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
   BeanDefinitionRegistry registry, Object source) {
  Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);
  //...
    //@Autowire
  if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
   RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
   def.setSource(source);
   beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
  }
  // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
   //@Resource
  if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      //特殊的Bean
   RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
   def.setSource(source);
   beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
  }
  //...
  return beanDefs;
 }

以@Resource為例,看看這個特殊的bean做了什麼

public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
  implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
      public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, 
      Object bean, String beanName) throws BeansException {
          InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass());
          try {
            //屬性注入
            metadata.inject(bean, beanName, pvs);
          }
          catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
          }
          return pvs;
    }
}

我們看到在postProcessPropertyValues方法中,進行了屬性注入

「invokeAware」

實現BeanFactoryAware介面的類,會由容器執行setBeanFactory方法將當前的容器BeanFactory注入到類中

@Bean
class BeanFactoryHolder implements BeanFactoryAware{
    private static BeanFactory beanFactory;
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

「BeanPostProcessor#postProcessBeforeInitialization」

實現ApplicationContextAware介面的類,會由容器執行setApplicationContext方法將當前的容器applicationContext注入到類中

@Bean
class ApplicationContextAwareProcessor implements BeanPostProcessor {
    private final ConfigurableApplicationContext applicationContext;
    public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
      this.applicationContext = applicationContext;
    }
    @Override
    public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
      //...
      invokeAwareInterfaces(bean);
      return bean;
    }
    private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof ApplicationContextAware) {
          ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
        }
    }
}

我們看到是在BeanPostProcessorpostProcessBeforeInitialization中進行了setApplicationContext方法的呼叫

class ApplicationContextHolder implements ApplicationContextAware{
    private static ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

「afterPropertySet()和init-method」

目前很多Java中介軟體都是基本Spring Framework搭建的,而這些中介軟體經常把入口放到afterPropertySet或者自定義的init中

「BeanPostProcessor#postProcessAfterInitialization」

熟悉aop的同學應該知道,aop底層是通過動態代理實現的

當設定了<aop:aspectj-autoproxy/>時候,預設開啟aop功能,相應地呼叫方需要被aop織入的物件也需要替換為動態代理物件

不知道大家有沒有思考過動態代理是如何**「在呼叫方無感知情況下替換原始物件」**的?

根據上文的講解,我們知道:

<aop:aspectj-autoproxy/>

Spring也提供了特殊的解析器,和其他的解析器類似,在核心的parse方法中註冊了特殊的bean

這裡是一個BeanPostProcessor型別的bean

class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
 @Override
 public BeanDefinition parse(Element element, ParserContext parserContext) {
    //註冊特殊的bean
  AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
  extendBeanDefinition(element, parserContext);
  return null;
    }
}

將於當前bean對應的動態代理物件返回即可,該過程對呼叫方全部透明

public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
          Object cacheKey = getCacheKey(bean.getClass(), beanName);
          if (!this.earlyProxyReferences.containsKey(cacheKey)) {
            //如果該類需要被代理,返回動態代理物件;反之,返回原物件
            return wrapIfNecessary(bean, beanName, cacheKey);
          }
        }
        return bean;
 }
}

正是利用Spring的這個擴充套件點實現了動態代理物件的替換

「destroy()和destroy-method」

bean生命週期的最後一個擴充套件點,該方法用於執行一些bean銷燬前的準備工作,比如將當前bean持有的一些資源釋放掉

總結

到此這篇關於Java中Spring技巧之擴充套件點的應用的文章就介紹到這了,更多相關Spring擴充套件點應用內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com