欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot @Scope與@RefreshScope注解使用詳解

 更新時間:2022年11月17日 16:13:44   作者:氵奄不死的魚  
spring的bean管理中,每個bean都有對應的scope。在BeanDefinition中就已經(jīng)指定scope,默認的RootBeanDefinition的scope是prototype類型,使用@ComponentScan掃描出的BeanDefinition會指定是singleton,最常使用的也是singleton

前言

在SpringIOC中,我們熟知的BeanScope有單例(singleton)、原型(prototype), Bean的Scope影響了Bean的管理方式,例如創(chuàng)建Scope=singleton的Bean時,IOC會保存實例在一個Map中,保證這個Bean在一個IOC上下文有且僅有一個實例。SpringCloud新增了一個refresh范圍的scope,同樣用了一種獨特的方式改變了Bean的管理方式,使得其可以通過外部化配置(.properties)的刷新,在應用不需要重啟的情況下熱加載新的外部化配置的值。

那么這個scope是如何做到熱加載的呢?RefreshScope主要做了以下動作:

單獨管理Bean生命周期創(chuàng)建Bean的時候如果是RefreshScope就緩存在一個專門管理的ScopeMap中,這樣就可以管理Scope是Refresh的Bean的生命周期了重新創(chuàng)建Bean外部化配置刷新之后,會觸發(fā)一個動作,這個動作將上面的ScopeMap中的Bean清空,這樣,這些Bean就會重新被IOC容器創(chuàng)建一次,使用最新的外部化配置的值注入類中,達到熱加載新值的效果下面我們深入源碼,來驗證我們上述的講法。

@Scope注解

Spring管理的Bean默認是單例的

@Scope (“prototype”) 通過注解可以實現(xiàn)多個實例的解決

Spring定義了多種作用域,可以基于這些作用域創(chuàng)建bean,包括:

單例( singleton):在整個應用中,只創(chuàng)建bean的一個實例。也就是單例

原型(prototype):每次注入或者通過Spring應用上下文獲取的時候:getBean,都會創(chuàng)建一個新的bean實例。多例,每次getBean的時候都會創(chuàng)建新的對象

request表示請求,即在一次http請求中,被注解的Bean都是同一個Bean,不同的請求是不同的Bean;

session表示會話,即在同一個會話中,被注解的Bean都是使用的同一個Bean,不同的會話使用不同的Bean。

創(chuàng)建一個Bean的時候,會去BeanFactory的doGetBean方法創(chuàng)建Bean,不同scope有不同的創(chuàng)建方式:

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  //....
  // Create bean instance.
  // 單例Bean的創(chuàng)建
  if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
      try {
        return createBean(beanName, mbd, args);
      }
      //...
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  }
  // 原型Bean的創(chuàng)建
  else if (mbd.isPrototype()) {
    // It's a prototype -> create a new instance.
    // ...
    try {
      prototypeInstance = createBean(beanName, mbd, args);
    }
    //...
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
  }
  else {
    // 由上面的RefreshScope注解可以知道,這里scopeName=refresh
    String scopeName = mbd.getScope();
    // 獲取Refresh的Scope對象
    final Scope scope = this.scopes.get(scopeName);
    if (scope == null) {
      throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
    }
    try {
      // 讓Scope對象去管理Bean
      Object scopedInstance = scope.get(beanName, () -> {
        beforePrototypeCreation(beanName);
        try {
          return createBean(beanName, mbd, args);
        }
        finally {
          afterPrototypeCreation(beanName);
        }
      });
      bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
    }
    //...
  }
}
//...
}
//...
}

這里可以看到幾件事情:

單例和原型scope的Bean是硬編碼單獨處理的

除了單例和原型Bean,其他Scope是由Scope對象處理的

具體創(chuàng)建Bean的過程都是由IOC做的,只不過Bean的獲取是通過Scope對象

通過scopeName獲取對應的scope實例

@RefreshScope刷新bean

這里scope.get獲取的Scope對象為RefreshScope,可以看到,創(chuàng)建Bean還是由IOC來做(createBean方法),但是獲取Bean,都由RefreshScope對象的get方法去獲取,其get方法在父類GenericScope中實現(xiàn):

public Object get(String name, ObjectFactory<?> objectFactory) {
  // 將Bean緩存下來
  BeanLifecycleWrapper value = this.cache.put(name,
                                              new BeanLifecycleWrapper(name, objectFactory));
  this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
  try {
    // 創(chuàng)建Bean,只會創(chuàng)建一次,后面直接返回創(chuàng)建好的Bean
    return value.getBean();
  }
  catch (RuntimeException e) {
    this.errors.put(name, e);
    throw e;
  }
}

首先這里將Bean包裝起來緩存下來

這里scope.get獲取的Scope對象為RefreshScope,可以看到,創(chuàng)建Bean還是由IOC來做(createBean方法),但是獲取Bean,都由RefreshScope對象的get方法去獲取,其get方法在父類GenericScope中實現(xiàn)。bean的生命周期也由GenericScope控制

public Object get(String name, ObjectFactory<?> objectFactory) {
  // 將Bean緩存下來
  BeanLifecycleWrapper value = this.cache.put(name,
                                              new BeanLifecycleWrapper(name, objectFactory));
  this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
  try {
    // 創(chuàng)建Bean,只會創(chuàng)建一次,后面直接返回創(chuàng)建好的Bean
    return value.getBean();
  }
  catch (RuntimeException e) {
    this.errors.put(name, e);
    throw e;
  }
}
private final ScopeCache cache;
// 這里進入上面的 BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) {
  return (BeanLifecycleWrapper) this.cache.put(name, value);
}

這里的ScopeCache對象其實就是一個HashMap:

public class StandardScopeCache implements ScopeCache {
  private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();
  //...
  public Object get(String name) {
    return this.cache.get(name);
  }
  // 如果不存在,才會put進去
  public Object put(String name, Object value) {
    // result若不等于null,表示緩存存在了,不會進行put操作
    Object result = this.cache.putIfAbsent(name, value);
    if (result != null) {
      // 直接返回舊對象
      return result;
    }
    // put成功,返回新對象
    return value;
  }
}

這里就是將Bean包裝成一個對象,緩存在一個Map中,下次如果再GetBean,還是那個舊的BeanWrapper?;氐絊cope的get方法,接下來就是調用BeanWrapper的getBean方法:

private Object bean;
public Object getBean() {
  if (this.bean == null) {
    synchronized (this.name) {
      if (this.bean == null) {
        this.bean = this.objectFactory.getObject();
      }
    }
  }
  return this.bean;
}

可以看出來,BeanWrapper中的bean變量即為實際Bean,如果第一次get肯定為空,就會調用BeanFactory的createBean方法創(chuàng)建Bean,創(chuàng)建出來之后就會一直保存下來。

由此可見,RefreshScope管理了Scope=Refresh的Bean的生命周期。

重新創(chuàng)建RefreshBean

當配置中心刷新配置之后,有兩種方式可以動態(tài)刷新Bean的配置變量值,(SpringCloud-Bus還是Nacos差不多都是這么實現(xiàn)的):

向上下文發(fā)布一個RefreshEvent事件

Http訪問/refresh這個EndPoint

不管是什么方式,最終都會調用ContextRefresher這個類的refresh方法,那么我們由此為入口來分析一下,熱加載配置的原理:

// 這就是我們上面一直分析的Scope對象(實際上可以看作一個保存refreshBean的Map)
private RefreshScope scope;
public synchronized Set<String> refresh() {
  // 更新上下文中Environment外部化配置值
  Set<String> keys = refreshEnvironment();
  // 調用scope對象的refreshAll方法
  this.scope.refreshAll();
  return keys;
}

我們一般是使用@Value、@ConfigurationProperties去獲取配置變量值,其底層在IOC中則是通過上下文的Environment對象去獲取property值,然后依賴注入利用反射Set到Bean對象中去的。

那么如果我們更新Environment里的Property值,然后重新創(chuàng)建一次RefreshBean,再進行一次上述的依賴注入,是不是就能完成配置熱加載了呢?@Value的變量值就可以加載為最新的了。

這里說的刷新Environment對象并重新依賴注入則為上述兩個方法做的事情:

Set keys = refreshEnvironment();

this.scope.refreshAll();

刷新Environment對象

刷新環(huán)境遍歷指的的是將配置替換到當前的Environment,后面如果再根據(jù)配置創(chuàng)建對象就會使用新的配置設置屬性。

例如org.springframework.cloud.endpoint.event.RefreshEventListener進行將配置文件刷新進入environment中的操作。

ConfigurableApplicationContext addConfigFilesToEnvironment() {
	StandardEnvironment environment = copyEnvironment(
					this.context.getEnvironment());
			SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
					.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
					.environment(environment);
			// Just the listeners that affect the environment (e.g. excluding logging
			// listener because it has side effects)
			builder.application()
					.setListeners(Arrays.asList(new BootstrapApplicationListener(),
							new ConfigFileApplicationListener()));
			capture = builder.run();
 }

可以看到,這里歸根結底就是SpringBoot啟動上下文那種方法,新做了一個Spring上下文,因為Spring啟動后會對上下文中的Environment進行初始化,獲取最新配置,所以這里利用Spring的啟動,達到了獲取最新的Environment對象的目的。然后去替換舊的上下文中的Environment對象中的配置值即可。

重新創(chuàng)建RefreshBean

經(jīng)過上述刷新Environment對象的動作,此時上下文中的配置值已經(jīng)是最新的了。思路回到ContextRefresher的refresh方法,接下來會調用Scope對象的refreshAll方法:

public void refreshAll() {
  // 銷毀Bean
  super.destroy();
  this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
public void destroy() {
  List<Throwable> errors = new ArrayList<Throwable>();
  // 緩存清空
  Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
  // ...
}

還記得上面的管理RefreshBean生命周期那一節(jié)關于緩存的討論嗎,cache變量是一個Map保存著RefreshBean實例,這里直接就將Map清空了。

思路回到BeanFactory的doGetBean的流程中,從IOC容器中獲取RefreshBean是交給RefreshScope的get方法做的:

public Object get(String name, ObjectFactory<?> objectFactory) {
  // 由于剛剛清空了緩存Map,這里就會put一個新的BeanLifecycleWrapper實例
  BeanLifecycleWrapper value = this.cache.put(name,
                                              new BeanLifecycleWrapper(name, objectFactory));
  this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
  try {
    // 在這里是新的BeanLifecycleWrapper實例調用getBean方法
    return value.getBean();
  }
  catch (RuntimeException e) {
    this.errors.put(name, e);
    throw e;
  }
}
public Object getBean() {
  // 由于是新的BeanLifecycleWrapper實例,這里一定為null
  if (this.bean == null) {
    synchronized (this.name) {
      if (this.bean == null) {
        // 調用IOC容器的createBean,再創(chuàng)建一個Bean出來
        this.bean = this.objectFactory.getObject();
      }
    }
  }
  return this.bean;
}

可以看到,此時RefreshBean被IOC容器重新創(chuàng)建一個出來了,經(jīng)過IOC的依賴注入功能,@Value的就是一個新的配置值了。到這里熱加載功能實現(xiàn)基本結束。

根據(jù)以上分析,我們可以看出只要每次我們都從IOC容器中getBean,那么拿到的RefreshBean一定是帶有最新配置值的Bean。

@RefreshScope代理對象

@Scope 的注冊 AnnotatedBeanDefinitionReader#registerBean

  public void registerBean(...){
    ...
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
      abd.setScope(scopeMetadata.getScopeName());
    ...
      definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  }

讀取@Scope元數(shù)據(jù), AnnotationScopeMetadataResolver#resolveScopeMetadata

public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
          AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
                  annDef.getMetadata(), Scope.class);
          if (attributes != null) {
              metadata.setScopeName(attributes.getString("value"));
              ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
              if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
                  proxyMode = this.defaultProxyMode;
              }
              metadata.setScopedProxyMode(proxyMode);
          }
}

Scope實例對象通過ScopedProxyFactoryBean創(chuàng)建,其中通過AOP使其實現(xiàn)ScopedObject接口,這里不再展開

每次使用@RefreshScope的bean的get方法時都會重新通過this.beanFactory.getBean(this.targetBeanName);

如果被清空了的話,那么會重新創(chuàng)建bean會使用,刷新后的environment的配置注入屬性,實現(xiàn)動態(tài)刷新。

到此這篇關于SpringBoot @Scope與@RefreshScope注解使用詳解的文章就介紹到這了,更多相關SpringBoot @Scope與@RefreshScope內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論