配置類爲什麼要添加@Configuration註解?

配置類爲什麼要添加@Configuration註解呢?

本系列文章:

讀源碼,我們可以從第一行讀起

你知道Spring是怎麼解析配置類的嗎?

推薦閱讀:

Spring官網閱讀 | 總結篇

Spring雜談

本系列文章將會帶你一行行的將Spring的源碼吃透,推薦閱讀的文章是閱讀源碼的基礎!

不加@Configuration導致的問題

我們先來看看如果不在配置類上添加@Configuration註解會有什麼問題,代碼示例如下:

@ComponentScan("com.dmz.source.code")
//@Configuration
public class Config{
	@Bean
	public A a(){
	return new A(dmzService());
	}

	@Bean
	public DmzService dmzService(){
		return new DmzService();
	}
}

public class A {
	public A(DmzService dmzService){
		System.out.println("create A by dmzService");
	}
}

@Component
public class DmzService {
	public DmzService(){
		System.out.println("create dmzService");
	}
}

不添加@Configuration註解運行結果:

create dmzService
create A by dmzService
create dmzService

添加@Configuration註解運行結果:

create dmzService
create A by dmzService

在上面的例子中,我們會發現沒有添加@Configuraion註解時dmzService被創建了兩次, 這是因爲第一次創建是被Spring容器所創建的,Spring調用這個dmzService()創建了一個Bean被放入了單例池中(沒有添加其它配置默認是單例的),第二次創建是Spring容器在創建a時調用了a(),而a()又調用了dmzService()方法。

這樣的話,就出現問題了。

第一,對於dmzService而言,它被創建了兩次,單例被打破了

第二,對於a而言,它所依賴的dmzService不是Spring所管理的,而是直接調用的一個普通的java method創建的普通對象。這個對象不被Spring所管理意味着,首先它的域(Scope)定義失效了其次它沒有經過一個完整的生命週期,那麼我們所定義所有的Bean的後置處理器都沒有作用到它身上,其中就包括了完成AOP的後置處理器,所以AOP也失效了

上面的分析不能說服你的話,我們可以看看官方在@Bean上給出的這一段註釋

bean2

首先,Spring就在註釋中指出了,通常來說,BeanMethod一般都申明在一個被@Configuration註解標註的類中,在這種情況下,BeanMethod可能直接引用了在同一個類中申明的beanMethod,就像本文給出的例子那樣,a()直接引用了dmzService(),我們重點再看看劃紅線的部分,通過調用另外一個beanMethod進入的Bean的引用會被保證是遵從域定義以及AOP語義的,就像getBean所做的那樣。這是怎麼實現的呢?在最後被紅線標註的地方也有說明,是通過在運行時期爲沒有被@Configuration註解標註的配置類生成一個CGLIB的子類。

源碼分析

Spring是在什麼時候創建的代理呢?到目前爲止我們應該沒有落掉Spring整個啓動流程的任何關鍵代碼,那麼我們不妨帶着這個問題繼續往下看。目前來說我們已經閱讀到了Spring執行流程圖中的3-5步,也就是org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法,在之前的分析中我們已經知道了,這個方法的主要作用就是執行BeanFactoryPostProcessor中的方法,首先執行的是BeanDefinitionRegistryPostProcessor(繼承了BeanFactoryPostProcessor)的postProcessBeanDefinitionRegistry方法,然後執行postProcessBeanFactory方法。而到目前爲止我們並沒有向容器中註冊bean工廠的後置處理器(BeanFactoryPostProcessor),這就意味着當前容器中只有一個ConfigurationClassPostProcessor會被執行,在前文中我們已經分析過了它的postProcessBeanDefinitionRegistry方法,緊接着我們就來看看它的postProcessBeanFactory方法做了什麼。其源碼如下:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   int factoryId = System.identityHashCode(beanFactory);
   // 防止重複處理
   if (this.factoriesPostProcessed.contains(factoryId)) {
       throw new IllegalStateException(
           "postProcessBeanFactory already called on this post-processor against " + beanFactory);
   }
   this.factoriesPostProcessed.add(factoryId);
   // 在執行postProcessBeanDefinitionRegistry方法的時就已經將這個id添加到registriesPostProcessed集合中了
   if (!this.registriesPostProcessed.contains(factoryId)) {
       processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
   }
	// 看起來這個方法就是完成了代理
   enhanceConfigurationClasses(beanFactory);
   // 添加了一個後置處理器
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

enhanceConfigurationClasses源碼分析

	public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {		// map中放置的是所有需要被代理的類
		Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
		for (String beanName : beanFactory.getBeanDefinitionNames()) {
			BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
				// 省略異常跟日誌代碼....
               // 這個代碼的含義就是如果是一個被@Configuration註解標註的類,那麼將其放入到configBeanDefs這個集合中
				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();
			// 對於配置類永遠使用cglib代理
			beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
			try {
				// cglib代理是基於類實現的,所以在這之前要明確代理的類是什麼
				Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
				if (configClass != null) {
                   // 通過ConfigurationClassEnhancer獲取到一個經過代理的class
					Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
                  // 省略日誌....
                   
                   // 將原有的配置類的bd中的beanClass屬性替換成代理後的class
						beanDef.setBeanClass(enhancedClass);
					}
				}
			}
			catch (Throwable ex) {
				throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
			}
		}
	}

這段代碼非常簡單,核心的代碼在ConfigurationClassEnhancer中,所以我們要分析下ConfigurationClassEnhancer的源碼,在分析它的源碼前,我們需要對cglib有一定的瞭解。

1、cglib原理分析

1.1、使用示例

public class Target{
   public void f(){
       System.out.println("Target f()");
   }
   public void g(){
       System.out.println("Target g()");
   }
}

public class Interceptor implements MethodInterceptor {
   @Override
   public Object intercept(Object obj, Method method, Object[] args,    MethodProxy proxy) throws Throwable {
       System.out.println("I am intercept begin");
//Note: 此處一定要使用proxy的invokeSuper方法來調用目標類的方法
       proxy.invokeSuper(obj, args);
       System.out.println("I am intercept end");
       return null;
   }
}

public class Test {
   public static void main(String[] args) {
       // 設置這個屬性,將代理類的字節碼文件生成到F盤的code目錄下
   System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code");
        //實例化一個增強器,也就是cglib中的一個class generator
       Enhancer eh = new Enhancer();
        //設置目標類
       eh.setSuperclass(Target.class);
       // 設置攔截對象
       eh.setCallback(new Interceptor());
       // 生成代理類並返回一個實例
       Target t = (Target) eh.create();
       t.f();
       t.g();
   }
}

運行結果爲:

I am intercept begin
Target f()
I am intercept end
I am intercept begin
Target g()
I am intercept end

1.2、原理分析

查看F盤的code目錄,會發現多了以下幾個文件

cglib

其中第二個文件就是我們的代理類字節碼,將其直接用IDEA打開

// 省略多餘的方法,我們就關注g方法
public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory
{
   
   final void CGLIB$g$0()
   {
     super.g();
   }
   
   // 經過代理過的g方法
   public final void g()
   {
   
   // 查看是否有攔截器存在
     MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
     if (tmp4_1 == null)
     {
         CGLIB$BIND_CALLBACKS(this);
         tmp4_1 = this.CGLIB$CALLBACK_0;
     }
     
     // 如果有攔截器的存在的話,直接調用攔截器的方法
     if (this.CGLIB$CALLBACK_0 != null) {
         tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
     }
     
     // 如果沒有攔截器,說明不需要代理,直接調用父類方法,也就是目標類的方法
     else{
         super.g();
     }
   }
}
可以看到,代理類繼承了目標類(Target),代理類爲每個目標類的方法生成兩個方法,例如針對目標類中的每個非private方法,代理類會生成兩個方法,以g方法爲例:一個是@Override的g方法,一個是CGLIB$g$0(CGLIB$g$0相當於目標類的g方法)。我們在示例代碼中調用目標類的方法t.g()時,實際上調用的是代理類中的g()方法。

從這裏就能看出,跟JDK動態代理不同的是,cglib代理採用的是繼承的方式生成的代理對象。

在上面的例子中,我們實現了對cglib中方法的攔截,但是就目前而言我們沒有辦法選擇性的攔截目標類中的某一個方法,假設現在我們只想攔截Target中的g方法而不攔截f方法有什麼方法呢?我們看下面這個例子

public class Main {
	public static void main(String[] args) {
		System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code");
		//實例化一個增強器,也就是cglib中的一個class generator
		Enhancer eh = new Enhancer();
		//設置目標類
		eh.setSuperclass(Target.class);
		// 設置攔截對象
		eh.setCallbacks(new Callback[]{new Interceptor(), NoOp.INSTANCE});
		eh.setCallbackFilter(new CallbackFilter() {
			@Override
			public int accept(Method method) {
				if(method.getName().equals("g"))
             // 這裏返回的是上面定義的callback數組的下標,0就是我們的Interceptor對象,1是內置的NoOp對象,代表不做任何操作
				return 0;
				else return 1;
			}
		});
		// 生成代理類並返回一個實例
		Target t = (Target) eh.create();
		t.f();
		t.g();
	}
}

運行結果:

Target f()
I am intercept begin
Target g()
I am intercept end

此時f方法已經不會被代理了

2、ConfigurationClassEnhancer源碼分析

2.1、創建代理過程分析

在對cglib的原理有了一定了解後,我們再來看ConfigurationClassEnhancer的源碼就輕鬆多了

我們就關注其中核心的幾個方法,代碼如下:

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
   // 如果已經實現了EnhancedConfiguration接口,說明被代理過了,直接返回
   if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
       return configClass;
   }
   // 否則調用newEnhancer方法先創建一個增強器,然後直接使用這個增強器生成代理類的字節碼對象
   Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
   if (logger.isDebugEnabled()) {
       logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
                                  configClass.getName(), enhancedClass.getName()));
   }
   return enhancedClass;
}

private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
   Enhancer enhancer = new Enhancer();
   // 設置目標類
   enhancer.setSuperclass(configSuperClass);
   // 讓代理類實現EnhancedConfiguration接口,這個接口繼承了BeanFactoryAware接口
   // 主要兩個作用:1.起到標記作用,如果實現了,代表已經被代理過了
   // 2.代理類需要訪問BeanFactory,所有實現了BeanFactoryAware接口
   enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
   // 設置生成的代理類不實現factory接口
   enhancer.setUseFactory(false);
   // 設置代理類名稱的生成策略
   enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
   // 代理類中引入一個BeanFactory字段
   enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
   // 設置過濾器,CALLBACK_FILTER中也同時設置了攔截器
   enhancer.setCallbackFilter(CALLBACK_FILTER);
   enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
   return enhancer;
}

// 使用增強器生成代理類的字節碼對象
private Class<?> createClass(Enhancer enhancer) {
   Class<?> subclass = enhancer.createClass();
   Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
   return subclass;
}

並且我們會發現,在最開始這個類就申明瞭三個攔截器

// 聲明的三個攔截器
private static final Callback[] CALLBACKS = new Callback[] {
   new BeanMethodInterceptor(),
   new BeanFactoryAwareMethodInterceptor(),
   NoOp.INSTANCE
};

2.2、攔截器源碼分析

基於我們之前對cglib的學習,肯定能知道,代理的核心邏輯就是依賴於攔截器實現的。其中NoOp.INSTANCE代表什麼都沒做,我們就關注前面兩個。

BeanFactoryAwareMethodInterceptor

之所以把這個攔截器放到前面分析是因爲這個攔截器的執行時機是在創建配置類的時候,其源碼如下:

private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {

		@Override
		@Nullable
		public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
			// 在生成代理類的字節碼時,使用了BeanFactoryAwareGeneratorStrategy策略
			// 這個策略會在代理類中添加一個字段,BEAN_FACTORY_FIELD = "$$beanFactory"
			Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
			Assert.state(field != null, "Unable to find generated BeanFactory field");
			// 此時調用的方法是setBeanFactory方法,
			// 直接通過反射將beanFactory賦值給BEAN_FACTORY_FIELD字段
			field.set(obj, args[0]);

			// Does the actual (non-CGLIB) superclass implement BeanFactoryAware?
			// If so, call its setBeanFactory() method. If not, just exit.
			// 如果目標配置類直接實現了BeanFactoryAware接口,那麼直接調用目標類的setBeanFactory方法
			if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
				return proxy.invokeSuper(obj, args);
			}
			return null;
		}

		@Override
		// 在調用setBeanFactory方法時纔會攔截
		// 從前文我們知道,代理類是實現了實現EnhancedConfiguration接口的,
		// 這就意味着它也實現了BeanFactoryAware接口,那麼在創建配置類時,
		// setBeanFactory方法就會被調用,之後會就進入到這個攔截器的intercept方法邏輯中
		public boolean isMatch(Method candidateMethod) {
			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()));
		}
	}


BeanMethodInterceptor

相比於上面一個攔截器,這個攔截器的邏輯就要複雜多了,我們先來看看它的執行時機,也就是isMatch方法

public boolean isMatch(Method candidateMethod) {
   // 第一個條件,不能是Object,這個必定是滿足的
   // 第二個條件,不能是setBeanFactory方法,顯而易見的嘛,我們要攔截的方法實際只應該是添加了@Bean註解的方法
   // 第三個條件,添加了@Bean註解
   return (candidateMethod.getDeclaringClass() != Object.class &&
           !BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
           BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}

簡而言之,就是攔截@Bean標註的方法,知道了執行時機後,我們再來看看它的攔截邏輯,代碼其實不是很長,但是理解起來確很不容易,牽涉到AOP以及Bean的創建了,不過放心,我會結合實例給你講明白這段代碼,下面我們先看源碼:

public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
								MethodProxy cglibMethodProxy) throws Throwable {
			// 之前不是給BEAN_FACTORY_FIELD這個字段賦值了BeanFactory嗎,這裏就是反射獲取之前賦的值
			ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
			// 確定Bean的名稱
			String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

			// Determine whether this bean is a scoped-proxy
			// 判斷這個Bean是否是一個域代理的類
			Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
			// 存在@Scope註解,並且開啓了域代理模式
			if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
				String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
				// 域代理對象的目標對象正在被創建,什麼時候會被創建?當然是使用的時候嘛
				if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
					// 使用的時候調用@Bean方法來創建這個域代理的目標對象,所以@Bean方法代理的時候針對的是域代理的目標對象,目標對象需要通過getBean的方式創建
					beanName = scopedBeanName;
				}
			}

			// 判斷這個bean是否是一個factoryBean
			if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
					factoryContainsBean(beanFactory, beanName)) {
				Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
				if (factoryBean instanceof ScopedProxyFactoryBean) {
					// ScopedProxyFactoryBean還記得嗎?在進行域代理時使用的就是這個對象
					// 對於這個FactoryBean我們是不需要進行代理的,因爲這個factoryBean的getObject方法
					// 只是爲了得到一個類似於佔位符的Bean,這個Bean只是爲了讓依賴它的Bean在創建的過程中不會報錯
					// 所以對於這個FactoryBean我們是不需要進行代理的
					// 我們只需要保證這個FactoryBean所生成的代理對象的目標對象是通過getBean的方式創建的即可
				} else {
					// 而對於普通的FactoryBean我們需要代理其getObject方法,確保getObject方法產生的Bean是通過getBean的方式創建的
					// It is a candidate FactoryBean - go ahead with enhancement
					return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
				}
			}
			// 舉個例子,假設我們被@Bean標註的是A方法,當前創建的BeanName也是a,這樣就符合了這個條件
			// 但是如果是這種請求,a(){b()},a方法中調用的b方法,那麼此時調用b方法創建b對象時正在執行的就是a方法
			// 此時就不滿足這個條件,會調用這個resolveBeanReference方法來解決方法引用
			if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
				// 如果當前執行的方法就是這個被攔截的方法,(說明是在創建這個Bean的過程中)
				// 那麼直接執行目標類中的方法,也就是我們在配置類中用@Bean標註的方法
				return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
			}
   // 說明不是在創建中了,而是別的地方直接調用了這個方法,這時候就需要代理了,實際調用getBean方法
   return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);

		private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
											ConfigurableBeanFactory beanFactory, String beanName) {

			// The user (i.e. not the factory) is requesting this bean through a call to
			// the bean method, direct or indirect. The bean may have already been marked
			// as 'in creation' in certain autowiring scenarios; if so, temporarily set
			// the in-creation status to false in order to avoid an exception.
			// 什麼時候會是alreadyInCreation?就是正在創建中,當Spring完成掃描後得到了所有的BeanDefinition
			// 那麼之後就會遍歷所有的BeanDefinition,根據BeanDefinition一個個的創建Bean,在創建Bean前會將這個Bean
			// 標記爲正在創建的,如果是正在創建的Bean,先將其標記爲非正在創建,也就是這行代碼beanFactory.setCurrentlyInCreation(beanName, false)
			// 這是因爲之後又會調用getBean方法,如果已經被標記爲創建中了,那麼在調用getBean時會報錯
			boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
			try {
               // 如果是正在創建的Bean,先將其標記爲非正在創建,避免後續調用getBean時報錯
				if (alreadyInCreation) {
					beanFactory.setCurrentlyInCreation(beanName, false);
				}
               
               // 在調用beanMthod的時候,也就是被@Bean註解標註的方法的時候如果使用了參數,只要有一個參數爲null,就直接調用getBean(beanName),否則帶參數調用getBean(beanName,args),後面通過例子解釋這段代碼
				boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
				if (useArgs && beanFactory.isSingleton(beanName)) {
					for (Object arg : beanMethodArgs) {
						if (arg == null) {
							useArgs = false;
							break;
						}
					}
				}
				Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
						beanFactory.getBean(beanName));
               // 這裏發現getBean返回的類型不是我們方法返回的類型,這意味着什麼呢?
               // 在《你知道Spring是怎麼解析配置類的嗎?》我有提到過BeanDefinition的覆蓋
               // 這個地方說明beanMethod所定義的bd被覆蓋了
				if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {

					if (beanInstance.equals(null)) {
						beanInstance = null;
					} else {
						// 省略日誌
						throw new IllegalStateException(msg);
					}
				}
               // 註冊Bean之間的依賴關係
               // 這個method是當前執行的一個創建bean的方法
				Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
               // 不等於null意味着currentlyInvoked這個方法創建的bean依賴了beanName所代表的Bean
          		// 在開頭的例子中,currentlyInvoked就是a(),beanName就是dmzService,outBeanName就是a
				if (currentlyInvoked != null) {
					String outerBeanName = BeanAnnotationHelper.determineBeanNafanhr(currentlyInvoked);
                   // 註冊的就是a跟dmzService的依賴關係,註冊到容器中的dependentBeanMap中
                   // key爲依賴,value爲依賴所在的bean
					beanFactory.registerDependentBean(beanName, outerBeanName);
				}
				return beanInstance;
			} finally {
				if (alreadyInCreation) {
                   // 實際還在創建中,要走完整個生命週期流程
					beanFactory.setCurrentlyInCreation(beanName, true);
				}
			}
		}



3、結合例子講解難點代碼

這部分內容非常細節,不感興趣可以跳過,主要是BeanMethodInterceptor中的方法。

3.1、判斷這個Bean是否是一個域代理的類

示例代碼
@Configuration
@EnableAspectJAutoProxy
public class Config {
   @Bean
   @Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.TARGET_CLASS)
   public DmzService dmzService() {
       return new DmzService();
   }
}

@RestController
@RequestMapping("/test")
public class Controller {

   DmzService dmzService;

   @Autowired
   public void setDmzService(DmzService dmzService) {
       this.dmzService = dmzService;
   }

   @GetMapping("/get")
   public ResponseEntity<?> get() {
       System.out.println(dmzService);
       return ResponseEntity.ok().build();
   }
}


我們需要調試兩種情況

  • 創建Controller時,注入dmzService,因爲dmzService是一個request域的對象,正常情況下注入肯定是報錯的,但是我們在配置類上對域對象開啓了代理模式,所以在創建Controller時會注入一個代理對象。

0511

端點調試,也確實如我們所料,這個地方注入的確實是一個代理對象,因爲我們在配置類上申明瞭proxyMode = ScopedProxyMode.TARGET_CLASS,所以這裏是一個cglib的代理對象。

  • 使用dmzService的時候,這個時候使用的應該是實際的目標對象。所以按照我們的分析應該通過getBean(targetBeanName)的方式來獲取到這個Bean,執行流程應該是代理對象cglibDmzService調用了toString方法,然後調用getBean,getBean要根據BeanDefinition創建Bean,而根據BeanDefinition的定義,需要使用配置類中的BeanMethod來創建Bean,所以此時會進入到BeanMethodInterceptor的intecept方法。

我們直接在intecept方法中進行斷點,會發現此時的調用棧如下

0513

  1. 打印時,調用了toString方法
  2. 實際將會去創建目標Bean,所以此時getBean時對應的BeanName爲targetBeanName(“scopedTarget.”+beanName)
  3. 在getBean時根據BeanDefinition的定義會通過執行配置類中的beanMethod方法來創建Bean
  4. 最終就進入了攔截器中這個方法

這種情況下就會進入到下面這段代碼的邏輯中

// 判斷這個Bean是否是一個域代理的類
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
// 存在@Scope註解,並且開啓了域代理模式
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
   String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
   // 域代理對象的目標對象正在被創建,什麼時候會被創建?當然是使用的時候嘛
   if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
       // 使用的時候調用@Bean方法來創建這個域代理的目標對象,所以@Bean方法代理的時候針對的是域代理的目標對象
       beanName = scopedBeanName;
   }
}


3.3、方法引用的情況下,爲什麼會出現Bean正在創建中(isCurrentlyInCreation)?

也就是下面這段代碼什麼時候會成立

if (alreadyInCreation) {
   beanFactory.setCurrentlyInCreation(beanName, false);
}


示例代碼
@ComponentScan(value = "com.dmz.spring.first")
@Configuration
public class Config {
	@Bean
	public A a(){
		return new A();
	}

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

class A{
	B b;

	@Autowired
	public void setB(B b) {
		this.b = b;
	}
}
class B{

}


上面這種配置,在啓動的時候就會進入到if條件中,在創建a的時候發現需要注入b,那麼Spring此時就會去創建b,b在創建的過程中又調用了a方法,此時a方法在執行時又被攔截了,然後就會進入到if判斷中去。對Spring有一定了解的同學應該能感覺到,這個其實跟循環依賴的原理是一樣的。關於循環依賴,在後面我單獨寫一篇文章進行說明。

3.4、if (arg == null) {useArgs = false;}是什麼意思?

這個代碼我初看時也很不明白,爲什麼只要有一個參數爲null就直接標記成不使用參數呢?我說說自己的理解。

beanMethodArgs代表了調用beanMethod時傳入的參數,正常Spring自身是不會傳入這個參數的,因爲沒有必要,創建Bean時其依賴早就通過BeanDefinition確定了,但是可能出現下面這種情況

示例代碼
@Configuration
public class AnotherConfig {
	@Bean
	public DmzService dmzService(IndexService indexService) {
		return new DmzService(indexService);
	}

	@Bean
	public OrderService orderService() {
		DmzService dmzService = dmzService(null);
		return dmzService.createOrder();
	}
}


@Component
public class IndexService {
}

public class DmzService {
	public DmzService(IndexService indexService) {

	}

	public OrderService createOrder() {
		return new OrderService();
	}
}

public class OrderService {
}


這種情況下,我們在orderService()爲了得到當前容器中的dmzService調用了對應的BeanMethod,但是按照方法的定義我們不得不傳入一個參數,但是實際上我們知道BeanMethod等價於getBean,所以上面這段代碼可以等價於

@Configuration
public class AnotherConfig {
	
	@Autowired
	ApplicationContext applicationContext;
	
	@Bean
	public DmzService dmzService(IndexService indexService) {
		return new DmzService(indexService);
	}

	@Bean
	public OrderService orderService() {
		DmzService dmzService = (DmzService) applicationContext.getBean("dmzService");
		return dmzService.createOrder();
	}
}



對於getBean而言,傳入參數跟不傳參數在創建Bean時是有區別的,但是創建後從容器中獲取Bean時跟傳入的參數沒有一毛錢關係(單例情況),因爲這是從緩存中獲取嘛。也就是說單例下,傳入的參數只會影響第一次創建。正因爲如此,getBean在單純的做獲取的時候不需要參數,那就意味着beanMthod在獲取Bean的時候也可以不傳入參數嘛,但是beanMthod作爲一個方法又定義了形參,Spring就說,這種情況你就傳個null吧,反正我知道要去getBean,當然,這只是筆者的個人理解。

4、結合Spring整體對ConfigurationClassEnhancer相關源碼分析總結

4.1、Bean工廠後置處理器修改bd,對應enhance方法

執行流程

修改bd的整個過程都發生在Bean工廠後置處理器的執行邏輯中

在這裏插入圖片描述

執行邏輯

在這裏插入圖片描述

在上文中我們已經知道了,在執行bean工廠後置處理器前,Spring容器的狀態如下:

1

那麼執行完成Bean工廠後置處理器後(不考慮程序員自定義的後置處理器),容器的狀態應該是這樣的2

4.2、BeanFactoryAwareMethodInterceptor

執行流程

在容器中的bd就緒後,Spring會通過bd來創建Bean了,會先創建配置類,然後創建配置類中beanMethod定義的bean。在創建配置類的過程中在初始化Bean時,如果實現了Aware接口,會調用對於的setXxx方法,具體代碼位於org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean

3

在調用setBeanFactory方法時,會被攔截,進入到攔截器的邏輯中

執行邏輯

在這裏插入圖片描述

4.3、BeanMethodInterceptor

執行流程

以下面這段代碼爲例:

@Configuration
public class AnotherConfig {
	@Bean
	public DmzService dmzService(){
		return new DmzService();
	}

	@Bean
	public OrderService orderService(){
		return new OrderService(dmzService());
	}
}

Spring會根據beanMethod在配置類中定義順序來創建Bean,所以上面這段配置會先創建dmzServcice,之後在創建orderService

那麼BeanMethodInterceptor的攔截將會發生在兩個地方

  1. 直接創建dmzService的過程中,攔截的是dmzService()方法
  2. 創建orderService過程中,第一次攔截的是orderService()方法
  3. orderService()方法調用了dmzService()方法,dmzService()方法又被攔截

在直接創建dmzService時,由於isCurrentlyInvokedFactoryMethod(beanMethod)這句代碼會成立,所以會直接調用目標類的方法,也就是cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs),就是我們在配置類中定義的dmzService()方法,通過這個方法返回一個dmzService

而創建orderService時,方法的調用就略顯複雜,首先它類似於上面的直接創建dmzService的流程,orderService()方法會被攔截,但是由於正在執行的方法就是orderService()方法,所以orderService()也會被直接調用。但是orderService()中又調用了dmzService()方法,dmzService()方法又被攔截了,此時orderService()還沒被執行完成,也就是說正在執行的方法是orderService()方法,所以isCurrentlyInvokedFactoryMethod(beanMethod)這句代碼就不成立了,那麼就會進入org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#resolveBeanReference這個方法的邏輯中,在這個方法中,最終又通過getBean方法來獲取dmzService,因爲dmzService之前已經被創建過了,所以在單例模式下,就直接從單例池中返回了,而不會再次調用我們在配置類中定義的dmzService()方法。

執行邏輯

在這裏插入圖片描述

總結

這裏就在上篇文章的基礎上對流程圖再做一次完善吧,因爲圖片太大了,就放個鏈接~

Spring創建bean前的執行流程

碼字不易,要是覺得對你有幫助的話,記得點個贊吧~!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章