Spring之@Bean方法

@Bean方法解析

一、bean实例化的三种方式

Spring中的一个Bean,需要实例化得到一个对象,而实例化就需要用到构造方法。

  • 构造方法实例化,默认的:让Spring调用bean的构造方法,生成bean实例对象给我们
  • 工厂静态方法实例化:让Spring调用一个工厂类的静态方法,得到一个bean实例对象
  • 工厂非静态方法实例化(实例化方法):让Spring调用一个工厂对象的非静态方法,得到一个bean实例对象

1、构造方法实例化

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>

2、工厂静态方法实例化

  • 工厂类如下:com.guang.factory.StaticFactory
public class StaticFactory{
    public static UserDao createUserDao(){
        return new UserDaoImpl();
    }
}
  • 配置如下:
<bean id="userDao" class="com.itheima.factory.StaticFactory" 
      factory-method="createUserDao"></bean>

表示的是调用StaticFactory类中的createUserDao方法来创建出来一个bean对象。

3、工厂非静态方法实例化

  • 工厂类如下:com.guang.factory.InstanceFactory
public class InstanceFactory{
    public UserDao createUserDao(){
        return new UserDaoImpl();
    }
}
  • 配置如下:
<!-- 先配置工厂 -->
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>

<!-- 再配置UserDao -->
<bean id="userDao" factory-bean="instanceFactory" factory-method="createUserDao"></bean>

表示的是利用instanceFactory对象调用createUserDao方法来创建出来一个bean对象。

对于工厂的静态方法和非静态方法不止只有xml的使用方式,还有对应的java注解版本控制。

二、注解版本

可以使用这种方式来进行配置,在spring容器中也可以生成对应的Bean对象。

三、源码解析流程

3.1、入口

在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry中会来解析配置类,在对于@Bean方法所在的类来说,也是一种配置类。

在org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass中有下面几行代码来解析@Bean方法的

// 解析配置类中的@Bean,但并没有真正处理@Bean,只是暂时找出来
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
  configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

首先找到配置类中加了@Bean的方法,添加遍历循环,添加到ConfigurationClass的beanMethods属性中来,并将当前方法创建出来一个新的BeanMethod对象来进行保存。BeanMethod表示的是创建bean的方法。

那么具体的解析在外面:

// 可能会生成很多的BD,spring还得去检查有没有配置类
this.reader.loadBeanDefinitions(configClasses);

开始来解析加了@Bean方法的配置类

从保存在configClass中beanMethods属性集合中获取得到,然后来进行遍历每个@Bean方法进行解析

for (BeanMethod beanMethod : configClass.getBeanMethods()) {
  loadBeanDefinitionsForBeanMethod(beanMethod);
}

下面重点就是loadBeanDefinitionsForBeanMethod如果来对@Bean注解来进行解析的。

3.2、常用注解解析

那么这里就需要涉及到@Bean方法的一些常用属性,这些属性肯定是要来进行解析的。所以spring就是要先来做这些事情的。

public @interface Bean {
  // 当前bean对象的别名
  @AliasFor("nme")
  String[] value() default {};
  @AliasFor("value")
  String[] name() default {};
  // 当前bean是否能够成为一个候选的bean对象,跟依赖注入阶段相关
  boolean autowireCandidate() default true;
  // 初始化方法
  String initMethod() default "";
  // 销毁方法。有默认值,一般不用
  String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}

3.3、是否有static关键字的判断

// 创建@Bean注解对应的BeanDefinition
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
// 如果是statci修饰的
if (metadata.isStatic()) {
  // static @Bean method
  if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
    beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
  }
  else {
    // 设置当前配置类的全限定类名 当前为String,如:com.guang.config.MyAppConfig
    beanDef.setBeanClassName(configClass.getMetadata().getClassName());
  }
  // 设置唯一的method为当前的方法名称,如:userService
  beanDef.setUniqueFactoryMethodName(methodName);

  // 即调用com.guang.config.MyAppConfig类中的静态方法userService来创建对象
}
else {
  // 不是静态的,会执行到这里来
  // instance @Bean method
  // myAppConfig
  beanDef.setFactoryBeanName(configClass.getBeanName());
  // 设置唯一的method为当前的方法名称,如:userService
  beanDef.setUniqueFactoryMethodName(methodName);

  // 即调用myAppConfig的userService方法来创建对象
}

3.4、@Bean对应的方法是否是唯一的

注意:在setUniqueFactoryMethodName方法中,如下设置:

public void setUniqueFactoryMethodName(String name) {
  Assert.hasText(name, "Factory method name must not be empty");
  setFactoryMethodName(name);
  // @Bean对应的方法是否是独一无二的。如果只有一个,那么为true;如果存在多个,那么为false
  this.isFactoryMethodUnique = true;
}

什么意思呢?对应的代码如下所示:

@Bean
public  UserService userService(){
  return new UserService();
}

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

因为两个方法是重载,而且配置在同一个配置类中,那么spring最终会通过哪个配置方法来进行创建对象呢?

首先判断当前的BeanDefinitionMap中有几个UserService对应的Map?答案是只有一个

因为有一行代码判断:

// 如果出现了两个@Bean修改的方法名字一样(比如方法重载了),则直接return,并且会把已经存在的BeanDefinition的isFactoryMethodUnique为false
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
  // 如果beanName等于"appConfig",就会抛异常
  if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
    throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
"' clashes with bean name for containing configuration class; please make those names unique!");
  }
  return;
}

那么看下如何来判断重载的?

但是一般来说,这种情况很少见的。这种情况都是比较生僻的方式。

在方法命名的时候,通常都是采用不同的方法名称来进行命名的。如下所示:

3.5、额外属性设置

AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

3.6、注册BeanDefinition

this.registry.registerBeanDefinition(beanName, beanDefToRegister);

3.7、实例化过程

在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中的进行实例化的过程中

来到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance方法中来

// @Bean对应的BeanDefinition
// 在解析的时候来进行处理的
if (mbd.getFactoryMethodName() != null) {
  return instantiateUsingFactoryMethod(beanName, mbd, args);
}

最终这里会拿到对应的@Configuration所在的类,

1、如果是static关键字修饰的@Bean,会拿到对应的Class(CGLIB代理类);

2、如果是非static关键字修饰的@Bean,会拿到对应的bean对象和对应的Class(CGLIB代理类);

然后根据代理类获取得到被代理的类:

factoryClass = ClassUtils.getUserClass(factoryClass);

然后找到类中的@Bean方法

// 根据BD中的属性isFactoryMethodUnique判断@Bean对应的方法是不是唯一的
if (mbd.isFactoryMethodUnique) {
  if (factoryMethodToUse == null) {
    factoryMethodToUse = mbd.getResolvedFactoryMethod();
  }
  if (factoryMethodToUse != null) {
    candidates = Collections.singletonList(factoryMethodToUse);
  }
}

然后找到对应的方法开始来进行执行:

然后来执行对应的方法

然后就来到了@Configuration中最关键的一个环节:

正常来说,这里执行完了对应的方法之后,就会返回对应的result结果。

但是这里能够解决的问题是:

能够保证使用的是同一个OrderService对象,就是利用上面源码中的ThreadLocal来进行保证的。

posted @ 2023-02-13 22:36  写的代码很烂  阅读(1398)  评论(0编辑  收藏  举报