源码层面理解@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
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值