Spring注解配置:@Configuration 和 @Component 区别及原理详解

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 随着`Spring Boot`的盛行,注解配置式开发受到了大家的青睐,从此告别了基于`Spring`开发的繁琐`XML`配置。这里先来提纲挈领的了解一下`Spring`内部对于配置注解的定义,如`@Component、@Configuration、@Bean、@Import`等注解,从功能上来讲,这些注解所负责的功能的确不相同,但是

1.背景

随着Spring Boot的盛行,注解配置式开发受到了大家的青睐,从此告别了基于Spring开发的繁琐XML配置。这里先来提纲挈领的了解一下Spring内部对于配置注解的定义,如@Component、@Configuration、@Bean、@Import等注解,从功能上来讲,这些注解所负责的功能的确不相同,但是从本质上来讲,Spring内部都将其作为配置注解进行处理。

对于一个成熟的框架来讲,简单及多样化的配置是至关重要的,那么Spring也是如此,从Spring的配置发展过程来看,整体的配置方式从最初比较“原始”的阶段到现在非常“智能”的阶段,这期间Spring做出的努力是非常巨大的,从XML到自动装配,从Spring到Spring Boot,从@Component到@Configuration以及@Conditional,Spring发展到今日,在越来越好用的同时,也为我们隐藏了诸多的细节,那么今天让我们一起探秘@Component与@Configuration。

我们平时在Spring的开发工作中,基本都会使用配置注解,尤其以@Component及@Configuration为主,当然在Spring中还可以使用其他的注解来标注一个类为配置类,这是广义上的配置类概念,但是这里我们只讨论@Component和@Configuration,因为与我们的开发工作关联比较紧密,那么接下来我们先讨论下一个问题,就是@Component与@Configuration有什么区别?

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

2.@Component与@Configuration使用

2.1注解定义

在讨论两者区别之前,先来看看两个注解的定义:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
   

    /**
     * The value may indicate a suggestion for a logical component name,
     * to be turned into a Spring bean in case of an autodetected component.
     * @return the suggested component name, if any (or empty String otherwise)
     */
    String value() default "";

}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
   

    @AliasFor(annotation = Component.class)
    String value() default "";

    boolean proxyBeanMethods() default true;

}

从定义来看, @Configuration 注解本质上还是 @Component,因此 @ComponentScan 能扫描到@Configuration 注解的类。

2.2注解使用

接下来看看两者在我们日常开发中的使用:以这两种注解来标注一个类为配置类

@Configuration
public class AppConfig {
   
}

@Component
public class AppConfig {
   
}

上面的程序,Spring会将其认为配置类来做处理,但是这里有一个概念需要明确一下,就是在Spring中,对于配置类来讲,其实是有分类的,大体可以分为两类,一类称为LITE模式,另一类称为FULL模式,那么对应上面的注解,@Component就是LITE类型,@Configuration就是FULL类型,如何理解这两种配置类型呢?我们先来看这个程序。

当我们使用@Component实现配置类时:

@Component
public class AppConfig {
   
    @Bean
    public Foo foo() {
   
        System.out.println("foo() invoked...");
        Foo foo = new Foo();
        System.out.println("foo() 方法的 foo hashcode: " + foo.hashCode());
        return foo;
    }

    @Bean
    public Eoo eoo() {
   
        System.out.println("eoo() invoked...");
        Foo foo = foo();
        System.out.println("eoo() 方法的 foo hashcode: "+ foo.hashCode());
        return new Eoo();
    }

    public static void main(String[] args) {
   
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);
    }
}

执行结果如下:

foo() invoked...
foo() 方法的 foo hashcode: 815992954
eoo() invoked...
foo() invoked...
foo() 方法的 foo hashcode: 868737467
eoo() 方法的 foo hashcode: 868737467

从结果可知,foo()方法执行了两次,一次是bean方法执行的,一次是eoo()调用执行的,所以两次生成的foo对象是不一样的。很符合大家的预期,但是当我们使用@Configuration标注配置类时,执行结果如下:

foo() invoked...
foo() 方法的 foo hashcode: 849373393
eoo() invoked...
eoo() 方法的 foo hashcode: 849373393

这里可以看到foo()方法只执行了一次,同时eoo()方法调用foo()生成的foo对象是同一个。这也就是@Component@Configuration的区别现象展示,那么为什么会有这样的一个现象?我们来考虑一个问题,就是eoo()方法中调用了foo()方法,很明显这个foo()这个方法就是会形成一个新对象,假设我们调用的foo()方法不是原来的foo()方法,是不是就可能不会形成新对象?如果我们在调用foo()方法的时候去容器中获取一下foo这个Bean,是不是就可以达到这样的效果?那如何才能达到这样的效果呢?有一个方法,代理!换句话说,我们调用的eoo()和foo()方法,包括AppConfig都被Spring代理了,那么这里我们明白了@Component与@Configuration最根本的区别,那就是@Configuration标注的类会被Spring代理,其实这样描述不是非常严谨,更加准确的来说应该是如果一个类的BeanDefinition的Attribute中有Full配置属性,那么这个类就会被Spring代理

3.Spring如何实现FULL配置的代理

如果要明白这一点,那么还需要明确一个前提,就是Spring在什么时间将这些配置类转变成FULL模式或者LITE模式的,接下来我们就要介绍个人认为在Spring中非常重要的一个类,ConfigurationClassPostProcessor。

3.1ConfigurationClassPostProcessor是什么

首先来简单的看一下这个类的定义:

public class ConfigurationClassPostProcessor implements 
                                              BeanDefinitionRegistryPostProcessor,
                                               PriorityOrdered, 
                                               ResourceLoaderAware, 
                                               BeanClassLoaderAware, 
                                               EnvironmentAware {
   }

由这个类定义可知这个类的类型为BeanDefinitionRegistryPostProcessor,以及实现了众多Spring内置的Aware接口,如果了解Beanfactory的后置处理器,那应该清楚ConfigurationClassPostProcessor的执行时机,当然不了解也没有问题,我们会在后面将整个流程阐述清楚,现在需要知道的是ConfigurationClassPostProcessor这个类是在什么时间被实例化的?

3.2ConfigurationClassPostProcessor在什么时间被实例化

要回答这个问题,需要先明确一个前提,那就是ConfigurationClassPostProcessor这个类对应的BeanDefinition在什么时间注册到Spring的容器中的,因为Spring的实例化比较特殊,主要是基于BeanDefinition来处理的,那么现在这个问题就可以转变为ConfigurationClassPostProcessor这个类是在什么时间被注册为一个Beandefinition的?这个可以在源代码中找到答案,具体其实就是在初始化这个Spring容器的时候。

new AnnotationConfigApplicationContext(ConfigClass.class)
  -> new AnnotatedBeanDefinitionReader(this);
    -> AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
      -> new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        -> registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName)

从这里可以看出,ConfigurationClassPostProcessor已经被注册为了一个BeanDefinition,上面我们讲了Spring是通过对BeanDefinition进行解析,处理,实例化,填充,初始化以及众多回调等等步骤才会形成一个Bean,那么现在ConfigurationClassPostProcessor既然已经形成了一个BeanDefinition。

3.3 @Component与@Configuration的实现区别

上面ConfigurationClassPostProcessor已经注册到BeanDefinition注册中心了,说明Spring会在某个时间点将其处理成一个Bean,那么具体的时间点就是在BeanFactory所有的后置处理器的处理过程。

AbstractApplicationContext
  -> refresh()
    -> invokeBeanFactoryPostProcessors(beanFactory);
      -> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

这个处理BeanFactory的后置处理器的方法比较复杂,简单说来就是主要处理所有实现了BeanFactoryPostProcessor及BeanDefinitionRegistryPostProcessor的类,当然ConfigurationClassPostProcessor就是其中的一个,那么接下来我们看看实现的方法:

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
   
      throw new IllegalStateException(
          "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
   
      throw new IllegalStateException(
          "postProcessBeanFactory already called on this post-processor against " + registry);
    }
    this.registriesPostProcessed.add(registryId);

    processConfigBeanDefinitions(registry);
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   
    int factoryId = System.identityHashCode(beanFactory);
    if (this.factoriesPostProcessed.contains(factoryId)) {
   
      throw new IllegalStateException(
          "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    }
    this.factoriesPostProcessed.add(factoryId);
    if (!this.registriesPostProcessed.contains(factoryId)) {
   
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }

    enhanceConfigurationClasses(beanFactory);
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
  }

这两个方法应该就是ConfigurationClassPostProcessor最为关键的了,我们在这里先简单的总结一下,第一个方法主要完成了内部类,@Component,@ComponentScan,@Bean,@Configuration,@Import等等注解的处理,然后生成对应的BeanDefinition,另一个方法就是对@Configuration使用CGLIB进行增强,那我们先来看Spring是在哪里区分配置的LITE模式和FULL模式?在第一个方法中有一个checkConfigurationClassCandidate方法:

public static boolean checkConfigurationClassCandidate(
      BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
   

        // ...

      Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
        if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
   
          beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        }
        else if (config != null || isConfigurationCandidate(metadata)) {
   
          beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        }
        else {
   
          return false;
        }
        // ...      
      }

根据程序的判断可知,如果一个类被@Configuration标注且代理模式为true,那么这个类对应的BeanDefinition将会被Spring添加一个FULL配置模式的属性,有些同学可能对这个”属性“不太理解,这里可以简单说一下,其实这个”属性“在Spring中有一个特定的接口就是AttributeAccessor,BeanDefinition就是继承了这个接口,如何理解这个AttributeAccessor呢?其实也很简单,想想看,BeanDefinition主要是做什么的?这个主要是用来描述Class对象的,例如这个Class是不是抽象的,作用域是什么,是不是懒加载等等信息,那如果一个Class对象有一个“属性”是BeanDefinition描述不了的,那这个要如何处理呢?那这个接口AttributeAccessor又派上用场了,你可以向其中存放任何你定义的数据,可以理解为一个map,现在了解BeanDefinition的属性的含义了么?

在这里也能看到@Configuration(proxyBeanMethods = false)@Component一样效果,都是LITE模式

在这里第一步先判断出这个类是FULL模式还是LITE模式,那么下一步就需要开始执行对配置类的注解的解析了,在ConfigurationClassParser这个类有一个processConfigurationClass方法,里面有一个doProcessConfigurationClass方法,这里就是解析前文所列举的@Component等等注解的过程,解析完成之后,在ConfigurationClassPostProcessor类的方法processConfigBeanDefinitions,有一个loadBeanDefinitions方法,这个方法就是将前文解析成功的注解数据全都注册成BeanDefinition,这就是ConfigurationClassPostProcessor这个类的第一个方法所完成的任务,另外这个方法在这里是非常简单的描述了一下,实际上这个方法非常的复杂,需要慢慢的研究。

接下来再说ConfigurationClassPostProcessor类的enhanceConfigurationClasses方法,这个方法主要完成了对@Configuration注解标注的类的增强,进行CGLIB代理,代码如下:

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
   
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
   //判断是否被@Configuration标注
                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.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
   
                    logger.warn("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()) {
   
            // nothing to enhance -> return immediately
            return;
        }
        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);
            try {
   
                // Set enhanced subclass of the user-specified bean class
                Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);//生成代理的class
                if (configClass != enhancedClass) {
   
                    if (logger.isDebugEnabled()) {
   
                        logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
                                "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                    }
                    //替换class,将原来的替换为CGLIB代理的class
                    beanDef.setBeanClass(enhancedClass);
                }
            }
            catch (Throwable ex) {
   
                throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
        }
    }

这里需要CGLIB的一些知识,我就简单的在这里总结一下,这个方法从所有的BeanDefinition中找到属性为FULL模式的BeanDefinition,然后对其进行代理增强,设置BeanDefinition的beanClass。然后在增强时有一些细节稍微需要明确一下,就是我们这个普通类中的方法,比如eoo(),foo()等方法,将会被MethodInterceptor所拦截,这个方法的调用将会被BeanMethodInterceptor所代理,到这里我们大家应该稍微明确了ConfigurationClassPostProcessor是在什么时间被实例化,什么时间解析注解配置,什么时间进行配置增强。如果看到这里不太明白,那欢迎与我来讨论。

4.总结

@Component在Spring中是代表LITE模式的配置注解,这种模式下的注解不会被Spring所代理,就是一个标准类,如果在这个类中有@Bean标注的方法,那么方法间的相互调用,其实就是普通Java类的方法的调用。

@Configuration在Spring中是代表FULL模式的配置注解,这种模式下的类会被Spring所代理,那么在这个类中的@Bean方法的相互调用,就相当于调用了代理方法,那么在代理方法中会判断,是否调用getBean方法还是invokeSuper方法,这里就是这两个注解的最根本的区别。

一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。

目录
相关文章
|
1月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
130 26
|
2月前
|
缓存 Java 数据库
SpringBoot缓存注解使用
Spring Boot 提供了一套方便的缓存注解,用于简化缓存管理。通过 `@Cacheable`、`@CachePut`、`@CacheEvict` 和 `@Caching` 等注解,开发者可以轻松地实现方法级别的缓存操作,从而提升应用的性能和响应速度。合理使用这些注解可以大大减少数据库的访问频率,优化系统性能。
205 89
|
27天前
|
监控 Java Spring
SpringBoot:SpringBoot通过注解监测Controller接口
本文详细介绍了如何通过Spring Boot注解监测Controller接口,包括自定义注解、AOP切面的创建和使用以及具体的示例代码。通过这种方式,可以方便地在Controller方法执行前后添加日志记录、性能监控和异常处理逻辑,而无需修改方法本身的代码。这种方法不仅提高了代码的可维护性,还增强了系统的监控能力。希望本文能帮助您更好地理解和应用Spring Boot中的注解监测技术。
63 16
|
2月前
|
监控 Java 数据库连接
Spring c3p0配置详解
在Spring项目中配置C3P0数据源,可以显著提高数据库连接的效率和应用程序的性能。通过合理的配置和优化,可以充分发挥C3P0的优势,满足不同应用场景的需求。希望本文的详解和示例代码能为开发者提供清晰的指导,帮助实现高效的数据库连接管理。
106 10
|
3月前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
69 21
|
10月前
|
消息中间件 SpringCloudAlibaba Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
1133 0
|
XML Java 数据库连接
【Spring学习笔记 五】Spring注解及Java类配置开发
【Spring学习笔记 五】Spring注解及Java类配置开发
113 0
|
XML Java 数据格式
Spring学习笔记:02 spring配置
Spring学习笔记:02 spring配置
|
Java 数据库连接 Spring
spring学习笔记(22)声明式事务配置,readOnly无效写无异常
<div class="markdown_views"> <p>在上一节内容中,我们使用了编程式方法来配置事务,这样的优点是我们对每个方法的控制性很强,比如我需要用到什么事务,在什么位置如果出现异常需要回滚等,可以进行非常细粒度的配置。但在实际开发中,我们可能并不需要这样细粒度的配置。另一方面,如果我们的项目很大,service层方法很多,单独为每个方法配置事务也是一件很繁琐的
1696 0
|
Java Spring 前端开发
spring学习笔记(23)基于tx/aop配置切面增强事务
<div class="markdown_views"> <p>在上一篇文章中,我们使用了声明式事务来配置事务,使事务配置从service逻辑处理中解耦出来。但它还存在一些缺点: <br> 1. 我们只针对方法名的特定进行拦截,但无法利用方法签名的其它信息定位,如修饰符、返回值、方法入参、异常类型等。如果我们需要为同名不同参的同载方法配置不同事务就会出问题了。 <br> 2.
2286 0

热门文章

最新文章