@Configuration与@Component有什么区别?

环境:Spring6.1.2

1. 简介

@Configuration@Component在Spring框架中各自具有不同的作用和使用场景。

@Component是Spring中的一个通用注解,用于标识一个类作为组件。它告诉Spring这是一个普通的组件类,应该被Spring容器管理。当Spring启动时,它会扫描应用程序的类路径,并自动检测带有@Component注解的类。这些类随后会被Spring容器实例化并注册为Bean。

@Configuration用于标识配置类,这些类不同于普通的组件类,它主要包含了Bean的定义。在配置类中,可以使用@Bean注解来定义Bean的创建方法。Spring容器在启动时,会调用这些带有@Bean注解的方法,并将返回的对象注册为Bean。通过@Configuration,可以更加灵活和细粒度地控制Bean的创建和配置,从而实现更复杂的依赖注入和组件装配。

2. 两者区别

2.1 环境准备


public class PersonDAO {
}

public class PersonService{
}

public class AppConfig {
  @Bean
  public PersonDAO personDAO() {
    return new PersonDAO() ;
  }
  @Bean
  public PersonService personService() {
    return new PersonService() ;
  }
}

以上会基于上面的几个类进行测试

2.2 区别1

AppConfig使用@Configuration注解


@Configuration
public class AppConfig {
  // ...
}
// 测试代码
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class)) {
  System.out.println(context.getBean(AppConfig.class)) ;
  System.out.println(context.getBean(PersonDAO.class)) ;
}

输出结果

com.pack.AppConfig$$SpringCGLIB$$0@17f7cd29com.pack.PersonDAO@7d8704ef

AppConfig是代理类。

AppConfig使用@Component注解


@Component
static class AppConfig {
  // ...
}

输出结果​​​​​​​

com.pack.AppConfig@659a969bcom.pack.PersonDAO@76908cc0

 

总结:@Configuration与@Component注解的类中都可以使用@Bean定义方法bean。但使用@Configuration注解的这个类本身会被Spring容器创建为代理类。

2.3 区别2

我们在AppConfig配置类中@Bean方法互相调用。

AppConfig使用@Configuration注解


@Configuration
static class AppConfig {
  // 在personDAO方法中调用PersonService()方法
  @Bean
  PersonDAO personDAO() {
    PersonService ps = personService() ;
    System.out.printf("在@Bean方法中获取PersonService: %s%n", ps) ;
    return new PersonDAO() ;
  }
  @Bean
  PersonService personService() {
    return new PersonService() ;
  }
}
// 测试代码
AnnotationClassApplicationContext context = ...
System.out.printf("获取PersonService: %s%n", context.getBean(PersonService.class)) ;

输出结果​​​​​​​

在@Bean方法中获取PersonService: com.pack.PersonService@2d1ef81a获取PersonService: com.pack.PersonService@2d1ef81a

获取的同一个PersonService对象,这保证了容器中PersonService始终是同一个对象。

AppConfig使用@Component注解

将上面示例AppConfig注解改为@Component后

输出结果​​​​​​​

在@Bean方法中获取PersonService: com.pack.PersonService@3901d134获取PersonService: com.pack.PersonService@4c9f8c13

获取的是不同的对象,这就造成了问题,Spring容器中存在多个不同的实例,如果这里的调用的是DataSource数据源Bean的方法,那么就出大问题了,很可能你的事务都将不会生效等一些问题。

修改@Configuration注解属性


@Configuration(proxyBeanMethods = false)
static class AppConfig {}

将proxyBeanMethods属性设置为false,再次运行上面的代码,输出结果​​​​​​​

在@Bean方法中获取PersonService: com.pack.PersonService@7b227d8d获取PersonService: com.pack.PersonService@3ba9ad43

PersonService是不同的对象与@Component没有区别。

以上是@Configuration与@Component的区别,接下来通过源码讲解@Configuration到底做了什么。

3. @Configuration原理

首先,我们的知道该注解的处理器是ConfigurationClassPostProcessor这是一个Bean工厂处理器。在该处理器中会对@Configuration注解进行处理。


public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
  // 1. 第一步,在容器初始化时,该方法会先执行
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // ...
    processConfigBeanDefinitions(registry);
  }
  public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();
    // 遍历当前容器中注册的所有bean
    for (String beanName : candidateNames) {
      // 初始肯定没有值(除非你手动注册BeanDefinition对象设置了该属性)
      if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
      } 
      // 判断当前的bean,返回为true后添加到集合中
      else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
        configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
      }
      // 
      return true ;
    }
  }
  // 2. 第二步,对BeanDefinition进行增强配置
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // ...
    // 增强配置类
    enhanceConfigurationClasses(beanFactory);
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
  }
  // 增强配置类
  public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    // 遍历所有的BeanDefinition对象
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      // 这里获取当前BeanDefinition对象在第一步中设置的full还是lite
      Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
      // ...
      // 判断属性值是否为full
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
        // 添加到集合中
        configBeanDefs.put(beanName, abd);
      }
    }
    // 遍历找到的所有配置类属性为full的
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      // 获取原始class对象      
      Class<?> configClass = beanDef.getBeanClass();
      // 创建代理类
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
        // 将原始的class修改为代理类
        beanDef.setBeanClass(enhancedClass);
      }
    }
  }
}

ConfigurationClassUtils工具类


public abstract class ConfigurationClassUtils {
  static boolean checkConfigurationClassCandidate(
    BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
    // 这里简化了源代码
    AnnotationMetadata metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata() ; 
    // ...
    // 获取@Configuration注解的所有属性
    Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
    // 判断proxyBeanMethods属性是否为true,默认值为true
    if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
      // 那么为当前beandefinition配置该属性;设置为full
      beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    }
    // 否则设置为lite
    else if (config != null || Boolean.TRUE.equals(beanDef.getAttribute(CANDIDATE_ATTRIBUTE)) ||
        isConfigurationCandidate(metadata)) {
      beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    }
  }
}

在上面通过ConfigurationClassEnhancer类创建代理类,既然是代理,那么就一定有相应的拦截器


class ConfigurationClassEnhancer {
  // 这里我们就看下该静态属性即可明白
  static final Callback[] CALLBACKS = new Callback[] {
      // @Bean方法的拦截
      new BeanMethodInterceptor(),
      new BeanFactoryAwareMethodInterceptor(),
      NoOp.INSTANCE
  };
}

BeanMethodInterceptor拦截器,会在每次你调用方法的时候都会拦截从BeanFactory中获取你要Bean对象(根据方法名,及方法返回值)。

到此,我们已经分析完了@Configuration注解的类为什么在内部方法调用时始终能保证是同一个对象的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

missterzy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值