이번 포스트에서는 @Component를 사용하여 스프링 빈 등록하는 방법을 알아보자
📌 서론
Spring Framework의 @Component 어노테이션은 클래스 인스턴스를 스프링 빈으로 자동 등록한다. 이는 의존성 주입과 빈 생명주기 관리를 효율적으로 만들며, 스프링의 다양한 기능과도 잘 통합된다. @Component의 사용은 애플리케이션 구성을 간소화하고, 유지보수와 테스트가 쉬운 코드를 만드는 데 중요한 역할을 한다. 이를 통해 스프링 애플리케이션 개발이 더욱 간편하고 효과적이 된다.
1. @Component 사용법 및 특징
기본 사용법
- @Component 어노테이션은 스프링 프레임워크에서 클래스를 자동으로 빈으로 등록하기 위해 클래스 레벨에서 사용된다. 이는 스프링 컨테이너가 애플리케이션 시작 시 해당 클래스의 인스턴스를 생성하고, 이를 스프링 애플리케이션 컨텍스트에서 관리되는 빈으로 등록함을 의미한다.
이 예제에서 MyComponent 클래스는 @Component 어노테이션을 통해 스프링 빈으로 등록된다.
@Component
public class MyComponent {
// 필드, 메서드, 생성자 등
}
@Component의 주요 특징 1: 클래스 레벨 어노테이션
- @Component는 클래스 상단에 선언되며, 이는 해당 클래스가 스프링 빈으로 관리될 것임을 명시한다.(해당 클래스가 스프링의 관리 하에 들어갈 것임을 명시한다.)
@Component
public class MyComponent {
// 필드, 메서드, 생성자 등
}
@Component의 주요 특징 2: 자동 감지 및 빈 등록
- 스프링은 @ComponentScan 어노테이션이 적용된 설정에 따라 classpath를 스캔하고 @Component가 붙은 클래스를 자동으로 빈으로 등록한다.
@Component의 주요 특징 3: 명시적인 빈 이름 설정
- @Component("customBeanName") 형태로 명시적으로 빈의 이름을 지정할 수 있다. 이름을 지정하지 않으면 클래스 이름의 첫 글자를 소문자로 변환한 이름이 기본적으로 사용된다.
@Component("customComponent")
public class MyComponent {
// ...
}
@Component의 주요 특징 4: 다양한 파생 어노테이션 사용
- @Service, @Repository, @Controller 등은 @Component의 특화된 형태이며, 각각 서비스 계층, 데이터 접근 계층, 프레젠테이션 계층의 클래스에 사용된다. 이러한 어노테이션들은 내부적으로 @Component와 같은 방식으로 작동한다.
@Service
public class MyService {
// 서비스 계층의 로직
}
@Repository
public class MyRepository {
// 데이터 접근 로직
}
@Component의 주요 특징 5: 종속성 주입 지원
- @Component 어노테이션이 적용된 클래스는 @Autowired 어노테이션을 사용하여 다른 빈들을 주입받을 수 있다. 이를 통해 생성자, 세터, 필드 주입 등을 통한 종속성 주입이 가능하게 된다.
@Component
public class MyComponent {
private final AnotherBean anotherBean;
@Autowired
public MyComponent(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
}
2. @Component 활용예시
비즈니스 로직 클래스에 적용
- @Component는 다양한 비즈니스 로직을 수행하는 서비스 클래스에 주로 사용된다. 이를 통해 해당 클래스는 스프링 컨테이너에 의해 관리되고, 의존성 주입을 통해 필요한 다른 빈들과 상호작용할 수 있다.
- 여기서 UserService는 데이터베이스와의 상호작용을 담당하는 UserRepository를 주입받아 사용한다. 이를 통해 개발자는 비즈니스 로직과 데이터 액세스 로직을 분리할 수 있으며, 테스트와 유지보수가 용이한 코드를 작성할 수 있다.
@Component
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow(NotFoundException::new);
}
// 기타 사용자 관련 비즈니스 로직
}
데이터 액세스 객체(DAO)에 적용
- 데이터베이스와의 상호작용을 처리하는 DAO 클래스에 @Component를 적용함으로써, 스프링 컨테이너가 이러한 클래스들을 빈으로 관리하게 할 수 있다.
- UserRepository 클래스는 데이터베이스와의 상호작용을 담당한다. @Component를 사용함으로써 스프링은 이 클래스를 빈으로 관리하고, 필요한 곳에 주입할 수 있다.
@Component
public class UserRepository {
// 데이터베이스 액세스를 위한 JPA, Hibernate, JDBC 등의 코드
}
웹 컨트롤러에 적용
- 웹 애플리케이션에서는 @Controller 어노테이션을 사용하여 요청을 처리하는 컨트롤러 클래스를 정의한다. @Controller는 @Component의 특화된 형태로, 웹 요청과 응답을 처리하는 클래스에 사용된다.
- 여기서 UserController는 웹 요청을 처리하고, 필요한 비즈니스 로직은 UserService를 통해서 수행한다. 이러한 역할 분리는 코드의 단일 책임 원칙을 준수하고, 더 깔끔하고 유지보수하기 쉬운 코드 구조를 만든다.
@Controller
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
// 기타 웹 요청 처리 로직
}
3. @Component, @ComponentScan을 사용하여 의존성 주입 구성하기
DI(Dependency Injection)와의 관계:
- @Component 어노테이션이 스프링 애플리케이션에서 중요한 역할을 하는 부분 중 하나는 의존성 주입(Dependency Injection, DI)과의 관계다. 스프링은 @Component 어노테이션이 적용된 클래스들을 스프링 컨테이너에 빈으로 등록하고, 이를 다른 빈들에게 주입할 수 있도록 관리한다. 이것은 객체 간의 결합도를 낮추고, 애플리케이션의 유지보수성과 테스트 용이성을 향상시킨다.
예를 들어, 다음과 같이 @Component 어노테이션이 적용된 클래스를 보자.
@Component
public class MyService {
// 필드, 메서드 등
}
MyService 클래스가 @Component 어노테이션으로 스프링 빈으로 등록되면, 다른 클래스에서 이를 주입받을 수 있다.
- 위의 예시에서 MyOtherService 클래스는 MyService 클래스를 주입받아 사용하고 있다. 이렇게 @Component 어노테이션을 통해 스프링은 객체들 간의 의존성을 관리하고 주입하여 코드의 유연성을 높이고 결합도를 낮춘다.
@Service
public class MyOtherService {
private final MyService myService;
@Autowired
public MyOtherService(MyService myService) {
this.myService = myService;
}
// MyService를 이용한 로직 수행
}
스프링 부트의 @ComponentScan 원리
- 스프링 부트에서는 @SpringBootApplication 어노테이션을 사용하여 애플리케이션의 메인 클래스를 정의한다. 이 어노테이션은 스프링 부트의 핵심 기능인 자동 구성(auto-configuration)과 컴포넌트 스캔을 한다.
자동 구성(auto-configuration):
- 스프링 부트는 애플리케이션의 필요한 빈들을 자동으로 구성한다. 예를 들어, 웹 애플리케이션에 필요한 웹 서버(tomcat), 데이터베이스(db) 연결 등이 자동으로 설정된다.
컴포넌트 스캔(ComponentScan):
- 스프링 부트는 @ComponentScan 어노테이션을 통해 지정된 패키지 내의 클래스들을 스캔하여, @Component, @Service, @Repository, @Controller 등의 어노테이션이 붙은 클래스들을 스프링 빈으로 자동 등록한다.
JinanApplication 클래스를 통한 예시
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JinanApplication {
public static void main(String[] args) {
SpringApplication.run(JinanApplication.class, args);
}
}
@SpringBootApplication 어노테이션
- 이 어노테이션은 JinanApplication 클래스가 스프링 부트 애플리케이션의 진입점임을 나타낸다.
- @SpringBootApplication은 사실상 @Configuration, @EnableAutoConfiguration, @ComponentScan을 합친 것으로, 애플리케이션의 구성, 자동 구성, 그리고 컴포넌트 스캔을 담당한다.
SpringApplication.run() 메서드
- 이 메서드는 스프링 애플리케이션을 시작하는 실제 명령이다. 이것이 호출되면 스프링 부트는 JinanApplication 클래스가 위치한 패키지를 기준으로 컴포넌트 스캔을 수행한다.
결론
- 스프링 부트는 @SpringBootApplication 어노테이션을 통해 애플리케이션의 시작점을 정의하고, 이를 기반으로 필요한 빈들을 자동으로 구성하며, 지정된 패키지 내의 컴포넌트들을 자동으로 스캔하여 스프링 빈으로 등록한다. 이 과정은 개발자가 복잡한 설정을 수동으로 하지 않아도 되게 만들어, 애플리케이션 개발을 더욱 간편하고 효율적으로 만든다.
4. 스프링에서 @Component의 고급 활용: 특정 컴포넌트 주입, 커스텀 어노테이션, 조건부 빈 등록 및 환경별 서비스 관리
@Component의 고급 사용법: @Qualifier 어노테이션 사용 (특정 컴포넌트 주입)
- @Qualifier 어노테이션은 동일한 타입의 여러 빈 중에서 특정 빈을 주입받기 위해 사용된다. 이는 빈의 식별자 역할을 하며, 의존성 주입 시 어떤 빈을 사용할지 결정하는 데 도움을 준다.
@Autowired과 @Qualifier 사용
- 이 방식은 명시적으로 생성자에 @Autowired 어노테이션을 사용하고, 필요한 빈을 @Qualifier를 통해 지정한다.
- @Autowired를 생성자에 사용하는 것은 스프링 4.3부터 필수가 아니며, 생성자가 하나만 있고, 그 생성자의 파라미터가 빈으로 등록될 수 있는 타입일 경우 자동으로 주입된다.
@Service
public class NotificationDispatcher {
private final NotificationService notificationService;
@Autowired
public NotificationDispatcher(@Qualifier("emailService") NotificationService notificationService) {
this.notificationService = notificationService;
}
// 알림 발송 로직
}
@RequiredArgsConstructor(롬복)과 @Qualifier 사용
- @RequiredArgsConstructor는 Lombok 라이브러리의 어노테이션으로, final 또는 @NonNull 필드에 대한 생성자를 자동으로 생성한다. 이 방식은 생성자를 명시적으로 작성하지 않고, Lombok에 의해 자동으로 생성되는 생성자를 사용한다. @Qualifier 어노테이션은 필드에 직접 적용된다.
@Service
@RequiredArgsConstructor
public class NotificationDispatcher {
@Qualifier("emailService")
private final NotificationService notificationService;
// 알림 발송 로직
}
커스텀 어노테이션 생성
- @Component를 확장하여 사용자 정의 어노테이션을 만들 수 있다. 이를 통해 특정 유형의 컴포넌트에 대한 추가적인 설정이나 로직을 적용할 수 있다. 이러한 고급 기능들은 @Component의 기본 사용법을 넘어서 스프링 애플리케이션의 구성과 관리를 더욱 유연하고 효율적으로 만들어준다. 개발자는 이러한 기능들을 활용하여 애플리케이션의 다양한 요구사항을 충족시킬 수 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface JinanCustomService {
// 사용자 정의 어노테이션 설정
}
@JinanCustomService
public class MyCustomService {
// 특별한 로직이나 처리
}
@Conditional 어노테이션의 사용 방법
- @Conditional 어노테이션은 @Component, @Service, @Repository, @Controller 등 스프링의 스테레오타입 어노테이션과 함께 사용될 수 있다. 또한, @Configuration 어노테이션이 붙은 클래스의 @Bean 메서드와도 사용될 수 있다.
- @Conditional은 Condition 인터페이스를 구현한 클래스를 인자로 받는다. 이 인터페이스는 matches 메서드를 가지며, 이 메서드의 반환 값에 따라 빈의 등록 여부가 결정된다.
예시코드로 살펴보기
- 위의 예시에서, DevelopmentDatabaseService와 ProductionDatabaseService 클래스는 각각 @Conditional 어노테이션과 함께 DevelopmentCondition과 ProductionCondition 클래스를 사용한다.
@Component
@Conditional(DevelopmentCondition.class)
public class DevelopmentDatabaseService implements DatabaseService {
// 개발 환경 데이터베이스 서비스 로직
}
@Component
@Conditional(ProductionCondition.class)
public class ProductionDatabaseService implements DatabaseService {
// 운영 환경 데이터베이스 서비스 로직
}
- 각 Condition 구현 클래스는 matches() 메서드를 오버라이드하여 특정 조건을 검사한다. 이 예시에서는 애플리케이션의 활성 프로파일이 "dev"인지 또는 "prod"인지에 따라 각각 개발 또는 운영 환경 데이터베이스 서비스를 스프링 컨테이너에 등록한다. 이 방법을 사용하면 애플리케이션의 실행 환경에 따라 동적으로 빈을 등록하거나 제외할 수 있어서 환경에 따라 다르게 동작하는 애플리케이션을 구성할 수 있다.
public class DevelopmentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 개발 환경인지 확인하는 로직
return context.getEnvironment().getActiveProfiles().contains("dev");
}
}
public class ProductionCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 운영 환경인지 확인하는 로직
return context.getEnvironment().getActiveProfiles().contains("prod");
}
}
@Component의 유연성
- 개발 중인 애플리케이션에서 다양한 서비스를 관리해야 하며, 이 서비스들은 @Component로 간단하게 스프링 빈으로 등록될 것이다. 그러나 특정 서비스들은 특정 환경에서만 활성화되어야 한다.
상황 설명(코드)
- EmailService, SMSService, PushNotificationService와 같은 서비스 클래스들을 가지고 있다.
- 이러한 서비스들은 @Component를 사용하여 스프링 빈으로 등록되며, 기본적으로는 모두 활성화된다.
- 그러나 EmailService는 개발 환경에서만 활성화하고, 실제 운영 환경에서는 사용하지 않아야 한다.
이 상황에서 @Component를 사용하면 다음과 같이 될 것이다.
@Component
public class EmailService {
// 이메일 서비스의 구현
}
- EmailService 클래스를 @Component로 등록하면 스프링 컨테이너에 의해 자동으로 빈으로 관리된다. 그러나 이것만으로는 EmailService가 항상 활성화되어 개발 환경과 운영 환경에서 모두 사용될 것이다.
이제 조금 더 맞춤화를 하려면 다음과 같이 할 수 있다.
@Component
@Profile("development")
public class EmailService {
// 이메일 서비스의 구현
}
- 위 코드에서 @Profile("development") 어노테이션을 사용하여 EmailService 클래스를 개발 환경에서만 활성화되도록 설정하였다. 이제 EmailService는 개발 환경 프로파일에서만 빈으로 등록되고, 실제 운영 환경에서는 등록되지 않는다. 이렇듯 @Component를 사용하면 빈을 간단하게 등록할 수 있지만, @Profile과 같은 스프링의 다양한 맞춤화 기능을 사용하여 특정 환경 또는 조건에서만 빈을 사용하도록 등록할 수도 있다.
📌 마무리
이번 포스트에서는 @Component에 대해서 알아봤다. 나는 @Component를 항상 사용하지만 이런 내부적인 동작들이 있다는 것은 잘 알지 못했다. 이렇게 기본적인 것들을 공부하면서 스스로를 단련하면 개발할 때 좀 더 스프링을 잘 사용할 수 있을 것이라고 생각하기에 앞으로도 이런 기본기를 열심히 공부해야겠다.
@Bean을 통해 스프링 빈을 등록하는 방법이 궁금하다면?👇🏻👇🏻
'Spring 기초 > Spring 기초 지식' 카테고리의 다른 글
SpringBoot: 리소스 관리하기 (resource) (1) | 2023.11.25 |
---|---|
[Spring] @Component와 @Bean의 차이점 (0) | 2023.11.13 |
[Spring] @Bean을 사용한 스프링 빈 등록 (0) | 2023.11.12 |
[Spring] JAR와 WAR 이해하기 (0) | 2023.11.10 |
Spring Boot: 필터에서 doFilter와 FilterChain이란? (2) | 2023.11.05 |