在日常的开发过程中,想必大家都使用过 @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、使用限制
-
配置类必须以类的形式提供 (不能是工厂方法返回的实例),允许通过生成的子类在运行时增强 (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 对象,每调用一次就会生成一个新的实例
-