1. 기본 개념
1-1. @Component의 역할
@Component는 해당 클래스 자체를 스프링 빈으로 등록하는 어노테이션이다.
스프링이 컴포넌트 스캔을 할 때 이 어노테이션이 붙은 클래스를 자동으로 감지하고, 해당 클래스의 인스턴스를 생성하여 스프링 컨테이너에 빈으로 등록한다.
@Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void save(User user) {
userRepository.save(user);
}
}
위 코드에서 UserService 클래스는 @Component 어노테이션을 통해 빈으로 등록된다.
스프링은 리플렉션을 사용하여 클래스 정보를 읽고, 생성자를 찾아 필요한 의존성을 주입한 후 스프링 컨테이너에 등록한다.
1-2. @Configuration의 역할
@Configuration은 빈 설정을 담당하는 클래스임을 나타내는 어노테이션이다.
이 어노테이션이 붙은 클래스는 하나 이상의 @Bean 메서드를 포함하며, 개발자가 직접 객체를 생성하고 설정한 후 반환함으로써 빈으로 등록한다. @Configuration 클래스 자체도 @Component를 포함하고 있어 빈으로 등록된다.
@Configuration
public class AppConfig {
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
@Bean
public UserService userService() {
return new UserService(userRepository());
}
}
@Configuration을 사용하면 객체 생성 과정을 세밀하게 제어할 수 있다.
생성자에 특정 값을 전달하거나, 복잡한 초기화 로직을 수행하거나, 외부 라이브러리의 클래스를 빈으로 등록하는 등의 작업이 가능하다.
2. 빈 등록 방식의 차이
2-1. @Component의 자동 등록
@Component는 스프링의 컴포넌트 스캔 메커니즘을 통해 자동으로 빈이 등록된다.
스프링은 지정된 패키지를 스캔하여 @Component 어노테이션이 붙은 클래스를 찾고, 해당 클래스의 인스턴스를 생성한다.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
// @ComponentScan이 기본적으로 활성화되어 있어
// @Component가 붙은 클래스들이 자동으로 빈으로 등록됨
}
}
이 방식은 개발자가 만든 클래스들을 빠르게 빈으로 등록할 때 유용하다.
별도의 설정 없이 어노테이션 하나만 붙이면 되므로 간편하다.
2-2. @Configuration의 수동 등록
@Configuration은 @Bean 메서드를 통해 수동으로 빈을 등록한다. 메서드 내부에서 직접 객체를 생성하고 필요한 설정을 적용한 후 반환한다.
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(10);
config.setConnectionTimeout(30000);
return new HikariDataSource(config);
}
}
이 방식은 외부 라이브러리의 클래스를 빈으로 등록하거나, 복잡한 초기화 로직이 필요한 경우에 적합하다.
DataSource처럼 다양한 설정값을 코드로 직접 제어해야 하는 경우 @Configuration을 사용한다.
3. CGLIB 프록시와 싱글톤 보장
3-1. @Configuration의 동작 방식
@Configuration 어노테이션이 붙은 클래스는 일반 클래스와 다르게 동작한다.
스프링은 CGLIB 라이브러리를 사용하여 해당 클래스의 프록시 클래스를 생성한다.
이 프록시는 원본 클래스를 상속받은 서브클래스로, 빈의 싱글톤을 보장하는 역할을 한다.
@Configuration
public class AppConfig {
@Bean
public UserRepository userRepository() {
System.out.println("UserRepository 생성");
return new UserRepository();
}
@Bean
public UserService userService() {
return new UserService(userRepository());
}
@Bean
public OrderService orderService() {
return new OrderService(userRepository());
}
}
위 코드에서 userRepository() 메서드가 userService()와 orderService()에서 각각 호출되지만, "UserRepository 생성" 메시지는 단 한 번만 출력된다.
CGLIB 프록시가 빈을 캐싱하여 같은 인스턴스를 반환하기 때문이다.
3-2. CGLIB 프록시의 동작 원리
CGLIB은 런타임에 바이트코드를 조작하여 동적으로 서브클래스를 생성한다. 스프링이 생성하는 프록시 클래스는 대략 다음과 같은 형태다.
// 스프링이 내부적으로 생성하는 프록시 (개념적 표현)
public class AppConfig$$EnhancerBySpringCGLIB extends AppConfig {
private Map<String, Object> beanCache = new HashMap<>();
@Override
public UserRepository userRepository() {
// 캐시에 이미 존재하면 캐시에서 반환
if (beanCache.containsKey("userRepository")) {
return (UserRepository) beanCache.get("userRepository");
}
// 없으면 부모 클래스의 메서드를 호출하여 생성
UserRepository bean = super.userRepository();
beanCache.put("userRepository", bean);
return bean;
}
}
프록시는 @Bean 메서드가 호출될 때마다 캐시를 확인하고, 이미 생성된 빈이 있으면 그것을 반환한다.
이것 덕분에 같은 빈을 여러 곳에서 의존하더라도 싱글톤이 보장된다.
3-3. proxyBeanMethods 옵션
@Configuration 어노테이션에는 proxyBeanMethods라는 옵션이 있다. 이 옵션을 false로 설정하면 CGLIB 프록시를 생성하지 않는다.
@Configuration(proxyBeanMethods = false)
public class AppConfig {
@Bean
public UserRepository userRepository() {
System.out.println("UserRepository 생성");
return new UserRepository();
}
@Bean
public UserService userService() {
return new UserService(userRepository());
}
@Bean
public OrderService orderService() {
return new OrderService(userRepository());
}
}
이 경우 userRepository() 메서드를 직접 호출하면 매번 새로운 객체가 생성된다.
"UserRepository 생성" 메시지가 여러 번 출력되며, 싱글톤이 보장되지 않는다.
이 옵션은 빈 간의 의존성이 없고 성능 최적화가 필요한 경우에 사용한다.
4. 활용
4-1. @Component 사용 사례
@Component는 주로 개발자가 직접 작성한 비즈니스 로직을 담당하는 클래스에 사용한다.
Service, Repository, Controller 등의 역할을 하는 클래스들이 이에 해당한다. (타고들어가보면 @Component이 붙어있다!)
@Service // @Component의 특화 버전
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository,
EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
public void registerUser(User user) {
userRepository.save(user);
emailService.sendWelcomeEmail(user);
}
}
이러한 클래스들은 생성자를 통한 의존성 주입만으로 충분하며, 복잡한 초기화 로직이 필요하지 않다.
4-2. @Configuration 사용
@Configuration은 외부 라이브러리의 객체를 빈으로 등록하거나 복잡한 설정이 필요한 경우에 사용한다.
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/dashboard");
return http.build();
}
}
SecurityFilterChain처럼 외부 라이브러리의 클래스를 빈으로 등록하면서 다양한 설정을 적용해야 하는 경우 @Configuration이 적합하다.
5. 정리
@Component와 @Configuration은 스프링 빈을 등록하는 두 가지 방식이다.
@Component는 클래스 자체를 빈으로 등록하는 간단한 방식이고, @Configuration은 @Bean 메서드를 통해 객체 생성 과정을 세밀하게 제어할 수 있는 방식이다.
@Configuration의 가장 중요한 특징은 CGLIB 프록시를 통한 싱글톤 보장이다.
프록시는 빈을 캐싱하여 같은 객체를 반환함으로써 싱글톤을 보장한다. 이 프록시 메커니즘은 스프링 AOP의 기반이 되는 기술이기도 하다.
실무에서는 자신이 작성한 클래스는 @Component를 사용하고, 외부 라이브러리나 복잡한 설정이 필요한 객체는 @Configuration을 사용하는 것이 일반적이다. 두 방식을 적절히 조합하여 사용하면 스프링의 빈 관리 기능을 효과적으로 활용할 수 있다.
'공부일기.. > Spring' 카테고리의 다른 글
| [spring] final 키워드와 @RequiredArgsConstructor의 원리 (0) | 2025.10.22 |
|---|---|
| [spring] 스프링 컨테이너와 스프링 빈 (1) | 2025.10.15 |
| [spring] 언제! 리팩토링해야 할까? 실전 가이드-유지보수성 문제 (파트 3/3) (0) | 2025.10.05 |
| [spring] 언제! 리팩토링해야 할까? 실전 가이드- 코드 품질 문제 (2/3) (0) | 2025.10.04 |
| [spring] 언제! 리팩토링해야 할까? 실전 가이드- 설계원칙 위반 (1/3) (0) | 2025.10.03 |