配置类为什么要添加@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上给出的这一段注释

首先,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方法中进行断点,会发现此时的调用栈如下

  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容器的状态如下:

那么执行完成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的拦截将会发生在两个地方

  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前的执行流程

码字不易,要是觉得对你有帮助的话,记得点个赞吧~!

配置类为什么要添加@Configuration注解呢?的更多相关文章

  1. 你知道Spring是怎么解析配置类的吗?

    彻底读懂Spring(二)你知道Spring是怎么解析配置类的吗? 推荐阅读: Spring官网阅读系列 彻底读懂Spring(一)读源码,我们可以从第一行读起 Spring执行流程图如下: 如果图片 ...

  2. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  3. Spring源码解析 – @Configuration配置类及注解Bean的解析

    在分析Spring 容器创建过程时,我们知道容器默认会加载一些后置处理器PostPRocessor,以AnnotationConfigApplicationContext为例,在构造函数中初始化rea ...

  4. spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理

    @Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...

  5. 【Spring】简述@Configuration配置类注册BeanDefinition到Spring容器的过程

    概述 本文以SpringBoot应用为基础,尝试分析基于注解@Configuration的配置类是如何向Spring容器注册BeanDefinition的过程 其中主要分析了 Configuratio ...

  6. 配置类需要标注@Configuration却不知原因?那这次就不能给你涨薪喽

    专注Java领域分享.成长,拒绝浅尝辄止.关注公众号[BAT的乌托邦]开启专栏式学习,拒绝浅尝辄止.本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈.My ...

  7. springboot情操陶冶-@Configuration注解解析

    承接前文springboot情操陶冶-SpringApplication(二),本文将在前文的基础上分析下@Configuration注解是如何一步一步被解析的 @Configuration 如果要了 ...

  8. Spring boot 配置文件参数映射到配置类属性

    [参考文章]:SpringBoot之@EnableConfigurationProperties分析 [参考文章]:在Spring Boot中使用 @ConfigurationProperties 注 ...

  9. spring 配置 Java配置类装配bean

    https://www.cnblogs.com/chenbenbuyi/p/8457700.html 自动化装配的确有很大的便利性,但是却并不能适用在所有的应用场景,比如需要装配的组件类不是由自己的应 ...

随机推荐

  1. python与excel的关系;铁打的python流水的excel

    现在很多行业,都离不开用Excel: 做财务的,要用Excel做报表:做物流的,会用Excel来跟踪订单情况:做HR的,会用Excel算工资:做分析的,会用Excel计算数据做报表.不知道你有没有这样 ...

  2. Chrome插件安利!可以一键导出微信读书笔记|支持Markdown等三种格式

    众所周知,微信读书App 是一款非常优秀的阅读类App ,周围也有不少人在用.虽然工作比较忙.但是也没少在上面看书做笔记. 美中不足的是,目前微信读书虽然支持笔记导出,但是提供的是将笔记复制到剪切板, ...

  3. 漫谈LiteOS之开发板-LiteOS移植(基于GD32450i-EVAL)

    1 为什么移植? 嵌入式设备的芯片型号和外设的差异较大,资源有限.而RTOS无法适配集成所有的驱动,因此会先适配部分开发板,然后通过移植使得适配更多的开发板. 可移植性是嵌入式操作系统与普通操作系统的 ...

  4. 6.表单提交,input键盘变搜索,有关自定义属性input操作

    1.键盘变搜索 1.) 在form 上加action="#", 2.)input type=search, 3.)此时会提交到 #,需要再添加一个input display=non ...

  5. 乱 七 八 糟 $(n.)$

    \(2020/4/22\) 今天常规作业还是太慢了,白天似乎已经抓紧了,但总还能挤出时间来的.八点钟了还有物理和英语作业,回去又得很晚睡. 还是容易开小差,不过回忆了一下,今天化学课还是太懒散,其余的 ...

  6. Linux系统安装docker教程-CentOS7(完美教程)

     一.前言 最近有网友反应不在安装Linux 安装docker,为了方便大家更快的安装,以CentOS7安装为例,写了一篇比较简单的博客,让大家学习. 二.背景介绍 Linux,全称GNU/Linux ...

  7. Makefile 中引用多个 include 路径

    LIB=-L/usr/informix/lib/c++ INC=-I/usr/informix/incl/c++ -I/opt/informix/incl/public default: main m ...

  8. P1458 顺序的分数 Ordered Fractions(有技巧的枚举)+C++类封装=精简代码

    题目描述 输入一个自然数N,对于一个最简分数a/b(分子和分母互质的分数),满足1<=b<=N,0<=a/b<=1,请找出所有满足条件的分数. 这有一个例子,当N=5时,所有解 ...

  9. python selenium(用例断言)

    1.if ...else ...判断进行断言 from time import * from selenium import webdriver "): driver = webdriver ...

  10. 使用Jexus 容器化您的 Blazor 应用程序

    在本文中,我们将介绍如何将 Blazor 应用程序放入Jexus 容器以进行开发和部署.我们将使用 .NET Core  CLI,因此无论平台如何,使用的命令都将是相同的. Blazor 托管模型 B ...