源码层面理解@Component和@Configuration

相信看见这篇文章的小伙伴都使用过Spring的@Component和@Configuration注解。这里就不详细讲解它俩的使用方法了,下面我们先看一下示列:

A.class代码

public class A {
	public A(){
		System.out.println("init A");
	}
}

B.class代码

public class B {
	public B(){
		System.out.println("init B");
	}
}

使用@Configuration注解时ZKConfig.class代码

@Configuration
public class ZkConfig {

	@Bean
	public A a() {
		return new A();
	}

	@Bean
	public B b() {
		a();//调用a()方法获取a
		return new B();
	}

	public static void main(String... args) {
		AnnotationConfigApplicationContext ac = new 
              AnnotationConfigApplicationContext(ZkConfig.class);

	}

}

输出结果:
   init A
   init B

可以看见init A只输出了一次表示A只被实列化了一次,单从代码层面看A对象应该被实列化两次,一次是spring容器创建a这个bean时会调用方法a(),另一次时spring容器创建b这个bean时调方法b()时,在调用方法a();但很明显肯定有一次调用方法a()时没有执行new A();这是什么原因呢?等一下在分析,接着看

使用@Component注解时ZKConfig.class代码

@Component
public class ZkConfig {

	@Bean
	public A a() {
		return new A();
	}

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

	public static void main(String... args) {
		AnnotationConfigApplicationContext ac = new 
          AnnotationConfigApplicationContext(ZkConfig.class);

	}

}
输出结果:   
init A
init A
init B

这次结果很明显对象A被实列化了两次。

为什么会出现这种差异呢?当我们通过代码层面观看到执行结果和代码逻辑不一致时,可能我们会很自然的想到代码被修改了,那通过什么方式修改的呢,没错就是动态代理,spring框架采用的就是cglib动态代理。至于cglib如何使用笔者在这里就不详细讲解了,如果不了解cglib的可先了解一下cglib的基本使用,在往下看。

加@Configuration注解的ZkConfig对象的打印结果:com.config.ZkConfig$$EnhancerBySpringCGLIB$$8d6752c3@279ad2e3

加@Component注解的ZkConfig对象的打印结果:com.config.ZkConfig@76a3e297

 接下来我们就来分析这段增强逻辑

   spring容器在创建的时候会往IOC容器中注入一个ConfigurationClassPostProcess,我们可以理解为这是spring内置的bean工厂后置处理器,主要作用时用来扫描我们自己创建的bean,将这些bean封装成BeanDefinition,放入容器中,spring在通过BeanDefinition创建bean,若原来没有了解过spring源码的小伙伴,这里可能会看不懂,先跳过,不要纠结。
   紧接着spring会执行核心方法refresh()--->invokeBeanFactoryPostProcessors(beanFactory);这里就是执行bean工厂的后置处理器,在ConfigurationClassPostProcess中有一个postProcessBeanFactory()方法,代理的入口就是在这里。

下面只是笔者认为的spring的几行关键代码,不是全部

   //遍历所有beanName
  for (String beanName : beanFactory.getBeanDefinitionNames()) {
	 BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
   	 Object configClassAttr = 
        beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
     if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
      	configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
    } 
}

//获取代理后的class对象
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
//替换beanDefinition原来的beanClass属性为代理Class
beanDef.setBeanClass(enhancedClass);

   1.遍历容器中的所有BeanDefinitionName;

   2.通过name拿去BeanDefinition;

  3.获取BeanDefinition的属性configurationClass,若加了@Configuration且里面的属性      proxyBeanMethods=true,那么这个属性为full,否者为lite或者null,所以我们普遍认为加了@Configuration注解的类,就是全注解类;而@Component注解的属性为lite,所以不会走增强逻辑

4.若这个属性值为full,那么这个beanDefinition将会被加入到一个集合中,这个集合代表的意思就是这个类需要增强;

5.通过cglib获取代理后的class,这个 enhancer.enhance()方法就是cglib的逻辑,读者可自行通过spring源码观看;

6.并替换原来的beanClass属性。

了解动态代理的小伙伴应该,当我们通过代理对象调用目标方法时,会进行代理逻辑,cglib也是如此,cglib回调用自己的CallBack回调函数,spring在这里创建了三个回调函数

	private static final Callback[] CALLBACKS = new Callback[]{
			new BeanMethodInterceptor(),
			new BeanFactoryAwareMethodInterceptor(),
			NoOp.INSTANCE
	};

我们只需要关注BeanMethodInterceptor这个CallBack函数。此时调用就会进入 intercept()拦截方法。

/*
 *enhancedConfigInstance:代理对象
 *beanMethod:被代理方法
 *beanMethodArgs:方法参数
 *cglibMethodProxy :代理方法
 */
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] 
   beanMethodArgs,	MethodProxy cglibMethodProxy) throws Throwable {
        if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
				
	return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
   }
}

1.isCurrentlyInvokedFactoryMethod(beanMethod)这个方法看名字翻译过来就是是否为正在执行的工厂方法,首先我们了解一下通过@Bean注解创建bean的逻辑,我们以ZkConfig中a()方法为列;

通过上面的知识我们了解到spring创建一个bean大多数情况下都会先为其创建一个beanDefinition

@Bean也不列外,只是通过@bean创建的beanDefinition不会有beanClss属性,但会有另外两个属性,factoryBeanName和factoryMethodName,我们以a()方法为列,它的(factoryMethodName=zkConfig,factoryMethodName=a),看属性我们就可以知道@Bean是通过工厂方法创建的,当在创建时会将这个方法加入到一个ThreadLocal中


	private static final ThreadLocal<Method> currentlyInvokedFactoryMethod = new ThreadLocal<>();
//将执行的工厂方法放入线程上下文中
currentlyInvokedFactoryMethod.set(factoryMethod);

当代码执行到isCurrentlyInvokedFactoryMethod(beanMethod)这个方法时,会与我们上下文此时保存的factoryMethod比较判读是不是同一个,且名字是否相同,若返回为true则会执行
cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);这段代码的意思就是

执行父类的方法,父类就是目标类

2.注意这里方法参设传入的参数是代理对象而不是目标对象,所以执行父类方法时,若父类方法中调用了这个类中的其它方法时,其它方法也会进入代理逻辑,如在b()方法中执行a()方法,a方法也会走代理逻辑,这和我们熟悉的spring事务失效的原因之一:方法调用方法不走切面不同,所以这里b()再次调用a()时,a也会进切面。

3.这里就会返回new A()执行A对象的构造方法逻辑打印“init A”,并将a放入spring容器的单列池中。

但执行创建b这个bean时,也是如此,先将methodB放入上下文中,执行b()方法,进入代理逻辑,先判断自己是否是正在执行的工厂方法,返回true ,此时执行父类的方法

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

进入代理逻辑,先判断自己是否是正在执行的工厂方法,返回true,此时执行父类的方法

调用a(),也会进行代理逻辑此时在执行 isCurrentlyInvokedFactoryMethod(beanMethod)) 方法时会返回false,因为此时上下文存储的还是方法b但是我们此时正在执行的是方法a,那么此时就会执行这个方法 return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);

这个方法主要会执行

Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
      beanFactory.getBean(beanName));

此时是从调用的getBean从容器中那bean,而不是执行a()方法new出一个对象,所以A只会实列化一次

结论: 加了@Configuration注解的类,spring认为这是一个全注解类,若存在方法调用方法且方法都是通过@Bean修饰的,那么方法内部的方法调用就是走spring的 getBean()逻辑,从容器中获取一个bean,而不是直接调用方法创建

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
@Configuration码中的具体内容是什么?它的作用是什么? 回答: @Configuration注解是Spring框架中的一个注解,用于标记一个类作为配置类。通过@Configuration注解,可以将该类中声明的bean对象纳入到Spring容器的管理中。@Configuration注解可以与@Component注解一起使用,但是它们之间仍然有一些不同之处。 @Configuration注解的作用是告诉Spring容器,这个类是一个配置类,里面可以包含@Bean注解的方法用于创建bean对象。当Spring容器启动时,会解析@Configuration注解,读取其中的@Bean方法,并将这些方法返回的对象注册到容器中。 @Configuration注解还可以与其他注解一起使用,例如@EnableAutoConfiguration和@ComponentScan等,用于进一步配置Spring应用程序。 具体的@Configuration码分析可以参考引用和引用中的文章,这些文章深入解析了@Configuration注解的原理和实现细节。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Spring中@Configuration码深度解析(一)](https://blog.csdn.net/qq_35634181/article/details/104062321)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [spring码:@Configuration码](https://blog.csdn.net/CPLASF_/article/details/106840449)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值