主頁 > 後端開發 > spring原始碼決議---一文講透@Configuration

spring原始碼決議---一文講透@Configuration

2020-09-10 23:06:10 後端開發

file

這是一篇長文,但是很自信的是我相信我講的已經非常透徹了,想必你讀完之后,一定對@Configuration有一個很透徹的理解,

@Configuration注解在實際的開發程序中,好像顯的不是那么的重要,因為使用的程序中,加他,不加他,貌似都沒有什么大的影響,那他存在到底有什么意義呢?我們來看一下下面的這段測驗實體,

// 測驗類A
public class A {
  public A(){
    System.out.println("create a");
  }
}

// 測驗類B
public class B {
  public B(){
    System.out.println("create b");
  }
}

// 配置類
@Component
@Configuration
@ComponentScan("com.lupf.configuration")
public class AppConfig {

  @Bean
  public A a() {
    // 這里呼叫b()
    b();
    return new A();
  }

  @Bean
  public B b(){
    return new B();
  }

}

// 啟動類
public class Test {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    ac.register(AppConfig.class);
    ac.refresh();
  }
}

測驗代碼很簡單,就是很普通的A與B兩個類,然后通過@Bean的方式注入;我們在@Bean的a()方法中呼叫了b()方法,按方法的呼叫關系來看的話,這樣就違背了spring單例的原則,直接呼叫b()方法的時候會new一個B;a()方法執行b();這一行代碼的時候又會new一個B;那實際情況是不是這樣呢?

  • 不加@Configuration的測驗
    file

    和我們所想的是一樣的,果然B被實體化了兩次,很顯然,他違背了單例原則;

  • 加上@Configuration的測驗
    file

    wtf? 正常了,,,不過這才是應該有的樣子,那加了注解,他到底經歷了什么呢?

  • 我們來看一下加與不加 spring生成的AppConfig是什么樣的?
    // 以下代碼獲取AppConfig
    AppConfig bean = ac.getBean(AppConfig.class);
    
    file

    代理物件?這樣就可以解釋了,這兩個物件創建的程序被cglib代理了

圖示代理流程

file

原理分析

結合上面的流程圖,我們來一步步的分析;要關注的核心點就是這個代理物件如何產生的長什么樣起什么用

  • 偽代理物件原理分析
    public class AppConfigProxy extends AppConfig{
      //bean工廠
      BeanFactory $$beanFactory;
    
      @Override
      public A a() {
        return super.a();
      }
    
      @Override
      public B b(){
        // 通過工廠去獲取一個B
        B b = (B)$$beanFactory.getBean("b");
        // 如果拿到了,就直接回傳
        if(null!=b){
          return b;
        }
        // 如果沒有拿到,就呼叫父類的方法創建
        return super.b();
      }
    }
    

    代理物件重寫了b()方法;這個b()方法并不是直接去new一個物件,而是先去Bean工廠里面去取一遍,如果取到之后,就直接回傳,如果沒有取到,那么就呼叫目標物件(父類方法,因為cglib是基于繼承實作的)去實體化!原理是不是比較的容易理解!spring就是這么做的?對!他也是這么做的,只不過最終實作起來,并沒有這么簡單,因為我們是寫死的,但對于spring框架來說,他并不知道你是一個什么類,做了哪些邏輯;下面一起從原始碼的角度去分析一下他是如何去代理的,

原始碼決議@Configuration掃描

  • 一,Bean的掃描判斷是否是全注解類!
    當spring通過Bean的后置處理器掃描類的時候,會去判斷當前類是否是一個全注解的類;何為全注解?加了@Configuration注解的類就是一個全注解類;在執行Bean注冊后置處理器BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法時,ConfigurationClassPostProcessor這個實作中會呼叫一個工具方法ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)),其中有一段代碼是用來校驗是否是全注解的

    /**
     * 確定是不是一個全配置類  判斷是否加了 @Configuration
     * @see AttributeAccessorSupport 保存到一個用于存盤元資料的list中
     */
    if (isFullConfigurationCandidate(metadata)) {
      // 往元素據的Map attributes中添加一個值為full的鍵值對
      beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    } else if (isLiteConfigurationCandidate(metadata)) {
      // 往元素據的Map attributes中添加一個值為lite的鍵值對
      beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    } else {
      return false;
    }
    
    //============================================================================
    // isFullConfigurationCandidate工具類,很直觀,就時判斷是否加了@Configuration注解
    public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
      return metadata.isAnnotated(Configuration.class.getName());
    }
    
    //============================================================================
    // isLiteConfigurationCandidate工具類
    // 半注解的校驗也很簡單,就是看是否加了
    // @Component、@ComponentScan、@Import、@ImportResource、@Bean這些注解
    public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
      // Do not consider an interface or an annotation...
      // 如果是一個介面直接回傳false
      if (metadata.isInterface()) {
        return false;
      }
    
      /**
       * Any of the typical annotations found?
       * candidateIndicators包含以下四種注解
       * Component.class.getName();
       * ComponentScan.class.getName();
       * Import.class.getName();
       * ImportResource.class.getName();
       */
      for (String indicator : candidateIndicators) {
        if (metadata.isAnnotated(indicator)) {
          return true;
        }
      }
    
      // Finally, let's look for @Bean methods...
      try {
        // 查找是否有@Bean的方法
        return metadata.hasAnnotatedMethods(Bean.class.getName());
      } catch (Throwable ex) {
        if (logger.isDebugEnabled()) {
          logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
        }
        return false;
      }
    }
    

    其中最重要的一行判斷metadata.isAnnotated(Configuration.class.getName())就確定了當前類是否是全注解,由于AppConfig是一個全注解的類,因此會在這個BeanDefinition的元資料Map中放一個值為full的鍵值對,作為一個標識,可是扯半天,又沒有去做啥實際的操作,這有啥用?有用!!!下面的操作就會用上,以下為AppConfig掃描時的程序,
    file

  • 二、完成代理
    當后置處理器執行完BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法之后;會執行BeanFactoryPostProcessor的postProcessBeanFactory方法;這個介面的實作類ConfigurationClassPostProcessor就去完成了全注解物件的代理,代碼如下:

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    int factoryId = System.identityHashCode(beanFactory);
    this.factoriesPostProcessed.add(factoryId);
    if (!this.registriesPostProcessed.contains(factoryId)) {
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }
    
    // 呼叫了該方法進行了屬性增強
    enhanceConfigurationClasses(beanFactory);
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
  }
  
  //=============================================================================
  // 繼續走enhanceConfigurationClasses,代碼如下
  public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);

      /**
       * 判斷當前類是否是一個全注解的類
       * 這個時候,前面添加的值為full的鍵值對就用上了
       */
      if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
        configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
    }
    if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      return;
    }

    /**
     * 如果是一個全注解的類 那么下面的代碼就會對全注解的類生成一個代理物件
     * 目的是為了解決一個依賴呼叫的問題
     */
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      // If a @Configuration class gets proxied, always proxy the target class
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      try {
        // Set enhanced subclass of the user-specified bean class
        // 這里拿到目標物件(原始物件)的class
        Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
        if (configClass != null) {
          // 這里實體化一個代理物件的class
          // 注意這里只是一個class 如果得到的是一個物件,那么將無法通過spring去管理這個物件
          // 拿到class之后,后續Bean的生命周期會將其實體化
          Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
          // 判斷是否成功獲取到代理物件的class
          // 如果獲取成功,那么enhancedClass必定就是另外一個class物件了
          if (configClass != enhancedClass) {
            // 得到class之后 將這個class設定到對應的beanDefinition中
            // 目的是為了后面spring實體化物件
            beanDef.setBeanClass(enhancedClass);
          }
        }
      } catch (Throwable ex) {
        throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
      }
    }
  }

為了篇幅,刪掉了原始碼中部分無用代碼;可以看出,方法一進來,就回圈所有的BD判斷當前類是不是一個全注解的類(判斷條件就是前面添加的值為full的鍵值對),如果是,就將他放到一個容器里面;回圈完沒有一個全注解的類,就直接回傳;如包含全注解的類,就通過Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);生成了一個代理Class,然后通過beanDef.setBeanClass(enhancedClass);替換原有的Class;這樣就完成了下圖所示的部分功能,
為什么這里是一個Class而不是一個Object呢
因為spring實體化物件的生命周期并不是在這里,如果這里直接創建一個Object,那這個Object是一個沒有走標準生命周期的Object,所以也就不會被spring進行管理;這里生成一個Class,后續的流程會將這個Class實體化一個標準的Bean,放置到容器中,
.
到此,一個被代理后的AppConfig對應的class就這么生成并回傳了;但是到這里還并沒有看到底褲,我們最終的目的可是要看到底褲,長驅直入!!!
file
file

代理類是如何產生的?

繼續深入enhance方法,,,,

  public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {

    /**
     * 判斷當前類是否已經被代理過了
     * 如果代理過了,cglib在生成代理物件的時候,會在代理類上面實作EnhancedConfiguration 方法
     */
    if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
      // 如果已經試代理物件了  就直接回傳就好了
      return configClass;
    }
    /**
     * newEnhancer(configClass, classLoader) 表示實體化一個Enhancer物件
     * createClass 方法執行完 得到一個代理物件
     */
    Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
    // 回傳代理物件
    return enhancedClass;
  }

發現他只是個花架子,但是已經發現核心代碼了 createClass(newEnhancer(configClass, classLoader));newEnhancer原始碼如下,這里就是整個代理物件的關鍵:

private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
  // 實體化一個Enhancer物件
  Enhancer enhancer = new Enhancer();
  // 設定代理物件的父類  因為cglib代理是通過繼承的方式實作的
  enhancer.setSuperclass(configSuperClass);
  // 標示用于增強的介面 代理物件回實作這個介面
  // 同時在代理物件生成的程序中 也會使用這個物件來判斷是否已經生成過代理物件了
  enhancer.setInterfaces(new Class<?>[]{EnhancedConfiguration.class});

  enhancer.setUseFactory(false);

  // 設定代理物件的命名規則
  enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);

  /**
   * 設定一個策略
   * 當cglib生成一個代理物件的時候,會為這個物件添加一個$$beanFactory屬性
   * 見代碼: declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
   *
   * 該屬性是用來獲取bean物件的
   *
   * 這里只是添加了一個BeanFactory物件 會在后續的代碼中給實體化
   *
   */
  enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));

  /**
   * 這里會添加回呼的過濾器
   */
  enhancer.setCallbackFilter(CALLBACK_FILTER);
  enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
  return enhancer;
}

file

關鍵代碼說明!

  • 設定代理物件實作的介面

      enhancer.setInterfaces(new Class<?>[]{EnhancedConfiguration.class});
      
      // =================================================
      // 具體的EnhancedConfiguration介面類
      public interface EnhancedConfiguration extends BeanFactoryAware {
      }
    

    這行代碼會讓最后生成的代理物件實作指定的EnhancedConfiguration介面;上面代碼可以看出該介面是一個空介面,但是他的父類BeanFactoryAware有一個setBeanFactory(BeanFactory beanFactory)的介面;因此,實作EnhancedConfiguration介面的主要目的是為了后面給$$beanFactory賦值及賦值前生命周期流程中判斷是否是一個代理物件所用;如果不知道$$beanFactory是個啥可以跳過,下面一段就是介紹這個$$beanFactory是咋來的

  • 設定策略

    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    
    // 以下為BeanFactoryAwareGeneratorStrateg物件
    private static class BeanFactoryAwareGeneratorStrategy extends DefaultGeneratorStrategy {
    
      @Nullable
      private final ClassLoader classLoader;
    
      public BeanFactoryAwareGeneratorStrategy(@Nullable ClassLoader classLoader) {
        this.classLoader = classLoader;
      }
    
      @Override
      protected ClassGenerator transform(ClassGenerator cg) throws Exception {
        ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
          @Override
          public void end_class() {
            declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
            super.end_class();
          }
        };
        return new TransformingClassGenerator(cg, transformer);
      }
    
      @Override
      public byte[] generate(ClassGenerator cg) throws Exception {
        if (this.classLoader == null) {
          return super.generate(cg);
        }
    
        Thread currentThread = Thread.currentThread();
        ClassLoader threadContextClassLoader;
        try {
          threadContextClassLoader = currentThread.getContextClassLoader();
        } catch (Throwable ex) {
          // Cannot access thread context ClassLoader - falling back...
          return super.generate(cg);
        }
    
        boolean overrideClassLoader = !this.classLoader.equals(threadContextClassLoader);
        if (overrideClassLoader) {
          currentThread.setContextClassLoader(this.classLoader);
        }
        try {
          return super.generate(cg);
        } finally {
          if (overrideClassLoader) {
            // Reset original thread context ClassLoader.
            currentThread.setContextClassLoader(threadContextClassLoader);
          }
        }
      }
    }
    

    這里面有一段很關鍵的代碼
    declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
    就是為這個代理物件添加一個名叫$$beanFactoryBeanFactory屬性(下圖為證),對應的實作為DefaultListableBeanFactory,其目的就是用來獲取Bean,是不是和最開始我們構想的偽代碼有異曲同工之妙!
    可是那這玩兒動態加進去的,在哪里給$$beanFactory賦值的呢?您接著往后看

file

file

  • 添加回呼過濾器

    /**
     * 這里會添加回呼的過濾器
     */
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    
    // CALLBACK_FILTER是啥?
    private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
    
    // CALLBACKS是個啥?
    private static final Callback[] CALLBACKS = new Callback[]{
        // 在這里,執行方法的時候,就會優先去判斷,物件是否已經創建
        // 創建之后就回傳,沒有創建就呼叫父介面創建
        new BeanMethodInterceptor(),
    
        // 在這里 會通過反射的方式 給$$beanFactory賦值
        new BeanFactoryAwareMethodInterceptor(),
        NoOp.INSTANCE
    };
    

    從上面的代碼可以看出,其最終是添加了兩個方法執行的攔截器;這兩個方法執行的攔截器非常重要;BeanFactoryAwareMethodInterceptor主要的職責就是為$$beanFactory物件賦值BeanMethodInterceptor的主要功能是判斷Bean是否已經創建了;具體見下面的詳細分析

  • BeanFactoryAwareMethodInterceptor **
    該MethodInterceptor的主要功能就是通過反射的方式,給上面添加的
    $$beanFactory賦值**,具體代碼如下:

    private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
    
    	@Override
    	@Nullable
    	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    		// 找到$$beanFactory對應的field
    		Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
    		Assert.state(field != null, "Unable to find generated BeanFactory field");
    		// 通過反射為$$beanFactory賦值
    		field.set(obj, args[0]);
    
    		// Does the actual (non-CGLIB) superclass implement BeanFactoryAware?
    		// If so, call its setBeanFactory() method. If not, just exit.
    		if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
    			return proxy.invokeSuper(obj, args);
    		}
    		return null;
    	}
    
    	@Override
    	public boolean isMatch(Method candidateMethod) {
    		// 這里會去判斷是否是setBeanFactory方法,如果是,才會執行上面的intercept 方法
    		return isSetBeanFactory(candidateMethod);
    	}
    
    	public static boolean isSetBeanFactory(Method candidateMethod) {
    		return (candidateMethod.getName().equals("setBeanFactory") &&
    				candidateMethod.getParameterCount() == 1 &&
    				BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
    				BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
    	}
    }
    

    isSetBeanFactory(candidateMethod);這個判斷需要注意一下,因為他的存在,約束了這個攔截器就只會去處理setBeanFactory這個方法的呼叫;具體的賦值其實已經很簡單了,找到Field,然后通過反射去賦值;但是又帶出了一個更重要的問題,這個方法什么時候呼叫的?

  • setBeanFactory是什么時候呼叫的?
    討論這個問題之前,我們需要往前回顧一點東西,就是ConfigurationClassPostProcessor的postProcessBeanFactory方法通過呼叫enhanceConfigurationClasses()去完成了代理物件的生成之后,還做了一些事情;如果不記得了,這里我在把代碼再貼一遍:

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    	int factoryId = System.identityHashCode(beanFactory);
    	this.factoriesPostProcessed.add(factoryId);
    	if (!this.registriesPostProcessed.contains(factoryId)) {
    		processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    	}
    
    	// 呼叫了該方法進行了增強
    	enhanceConfigurationClasses(beanFactory);
    
    	/**
    	 * 這里的后處理器的作用就是給實作了EnhancedConfiguration介面的bean設定BeanFactory物件
    	 * 如果是全注解Bean,生成的代理Class都實作了EnhancedConfiguration介面
    	 */
    	beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }
    

    可以看到,當完成了代理class生成之后,就往Bean工廠里面添加了一個叫ImportAwareBeanPostProcessor的后置處理器,這個后置處理器就是呼叫setBeanFactory的關鍵,其中postProcessProperties方法(其他的方法暫時不重要)的具體代碼如下:

      @Override
      public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
      	if (bean instanceof EnhancedConfiguration) {
      		((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
      	}
      	return pvs;
      }
    

    代碼也很簡單明了,就是判斷bean是不是實作了EnhancedConfiguration介面,如果實作了就呼叫setBeanFactory方法,這樣也就和前面說的為什么要實作這個介面的原因呼應上了;現在是呼叫的地方找到了,那ImportAwareBeanPostProcessor這個后置處理器又是在哪里呼叫的呢? 這里就涉及到Bean生命周期了,詳細細節很難一兩句說清楚,那么這里就直接給個結論,后續會對spring bean生命周期進行詳細的介紹,如果讓你為一個物件去呼叫set方法賦值,你會在什么地方去做?當然是在物件創建之后撒!spring也是一樣,spring通過doCreateBean方法創建Bean的時,物件創建完會通過populateBean方法去完成屬性注入,注入時有一段代碼就是來呼叫這個后置處理器的,詳情如下:

    for (BeanPostProcessor bp : getBeanPostProcessors()) {
      	if (bp instanceof InstantiationAwareBeanPostProcessor) {
      		InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
      			/**
      		 * 在這里通過bean的后置處理器去完成依賴注入
      		 * 如果使用的是@Autowried進行注入的時候,使用的是 {@link AutowiredAnnotationBeanPostProcessor} 進行屬性的注入
      		 * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
      		 *
      		 * 如果使用的xml配置的,那么就使用的是 CommonAnnotationBeanPostProcessor 進行屬性的注入
      		 * @see org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
      		 */
      		PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
      		if (pvsToUse == null) {
      			if (filteredPds == null) {
      				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
      			}
      			//自動注入
      			pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
      			if (pvsToUse == null) {
      				return;
      			}
      		}
      		pvs = pvsToUse;
      	}
      }
    

    當AppConfig物件實體化成功之后,去注入屬性時,就會執行上面的for;而ImportAwareBeanPostProcessor就是要執行的后置處理器的其中一個,自然也就會呼叫setBeanFactory完成了AppConfig代理物件中$$beanFactory的填充,

file

  • BeanMethodInterceptor
    繼續回來介紹另外一個攔截器,他的主要職責就是通過$$beanFactory去判斷物件是否已經生成了,由于代碼比較多,這里只貼intercept的部分(無用代碼已經洗掉)

    public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
    						MethodProxy cglibMethodProxy) throws Throwable {
    
    	// 得到beanFactory
    	ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
    	// 拿到beanName
    	String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
    
    	// Determine whether this bean is a scoped-proxy
    	if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
    		String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
    		if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
    			beanName = scopedBeanName;
    		}
    	}
    
    	/**
    	 * 這里是用來判斷當前的Bean是不是一個FactoryBean 如果是的話,那么就先創建FactoryBean
    	 * 然后再創建Bean
    	 */
    	if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
    			factoryContainsBean(beanFactory, beanName)) {
    		Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
    		if (factoryBean instanceof ScopedProxyFactoryBean) {
    			// Scoped proxy factory beans are a special case and should not be further proxied
    		} else {
    			// It is a candidate FactoryBean - go ahead with enhancement
    			return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
    		}
    	}
    
    	if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
    		// 如果回傳的是同一個方法,說明正在實體化
    		// 那么就呼叫目標物件的方法
    		return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
    	}
    
    	// 如果不是一個   那么就去獲取bean
    	return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
    }	
    
    • beanFactory獲取
        ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
        
        // 核心代碼 通過反射獲取
        Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);
        Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);
      
    • beanName獲取
      通過拿到Mathod上面的@Bean注解,從注解中取到name屬性
    • beanFactory判斷
      優先判斷是否是一個beanFactory,如果是,先創建beanFactory,再獲取Bean
    • 核心判斷,校驗是否已經創建物件了
        if (isCurrentlyInvokedFactoryMethod(beanMethod))
        
        //====================================
        private boolean isCurrentlyInvokedFactoryMethod(Method method) {
        	// 獲取到當前是誰在呼叫對應的方法
        	Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
        	// 校驗method是否和currentlyInvoked為同一個方法
        	return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
        			Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
        }
      
      通過以下示例代碼講解這個判斷,注意,這里很重要,也是理解這塊兒東西的關鍵

    @Bean
    public A a() {
    // 這里呼叫b()
    b();
    return new A();
    }

    @Bean
    public B b(){
    return new B();
    }
    ```
    > 當spring創建@Bean物件的時候,會直接呼叫對應的方法,如創建物件B,就會直接呼叫appConfig.b();上面的那個判斷的邏輯就是創建這個物件的方法和呼叫當前的方法是不是同一個;這里創建B物件的時候,創建物件的方法是b(),呼叫者也是b(),所以判斷回傳的為true,就是執行了cglibMethodProxy.invokeSuper呼叫目標方法去實體化一個物件;
    > 當執行a()方法的時候,同樣會經歷上面的判斷,發現呼叫者和實體化的方法都是a();呼叫目標方法;但是(注意轉折了哈!),執行目標方法的時候,第一行就是去呼叫b();此時就來到了b()方法的呼叫,因為攔截器攔截的原因這里也會觸發這個intercept方法;但是現在就和之前不一樣了,現在的呼叫者是誰?是a()方法,執行實體化的是誰?是b()方法!這樣上面的if判斷回傳的就是false,就會讓流程走到resolveBeanReference方法去獲取Bean,如果有就直接從容器里面回傳,沒有就去實體化一個;就這樣,就算a()方法中會呼叫b();結果發現容器里面已經有一個了,就直接回傳了,如此騷操作,就可以保證物件只會創建一個,如果你們沒有想明白,麻煩把這一段話仔細讀幾遍,應該就能明白了
    file

到這里,不過如此嘛!是吧!

碼字不易,感謝您的關注!點贊!評論!!!

file

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/3658.html

標籤:Java

上一篇:Golang學習系列第三天:學習陣列、切片、Map、結構體、指標、函式、介面型別、channel通道

下一篇:Laravel 框架實作無限極分類

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more