详解配置类为什么要添加@Configuration注解

不加@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上给出的这一段注释

首先,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目录,会发现多了以下几个文件

其中第二个文件就是我们的代理类字节码,将其直接用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时会注入一个代理对象。

端点调试,也确实如我们所料,这个地方注入的确实是一个代理对象,因为我们在配置类上申明了proxyMode = ScopedProxyMode.TARGET_CLASS,所以这里是一个cglib的代理对象。

使用dmzService的时候,这个时候使用的应该是实际的目标对象。所以按照我们的分析应该通过getBean(targetBeanName)的方式来获取到这个Bean,执行流程应该是代理对象cglibDmzService调用了toString方法,然后调用getBean,getBean要根据BeanDefinition创建Bean,而根据BeanDefinition的定义,需要使用配置类中的BeanMethod来创建Bean,所以此时会进入到BeanMethodInterceptor的intecept方法。

我们直接在intecept方法中进行断点,会发现此时的调用栈如下

  • 打印时,调用了toString方法
  • 实际将会去创建目标Bean,所以此时getBean时对应的BeanName为targetBeanName(“scopedTarget.”+beanName)
  • 在getBean时根据BeanDefinition的定义会通过执行配置类中的beanMethod方法来创建Bean
  • 最终就进入了拦截器中这个方法

这种情况下就会进入到下面这段代码的逻辑中

// 判断这个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容器的状态如下:

那么执行完成Bean工厂后置处理器后(不考虑程序员自定义的后置处理器),容器的状态应该是这样的

4.2、BeanFactoryAwareMethodInterceptor执行流程

在容器中的bd就绪后,Spring会通过bd来创建Bean了,会先创建配置类,然后创建配置类中beanMethod定义的bean。在创建配置类的过程中在初始化Bean时,如果实现了Aware接口,会调用对于的setXxx方法,具体代码位于org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean

在调用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的拦截将会发生在两个地方

  • 直接创建dmzService的过程中,拦截的是dmzService()方法
  • 创建orderService过程中,第一次拦截的是orderService()方法
  • 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前的执行流程

到此这篇关于详解配置类为什么要添加@Configuration注解 的文章就介绍到这了,更多相关配置类添加@Configuration注解 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring @Configuration注解及配置方法

    Spring @Configuration注解 Spring3.0开始,@Configuration用于定义配置类,定义的配置类可以替换xml文件,一般和@Bean注解联合使用. @Configuration注解主要标注在某个类上,相当于xml配置文件中的<beans> @Bean注解主要标注在某个方法上,相当于xml配置文件中的<bean> 等价于 注意:@Configuration注解的配置类有如下要求: @Configuration不可以是final类型: @Configur

  • Springboot @Configuration @bean注解作用解析

    这篇文章主要介绍了springboot @Configuration @bean注解作用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 @Configuration注解可以达到在Spring中使用xml配置文件的作用 @Bean就等同于xml配置文件中的<bean> 在spring项目中我们集成第三方的框架如shiro会在spring.xml配置文件中进行配置,例如: <!-- 配置shiro框架提供过滤器工厂 --> <

  • 详解配置类为什么要添加@Configuration注解

    不加@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 D

  • 详解Angular5/Angular6项目如何添加热更新(HMR)功能

    本文介绍了详解Angular5/Angular6项目如何添加热更新(HMR)功能,分享给大家,具体如下: A:什么是HMR? Q:HMR(热替换)用于在运行的应用程序中更新代码而不需要重建它.这将导致更快的更新和更少的全页重新加载. angular6-hmr 提供angular6以上HMR(热更新)功能 步骤 1.进入angular项目父级目录内 git clone https://github.com/staven630/angular6-hmr angular6-hmr目录与angular项

  • 详解配置 Apache 服务器支持 PHP 文件的解析

    详解配置 Apache 服务器支持 PHP 文件的解析 [说明] 1. 本例中 Apache 版本为 httpd-2.4.20-x64-vc14 ,安装路径为 E:\Apache24 2. PHP 版本为 php-5.5.34-Win32-VC11-x64 ,安装路径为 E:\php-5.5.34 [下载] 登录 http://php.NET/downloads.php 下载 PHP,由于我要把它跟 Apache 集成,所以我这里下载的是 Thread Safe 版本: [安装] 1. 解压下载

  • 详解Python类和对象内容

    目录 一.什么是Python类? 二.Python类中的方法和属性 2.1.Python类中的方法 2.2.Python类中的属性 三.面向对象的概念 3.1.Python类:继承 3.2.Python类:多态性 3.3.Python类:抽象 一.什么是Python类? python中的类是创建特定对象的蓝图.它使您可以以特定方式构建软件.问题来了,怎么办?类允许我们以一种易于重用的方式对我们的数据和函数进行逻辑分组,并在需要时进行构建.考虑下图. 在第一张图片(A)中,它代表了一个可以被视为C

  • 详解微信小程序 template添加绑定事件

    详解微信小程序 template添加绑定事件 对于模板的使用,我是想将模板的事件单独出来,其他引用模板的页面中不再掺杂模板事件,比较方便管理,如果还有其他好的解决办法, 请赐教. template.wxml <view bindtap="clickView" class="tempClass">temp模板</view> template.js var temp = { clickView: function () { console.log

  • 详解Java 类的加载、连接和初始化

    系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类.本节将会详细介绍类加载.连接和初始化过程中的每个细节. JVM 和类 当调用 java 命令运行某个 Java 程序时,该命令将会启动一个 Java 虚拟机进程,不管该 Java 程序有多么复杂,该程序启动了多少个线程,它们都处于该 Java 虚拟机进程里.正如前面介绍的,同一个 JVM 的所有线程.所有变量都处于同一个进程里,它们都使用该 JVM 进程的内存区.当系统出现以下几种情况时,JVM 进程将被终止. 程序运行到最

  • 详解Java 类的加载机制

    一.类的加载机制 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接

  • 详解mysql 使用left join添加where条件的问题分析

    当前需求: 有group和factor两张表,一个group对应多个factor,现在想查询有效的group和对应的有效的factor,两个表都有isDel逻辑删除标志. 最开始的错误写法一: SELECT g.*,f.* FROM groups g LEFT JOIN factor f ON f.groupId = g.id where g.isDel=0 and f.isDel=0 LEFT JOIN 关键字会从左表 (table_name1) 那里返回所有的行,即使在右表 (table_n

  • 详解Java类动态加载和热替换

    前言 最近,遇到了两个和Java类的加载和卸载相关的问题: 1) 是一道关于Java的判断题:一个类被首次加载后,会长期留驻JVM,直到JVM退出.这个说法,是不是正确的? 2) 在开发的一个集成平台中,需要集成类似接口的多种工具,并且工具可能会有新增,同时在不同的环境部署会有裁剪(例如对外提供服务的应用,不能提供特定的采购的工具),如何才能更好地实现? 针对上面的第2点,我们采用Java插件化开发实现.上面的两个问题,都和Java的类加载和热替换机制有关. 1. Java的类加载器和双亲委派模

  • Java并发编程之详解ConcurrentHashMap类

    前言 由于Java程序员常用的HashMap的操作方法不是同步的,所以在多线程环境下会导致存取操作数据不一致的问题,Map接口的另一个实现类Hashtable 虽然是线程安全的,但是在多线程下执行效率很低.为了解决这个问题,在java 1.5版本中引入了线程安全的集合类ConcurrentMap. java.util.concurrent.ConcurrentMap接口是Java集合类框架提供的线程安全的map,这意味着多线程同时访问它,不会影响map中每一条数据的一致性.ConcurrentM

随机推荐