Spring 기초/Spring 기초 지식

[Spring] @Component로 스프링 빈 등록하기

Stark97 2023. 11. 12. 20:27
반응형
 
 

이번 포스트에서는 @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로 간단하게 스프링 빈으로 등록될 것이다. 그러나 특정 서비스들은 특정 환경에서만 활성화되어야 한다.

상황 설명(코드)

  1. EmailService, SMSService, PushNotificationService와 같은 서비스 클래스들을 가지고 있다.
  2. 이러한 서비스들은 @Component를 사용하여 스프링 빈으로 등록되며, 기본적으로는 모두 활성화된다.
  3. 그러나 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 빈 등록: @Bean의 기본과 활용

이번 포스트에는 스프링에서 가장 중요한 빈 등록 방식 @Bean에 대해 소개한다. 📌 서론 Spring Framework에서 @Bean은 매우 중요한 개념이다. 이는 개발자가 직접 제어할 수 없는 외부 라이브러리나 복

curiousjinan.tistory.com

반응형