Skip to content

ConfigurationClassPostProcessor 之 @Configuration 和 @Component

发现问题

@Configuration 和 @Component 都可以添加在类上面,从而被 Spring 容器实例化,那么二者之间有什么区别呢?

@Configuration 和 @Component 注解的异同

两个都会被 Spring 实例化,主要区别如这个类中所示:

在 StudentFactory 中有 this.student(); 那么问题来了,通过 applicationContext.getBean(Student.class) 和 applicationContext.getBean(StudentFactory.class).getStudent() 拿到的 Student 是同一个对象吗(Hash 值是否一样)?

分两种情况:

  1. 如果这个类加的 @Configuration 注解,实例化 Student 时,会走代理生成代理对象,此时两种打印方式都是从 factoryBean.getObject() 获取的,是单例的,因此一样。
  2. 如果这个类加的 @Component 注解,那么 this.student() 就是一个普通方法,重新创建了一个对象,因此打印的结果就不一样,破坏了 Spring 的单例,这是错误的用法。

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

java
package com.github.mengweijin.mybatisplus.demo.configuration;

import com.github.mengweijin.mybatisplus.demo.configuration.entity.Student;
import com.github.mengweijin.mybatisplus.demo.configuration.entity.StudentFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
//@Component
@Configuration
public class StudentConfiguration {

    @Bean
    public Student student() {
        return new Student();
    }

    @Bean
    public StudentFactory studentFactory() {
        StudentFactory factory = new StudentFactory();
        factory.setStudent(student());
        return factory;
    }
}
java
/**
 * 注意:测试的时候,不要用 Lombok 加 @Data 注解,这个会重写 Student 和 StudentFactory 的 hashcode 方法,造成观测结果不正确。
 * 也不能手动重写 hashcode 方法。
 * 可以使用 == 来判断两个对象是不是同一个对象(相同的 hashcode)。
 */
@Getter @Setter
public class StudentFactory {
    private Student student;
}

@Getter @Setter
public class Student {
}

测试

  • 当 StudentConfiguration 类上面是 @Configuration 时,单元测试通过。
  • 当 StudentConfiguration 类上面是 @Component 时,单元测试报错。 说明 Spring 容器中的 Student 对象和 StudentFactory 中持有的不是同一个对象。

所以,当我们需要保持为同一个对象注入到 StudentFactory 时,需要使用 @Configuration 注解。

java
package com.github.mengweijin.mybatisplus.demo.configuration;

import com.github.mengweijin.mybatisplus.demo.configuration.entity.Student;
import com.github.mengweijin.mybatisplus.demo.configuration.entity.StudentFactory;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@Slf4j
public class TestConfigurationAndComponentAnnotation {

    @Test
    public void testComponentAnnotation() {
        String basePackages = "com.github.mengweijin.mybatisplus.demo.configuration";
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(basePackages);

        // 看下面这两个打印的 HashCode 是否一样
        Student student = applicationContext.getBean(Student.class);
        StudentFactory studentFactory = applicationContext.getBean(StudentFactory.class);
        Student student2 = studentFactory.getStudent();

        Assertions.assertTrue(student == student2);
        Assertions.assertTrue(student.hashCode() == student2.hashCode());
    }
}