2024.12.03(Spring Annotation)

칙촉·2024년 12월 3일

오늘 공부한 내용은 따로 정리하기 애매해 어제 적은 글에 보충하는 식으로 하고, 오늘은 대신 지금껏 공부한 어노테이션들에 대해 정리해 볼까 한다.
물론 어노테이션은 그 종류가 상당히 많기에, 모든 것을 적을 수는 없고, 공부하며 새롭게 알아가는 것들을 다시 찾아와서 계속 추가하고 정리할 예정이다.

🎯 목차

  1. 스프링 어노테이션
  2. 어노테이션 직접 만들기

@ 스프링 어노테이션

@SpringBootApplication

용도 : 스프링 부트 애플리케이션의 진입점.

기능 : @Configuration, @EnableAutoConfiguration, @ComponentScan 어노테이션을 포함하고 있어서 스프링 부트 애플리케이션의 설정과 자동 구성을 활성화한다.

예시 :

@SpringBootApplication
public class StudyApplication {
    public static void main(String[] args) {
        SpringApplication.run(StudyApplication.class, args);
    }
}

@Component

@Service

@Repository

@Controller

용도 : 스프링 컨테이너에 빈을 등록

기능 : 객체를 싱글톤(Singleton)으로 스프링 컨테이너에 등록한다.

예시 :

@Service
public class UserService {
    // ...
}

@Configuration

용도 : 자바 클래스를 설정 클래스로 선언

기능 : 스프링 설정 정보를 제공하고, @Bean 어노테이션을 통해 빈을 등록할 수 있다.

@Bean

용도 : 메서드를 스프링 빈으로 등록

기능 : @Configuration 클래스 내부에서 메서드를 통해 빈을 수동으로 등록한다.

예시 :

@Configuration
public class Appconfig {
	@Bean
    public UserService(){
    	return new UserServiceImpl();
    }
}

@Autowired

용도: 의존성 주입(Dependency Injection)을 자동으로 수행.

기능: 해당 클래스의 의존성을 스프링 컨테이너에서 자동으로 주입해준다.

예시:

@Service
public class UserService {
 
    private UserRepository repository;

  	@Autowired
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
}

@GetMapping

용도 : 데이터를 조회하거나 읽을 때 사용.

기능 : @RequestMapping(method = RequestMethod.GET)을 단순화한 어노테이션으로, 클라이언트의 GET 요청을 처리한다.

예시 :

@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/users")
    public List<User> getUsers() {
        return userService.getAllUsers();
    }
}

@PostMapping

용도 : 서버에 데이터 생성 요청.

기능 : @RequestMapping(method = RequestMethod.POST)을 단순화한 어노테이션으로, 클라이언트의 POST 요청을 처리한다.
예시:

@RestController
@RequestMapping("/api")
public class UserController {

    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return userService.saveUser(user);
    }
}

@PutMapping

용도 : 서버에서 리소스 업데이트 요청.

기능 : @RequestMapping(method = RequestMethod.PUT)을 단순화한 어노테이션으로, 클라이언트의 PUT 요청을 처리한다.

@RestController
@RequestMapping("/api")
public class MyController {

    @PutMapping("/users/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        return userService.updateUser(id, user);
    }
}

@DeleteMapping

용도: 서버에서 리소스 삭제 요청.

기능 : @RequestMapping(method = RequestMethod.DELETE)을 단순화한 어노테이션으로, 클라이언트의 DELETE 요청을 처리한다.

@RestController
@RequestMapping("/api")
public class UserController {

    @DeleteMapping("/users/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

@Qualifier

용도 : 동일한 타입의 빈이 여러 개일 때, 특정 이름을 가진 빈을 주입.

기능 : 빈 이름을 기준으로 원하는 빈을 선택적으로 주입하고, 중복된 타입의 빈들 중 특정 빈을 선택해 의존성 주입 문제를 해결.

예시 :

@Component
public class GoodBean implements Bean {
}

@Component
public class BadBean implements Bean {
}
@Service
public class UserService {
    private final Bean bean;

    public UserService(@Qualifier("goodBean") Bean bean) {
        this.bean = bean;
    }
}

@Primary

용도: 동일한 타입의 빈이 여러 개일 때, 기본적으로 주입할 빈을 지정.

기능: 주입 시 우선순위가 가장 높은 기본 빈을 설정하고, 추가적인 설정 없이도 기본값처럼 작동한다.

예시 :

@Primary
@Component
public class GoodUserRepository implements UserRepository {
}

@Component
public class BadUserRepository implements UserRepository {
}

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        // GoodUserRepository가 자동 주입됨
        this.dataService = dataService;
    }
}

@커스텀 어노테이션 만들기

복잡한 여러 기능을 가진 어노테이션을 만드는 것은 아직 무리겠지만 위에서 설명한 어노테이션들을 이용한다면 상황에 따라 간단한 어노테이션을 만들어 볼 수 있다.

예시를 들기 위해 임의의 상황을 만들어 보자면 :
애플리케이션에서 결제 수단을 관리하는 PaymentService가 있고, 결제 수단에는 CreditCardPaymentPayPalPayment 두 가지 구현체가 있다. 각 컨텍스트에서 사용할 결제 서비스를 명시적으로 구분해 주입해야 한다는 상황을 가정하면 다음과 같은 상황이 펼쳐진다.

  • 인터페이스 정의
    public interface PaymentService {
        void processPayment(double amount);
    }
  • CreditCardPaymentPayPalPayment
    @Component
    public class CreditCardPayment implements PaymentService {
        @Override
        public void processPayment(double amount) {
            System.out.println("Processing credit card payment of $" + amount);
        }
    }
    
    @Component
    public class PaypalPayment implements PaymentService {
        @Override
        public void processPayment(double amount) {
            System.out.println("Processing PayPal payment of $" + amount);
        }
    }

PaymentService 타입의 빈을 주입하려고 하면 CreditCardPaymentPayPalPayment 둘 다 후보로 등록되므로 충돌이 발생하게 된다.

이 때 위에서 설명한 @Qualifier를 이용한다면 둘을 구분지을 수 있다.

@Qualifier("CreditCard")
@Component
public class CreditCardPayment implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}

@Qualifier("Paypal")
@Component
public class PaypalPayment implements PaymentService {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing PayPal payment of $" + amount);
    }
}

물론 Component("Paypal") 이후 의존관계를 주입할 때만 @Qualifier("Paypal") 혹은@Qualifier("PaypalPayment") 를 써도 되지만 그다지 권장하지는 않는다.
일단 난 출력문도 아닌 주제에 문자열이 코드에 섞여있는 것을 매우 싫어하기 때문에 일단 어노테이션으로 만들어버리기로 했다.

일단 만들기는 했는데... 이제 뭐하지?

@Qualifier의 코드를 타고 들어가 붙어있는 어노테이션들을 훔쳐오기로 했다.

이후 @Qualifier("Paypalpayment")를 추가로 붙여준 뒤, 똑같이 @CreditCardPayment 어노테이션도 만들어줬다.

Bean 등록까지 마쳤으니 이제 적당한 사용처가 있다고 치고 의존관계를 주입해보면 이런 그림이 된다.


물론 이건 예시를 위해 아무렇게나 짜놓은 코드이고,스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의 하는 것은 유지보수에 큰 혼란을 야기할 수 있기에 알잘딱하게 잘 써먹을 필요가 있다.

profile
강세민

0개의 댓글