从源码角度分析Spring@Component和@Configuration到底有啥区别

我们都知道@Component和@Configuration都可以把一个类定义为一个Bean,这里我们不从使用场景分析想必大家都知道这两个注解怎么用(百度一搜都是在说一个标识组件bean一个标识配置bean)

我们来看一个有意思的例子,这里我定义了两个类ImComponent和ImConfiguration,他们分别有一个bean方法去定义一个Bean,然后从另一个方法中打印bean

java 复制代码
@Component
public class ImComponent {

    /**
     * 创建一个bean名为imBeanFromComponent
     * */
    @Bean
    public ImBean imBeanFromComponent(){
       return new ImBean();
    }

    public void printBean(){
       System.out.println("component-printBean:"+imBeanFromComponent());
    }

}
java 复制代码
@Configuration
public class ImConfiguration {

    /**
     * 定义一个bean名为imBeanFromConfiguration
     * */
    @Bean
    public ImBean imBeanFromConfiguration(){
       return new ImBean();
    }

    public void printBean(){
       System.out.println("configuration-printBean:"+imBeanFromConfiguration());
    }
}

接下来看这段代码,我们分别从容器中获取component、configuration和bean方法创建的两个bean,然后分别调用component的print和configuration的print

java 复制代码
public static void main(String[] args) {
    AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
    ImComponent component = context.getBean(ImComponent.class);
    ImConfiguration configuration = context.getBean(ImConfiguration.class);

    ImBean imBeanFromComponent = context.getBean("imBeanFromComponent", ImBean.class);
    System.out.println("imBeanFromComponent:"+imBeanFromComponent);
    ImBean imBeanFromConfiguration = context.getBean("imBeanFromConfiguration", ImBean.class);
    System.out.println("imBeanFromConfiguration:"+imBeanFromConfiguration);

    component.printBean();
    configuration.printBean();
}

打印结果为

我们可以看到component中的printBean调用bean方法直接创建了一个对象并没有使用到我们创建的bean,而configuration的printBean方法直接使用了我们创建的bean。 这里不难想肯定是Spring对@Configuration做了一些手脚,接下来我们再看

很明显@Configuration注解的Bean不单单是一个普普通通的bean,而是一个代理对象。 这里我们要引入一个概念Spring中的LITE配置类FULL配置类,来自于Spring中的源码

java 复制代码
//代码位置org.springframework.context.annotation.ConfigurationClassUtils#checkConfigurationClassCandidate

Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
// 存在@Configuration,并且proxyBeanMethods不为false(为true或为null)时,就是Full配置类
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
    beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 存在@Configuration,并且proxyBeanMethods为false时,是lite配置类
// 或者不存在@Configuration,但是只要存在@Component、@ComponentScan、@Import、@ImportResource四个中的一个,就是lite配置类
// 或者不存在@Configuration,只要存在@Bean注解了的方法,就是lite配置类
else if (config != null || isConfigurationCandidate(metadata)) {
    beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
    return false;
}

源码中我们可以看到对应@Configuration注解的类的元数据属性configurationClass 赋值了full

配置类加载时的代码

scss 复制代码
/**
 * Post-processes a BeanFactory in search of Configuration class BeanDefinitions;
 * any candidates are then enhanced by a {@link ConfigurationClassEnhancer}.
 * Candidate status is determined by BeanDefinition attribute metadata.
 * @see ConfigurationClassEnhancer
 */
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance");
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();

    // 遍历,找出配置类
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
       BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
       Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
       AnnotationMetadata annotationMetadata = null;
       MethodMetadata methodMetadata = null;
       if (beanDef instanceof AnnotatedBeanDefinition) {
          AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDef;
          annotationMetadata = annotatedBeanDefinition.getMetadata();
          methodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata();
       }
       if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
          // Configuration class (full or lite) or a configuration-derived @Bean method
          // -> eagerly resolve bean class at this point, unless it's a 'lite' configuration
          // or component class without @Bean methods.
          AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;

          // 没有加载类就去加载
          if (!abd.hasBeanClass()) {
             boolean liteConfigurationCandidateWithoutBeanMethods =
                   (ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&
                      annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
             if (!liteConfigurationCandidateWithoutBeanMethods) {
                try {
                   abd.resolveBeanClass(this.beanClassLoader);
                }
                catch (Throwable ex) {
                   throw new IllegalStateException(
                         "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
                }
             }
          }
       }

       //FULL类型的配置类,需要增强
       if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
          if (!(beanDef instanceof AbstractBeanDefinition)) {
             throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                   beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
          }
          else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
             logger.info("Cannot enhance @Configuration bean definition '" + beanName +
                   "' since its singleton instance has been created too early. The typical cause " +
                   "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                   "return type: Consider declaring such methods as 'static'.");
          }
          configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
       }
    }
    if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {
       // nothing to enhance -> return immediately
       enhanceConfigClasses.end();
       return;
    }

    // 生成代理类,并设置到BeanDefinition中  Full Config
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
       AbstractBeanDefinition beanDef = entry.getValue();
       // If a @Configuration class gets proxied, always proxy the target class
       beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
       // Set enhanced subclass of the user-specified bean class
       Class<?> configClass = beanDef.getBeanClass();  // Appconfig
       // 生成代理类
       Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
       if (configClass != enhancedClass) {
          if (logger.isTraceEnabled()) {
             logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                   "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
          }
          //修改增强配置类的BeanDefinition的beanclass属性为代理类
          beanDef.setBeanClass(enhancedClass);
       }
    }
    enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();
}

可以看到这时会判断BeanDefinition如果是FULL配置类,则直接修改beanClass为使用cglib代理的class,从而在实例化bean的时候使用的beanClass就已经不是我们原生的ImConfiguration.class了

接下来我们再看一下FULL配置类的增强代理逻辑

less 复制代码
/**
 * Intercepts the invocation of any {@link Bean}-annotated methods in order to ensure proper
 * handling of bean semantics such as scoping and AOP proxying.
 * @see Bean
 * @see ConfigurationClassEnhancer
 */
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

    /**
     * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
     * existence of this bean object.
     * @throws Throwable as a catch-all for any exception that may be thrown when invoking the
     * super implementation of the proxied method i.e., the actual {@code @Bean} method
     */
    @Override
    @Nullable
    public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
             MethodProxy cglibMethodProxy) throws Throwable {
       // enhancedConfigInstance是代理对象
       ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
       String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

       // Determine whether this bean is a scoped-proxy
       if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
          String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
          if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
             beanName = scopedBeanName;
          }
       }

       // To handle the case of an inter-bean method reference, we must explicitly check the
       // container for already cached instances.

       // First, check to see if the requested bean is a FactoryBean. If so, create a subclass
       // proxy that intercepts calls to getObject() and returns any cached bean instance.
       // This ensures that the semantics of calling a FactoryBean from within @Bean methods
       // is the same as that of referring to a FactoryBean within XML. See SPR-6602.
       if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
             factoryContainsBean(beanFactory, beanName)) {
          Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
          if (factoryBean instanceof ScopedProxyFactoryBean) {
             // Scoped proxy factory beans are a special case and should not be further proxied
          }
          else {
             // It is a candidate FactoryBean - go ahead with enhancement
             return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
          }
       }

       // 如果代理对象正在执行的方法就是正在创建Bean的工厂方法,那就直接执行对应的方法得到对象作为Bean
       if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
          // The factory is calling the bean method in order to instantiate and register the bean
          // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
          // create the bean instance.
          if (logger.isInfoEnabled() &&
                BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
             logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
                         "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
                         "result in a failure to process annotations such as @Autowired, " +
                         "@Resource and @PostConstruct within the method's declaring " +
                         "@Configuration class. Add the 'static' modifier to this method to avoid " +
                         "these container lifecycle issues; see @Bean javadoc for complete details.",
                   beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
          }
          // 注意这里传入的是代理对象,相当于在执行父类的方法,注意和Spring事务做区分
          return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
       }

       // 如果代理对象正在执行的方法不是正在创建Bean的方法,那就直接根据方法的名字去Spring容器中获取
       return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
    }

到这里已经很明显了,如果我们执行了代理对象的创建bean的方法,那么就会去容器中根据方法直接找到这个bean然后返回

解析: 我们看倒数第一个return和倒数第二个return

倒数第二个return是在实例化bean的时候使用是在容器启动过程中执行ImConfiguration中的imBeanFromConfugration方法创建Bean放在容器中,因为这里需要创建出来bean一个放在容器中所以直接执行了被代理方法得到了一个Bean;

倒数第一个return则是容器启动之后 ,我们自己再去执行ImConfiguration中的imBeanFromConfugration方法时执行的,因为此时我们的容器中已经存在 了一个imBeanFromConfugration的bean,所以直接去容器中获取即可。

反思: Spring为什么要这么做?

在网上没有找到答案,但我感觉是Spring为了解决Bean依赖的问题或者是为了方便开发者不需要再手动注入bean,如果没有这一层代理关系我们就必须要在方法上或者在类的属性中提前注入我们需要的Bean ,这里就会多余出bean依赖的维护关系,至少在创建类或者执行方法前要确保容器中已经存在了我们需要bean才可以执行方法,而有了这一层代理关系则只需要在我们真正想要使用bean才从容器中获取bean。但我感觉更多的还是方便开发者不用再手动注入bean。

typescript 复制代码
//如果是@Component则必须要这样写
@Component
public class ImComponent {

    /**
     * 创建一个bean名为imBeanFromComponent
     * */
    @Bean
    public ImBean imBeanFromComponent(){
       return new ImBean();
    }
    
    /**
    * 提前注入bean
    * 但是对于ImComponent这个bean来说,注入的这个bean我们可能只需要在printBean方法中使用且极有可能只需要使用一次
    * 为此Spring要维护ImComponent这个Bean依赖了imBeanFromComponent这个bean
    * 对于开发者来说我们要多写两行代码
    * 这样一来就显得很多余
    * 如果是@Configuration我们则在使用imBeanFromComponent这个bean的地方直接调用imBeanFromComponent()得到bean,就显得十分优雅
    */
    @Resource
    private ImBean imBeanFromComponent;

    public void printBean(){
       System.out.println("component-printBean:"+imBeanFromComponent);
    }

}
相关推荐
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
苏-言2 小时前
Spring IOC实战指南:从零到一的构建过程
java·数据库·spring
草莓base2 小时前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
冰帝海岸9 小时前
01-spring security认证笔记
java·笔记·spring
没书读了10 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
代码小鑫13 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖13 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring
斗-匕15 小时前
Spring事务管理
数据库·spring·oracle
Doker 多克18 小时前
Spring AI 框架使用的核心概念
人工智能·spring·chatgpt
请叫我青哥21 小时前
第五十二条:谨慎使用重载
java·spring