Spring -- 05 -- @Configuration和@Component注解的区别

在日常的开发过程中,想必大家都使用过 @Configuration 和 @Component 这两个注解吧,两者都可以将标记的类注册成为 Spring 容器中的 Bean 对象,而它们之间又有什么区别呢?让我们一起来看看


一、@Configuration 注解

1、源码解析
  • @Configuration 注解内部引用了 @Component 注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Configuration {
    
    	@AliasFor(annotation = Component.class)
    	String value() default "";
    
    	boolean proxyBeanMethods() default true;
    
    }
    
    • 因此 @Configuration 注解修饰的类可以被 <context:component-scan/> XML 配置或 @ComponentScan 注解扫描到

    • 此外该类还可以像普通 Bean 对象一样在 XML 文件中定义

      <beans>
          <context:annotation-config/>
          <bean class="com.config.AppConfig"/>
      </beans>
      
    • 这里需要注意的是,<context:annotation-config/> 该 XML 配置是必需的,起作用是启动配置类后置处理器 (ConfigurationClassPostProcessor) 以及其他注解相关的后置处理器,用于处理 @Configuration 注解修饰的类

  • 源码如上所示,可知 @Configuration 注解共有 2 个属性,作用如下

    属性作用
    value()与 @Component 注解的 value() 属性互为别名
    proxyBeanMethods()是否开启 CGLIB 动态代理,默认为 true
2、使用限制

image

  • 配置类必须以类的形式提供 (不能是工厂方法返回的实例),允许通过生成的子类在运行时增强 (CGLIB 动态代理)

  • 配置类不能使用 final 进行修饰 (使用 final 修饰的类无法被继承,也就是无法使用 CGLIB 动态代理),除非将 proxyBeanMethods() 属性设置为 false (关闭 CGLIB 动态代理),即在运行时不会生成配置类的子类

  • 配置类必须是非本地的 (不得在方法中声明)

  • 配置类中的任何嵌套类都必须使用 static 进行修饰

  • @Bean 方法可能不会再创建进一步的配置类 (任何该配置类的实例都将被视为普通 Bean,不会被特殊处理)

3、CGLIB 动态代理
  • Spring 容器在启动时,会默认加载一些后置处理器,针对 @Configuration 注解修饰的类而言,该后置处理器为 ConfigurationClassPostProcessor (可通过源码注释找到)

  • ConfigurationClassPostProcessor 后置处理器负责处理 @Configuration 注解修饰的类 (此过程在 Bean 对象已加载但尚未初始化前进行),主要就是使用 CGLIB 动态代理来增强类 (针对配置类中带有 @Bean 注解的方法进行处理)

  • 核心方法如下所示

    • postProcessBeanFactory()

      public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
          // 获取 Bean 工厂的 Hash 值作为工厂 ID
      	int factoryId = System.identityHashCode(beanFactory);
      	// 判断后置处理器工厂集合是否包含工厂 ID
      	if (this.factoriesPostProcessed.contains(factoryId)) {
      	    // 如果包含,则抛出以下异常
      		throw new IllegalStateException(
      				"postProcessBeanFactory already called on this post-processor against " + beanFactory);
      	}
      	// 如果不包含,则将工厂 ID 添加进后置处理器工厂集合
      	this.factoriesPostProcessed.add(factoryId); 
      	/*
      	 * 判断注册后置处理器集合是否包含工厂 ID
      	 * (不包含则说明该 Bean 工厂尚未被加载)
      	 */
      	if (!this.registriesPostProcessed.contains(factoryId)) {
      		// 如果不包含,则调用以下方法进行加载
      		processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
      	}
      	// 增强配置类
      	enhanceConfigurationClasses(beanFactory);
      	// 创建 ImportAwareBeanPostProcessor 类型的后置处理器,并将其添加进 Bean 工厂
      	beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
      }
      
    • enhanceConfigurationClasses()

      public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
      	Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
      	// 获取所有 Bean 对象名称并循环遍历
      	for (String beanName : beanFactory.getBeanDefinitionNames()) {
      	    // 根据 Bean 对象名称获取 Bean 对象
      		BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      		// 获取配置类属性 (共有 full、lite 两种属性)
      		Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
      		// 声明方法元数据
      		MethodMetadata methodMetadata = null;
      		// 判断 Bean 对象类型是否为 AnnotatedBeanDefinition
      		if (beanDef instanceof AnnotatedBeanDefinition) {
      		    // 如果是,则获取 Bean 对象的方法元数据 (即生成 Bean 对象的那个方法)
      			methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
      		}
      		/*
      		 * (如果配置类属性不为 null 或方法元数据不为 null) 
      		 * 且 Bean 对象类型为 AbstractBeanDefinition
      		 */
      		if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
      			// Configuration class (full or lite) or a configuration-derived @Bean method
      			// -> resolve bean class at this point...
      			AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
      			// 判断 Bean 对象是否已被加载
      			if (!abd.hasBeanClass()) {
      				try {
      				    // 如果没有被加载,则加载 Bean 对象
      					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)) {
      		    // 如果不为 full,则再判断 Bean 对象类型是否为 AbstractBeanDefinition
      			if (!(beanDef instanceof AbstractBeanDefinition)) {
      			    // 如果不是,则抛出异常
      				throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
      						beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
      			}
      			// 如果是,则再判断 Bean 工厂中是否已存在指定名称的 Bean 对象
      			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'.");
      			}
      			// 将 Bean 对象放入 configBeanDefs 中,供下文进行增强
      			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);
      		// 获取配置类
      		Class<?> configClass = beanDef.getBeanClass();
      		// 获取增强类 (通过生成子类来进行增强)
      		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()));
      			}
      			// 如果不相等,则使用增强类替换掉配置类
      			beanDef.setBeanClass(enhancedClass);
      		}
      	}
      }
      

      1、配置类属性 full、lite,即 Spring 中存在两种配置模式,一种是 Full @Configuration,另一种是 lite @Bean mode

      • Full @Configuration:当 @Bean 方法在使用了 @Configuration 注解的类中声明,称之为 Full @Configuration,定义的 Bean 对象会被 CGLIB 动态代理

      • lite @Bean mode:当 @Bean 方法在没有使用 @Configuration 注解的类中声明,称之为 lite @Bean mode,定义的 Bean 对象不会被 CGLIB 动态代理

      2、增强类 ConfigurationClassEnhancer,该类的主要作用是对配置类 (使用 @Configuration 注解修饰的类) 进行增强 (通过生成子类来增强),通过增强后,配置类中的 @Bean 方法将不再是普通的方法了,它们具有与 Bean 相同的作用域

      • @Bean 方法首次被调用时,会创建相应的 Bean 对象

      • @Bean 方法再次被调用时,不会再创建新的 Bean 对象,而是根据 Bean 对象名称返回首次被该方法创建的 Bean 对象


二、@Component 注解

  • 相比较 @Configuration 注解,@Component 注解就简单多了

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Indexed
    public @interface Component {
    
    	String value() default "";
    }
    
  • 源码如上所示,可知 @Component 注解仅有 1 个属性,作用如下

    属性作用
    value()定义 Bean 对象的名称
  • @Component 注解仅用于将标记的类注册成为 Bean 对象,并没有使用 CGLIB 动态代理来增强配置类,与之相类似的还有 @Controller、@Service、@Repository 注解


三、测试

  • Student、Classroom 实体类

    @Data
    public class Student {
    
        private String name;
        private Integer age;
    
        public Student(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    }
    
    @Data
    public class Classroom {
    
        private Student student;
    
        public Classroom(Student student) {
            this.student = student;
        }
    }
    
  • SchoolConfig 配置类 (使用 @Configuration 注解)

    @Configuration
    public class SchoolConfig {
    
        @Bean
        public Student student() {
            return new Student(UUID.randomUUID().toString().substring(0, 4), (int) (Math.random() * 10 + 20));
        }
    
        @Bean
        public Classroom classroom() {
            return new Classroom(student());
        }
    }
    
  • 进行测试

    public class AnnotationTest {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SchoolConfig.class);
            Student student = context.getBean(Student.class);
            System.out.println(student);
            Classroom classroom = context.getBean(Classroom.class);
            System.out.println(classroom.getStudent());
            System.out.println(student == classroom.getStudent());
            
            // Student(name=2910, age=27)
            // Student(name=2910, age=27)
            // true
        }
    }
    
    • 由上可知,调用 student() 方法和调用 classroom() 方法中的 student()方法获取到的是同一个 Student 对象

    • 即使用 @Configuration 注解的配置类中所有 @Bean 方法都会被动态代理,调用这些方法返回的都是同一个实例

  • SchoolConfig 配置类 (使用 @Component 注解)

    @Component
    public class SchoolConfig {
    
        @Bean
        public Student student() {
            return new Student(UUID.randomUUID().toString().substring(0, 4), (int) (Math.random() * 10 + 20));
        }
    
        @Bean
        public Classroom classroom() {
            return new Classroom(student());
        }
    }
    
  • 进行测试

    public class AnnotationTest {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SchoolConfig.class);
            Student student = context.getBean(Student.class);
            System.out.println(student);
            Classroom classroom = context.getBean(Classroom.class);
            System.out.println(classroom.getStudent());
            System.out.println(student == classroom.getStudent());
            
            // Student(name=a230, age=26)
            // Student(name=ebe7, age=20)
            // false
        }
    }
    
    • 由上可知,调用 student() 方法和调用 classroom() 方法中的 student()方法获取到的不是同一个 Student 对象

    • 即使用 @Component 注解的配置类中所有 @Bean 方法只是简单地将生成的对象注册成为 Bean 对象,每调用一次就会生成一个新的实例


四、参考资料

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值