오늘 공부한 내용은 따로 정리하기 애매해 어제 적은 글에 보충하는 식으로 하고, 오늘은 대신 지금껏 공부한 어노테이션들에 대해 정리해 볼까 한다.
물론 어노테이션은 그 종류가 상당히 많기에, 모든 것을 적을 수는 없고, 공부하며 새롭게 알아가는 것들을 다시 찾아와서 계속 추가하고 정리할 예정이다.
용도 : 스프링 부트 애플리케이션의 진입점.
기능 :
@Configuration,@EnableAutoConfiguration,@ComponentScan어노테이션을 포함하고 있어서 스프링 부트 애플리케이션의 설정과 자동 구성을 활성화한다.예시 :
@SpringBootApplication public class StudyApplication { public static void main(String[] args) { SpringApplication.run(StudyApplication.class, args); } }
용도 : 스프링 컨테이너에 빈을 등록
기능 : 객체를 싱글톤(Singleton)으로 스프링 컨테이너에 등록한다.
예시 :
@Service public class UserService { // ... }
용도 : 자바 클래스를 설정 클래스로 선언
기능 : 스프링 설정 정보를 제공하고,
@Bean어노테이션을 통해 빈을 등록할 수 있다.
용도 : 메서드를 스프링 빈으로 등록
기능 :
@Configuration클래스 내부에서 메서드를 통해 빈을 수동으로 등록한다.예시 :
@Configuration public class Appconfig { @Bean public UserService(){ return new UserServiceImpl(); } }
용도: 의존성 주입(Dependency Injection)을 자동으로 수행.
기능: 해당 클래스의 의존성을 스프링 컨테이너에서 자동으로 주입해준다.
예시:
@Service public class UserService { private UserRepository repository; @Autowired public UserService(UserRepository repository) { this.repository = repository; } }
용도 : 데이터를 조회하거나 읽을 때 사용.
기능 :
@RequestMapping(method = RequestMethod.GET)을 단순화한 어노테이션으로, 클라이언트의GET요청을 처리한다.예시 :
@RestController @RequestMapping("/api") public class UserController { @GetMapping("/users") public List<User> getUsers() { return userService.getAllUsers(); } }
용도 : 서버에 데이터 생성 요청.
기능 :
@RequestMapping(method = RequestMethod.POST)을 단순화한 어노테이션으로, 클라이언트의POST요청을 처리한다.
예시:@RestController @RequestMapping("/api") public class UserController { @PostMapping("/users") public User createUser(@RequestBody User user) { return userService.saveUser(user); } }
용도 : 서버에서 리소스 업데이트 요청.
기능 :
@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); } }
용도: 서버에서 리소스 삭제 요청.
기능 :
@RequestMapping(method = RequestMethod.DELETE)을 단순화한 어노테이션으로, 클라이언트의DELETE요청을 처리한다.@RestController @RequestMapping("/api") public class UserController { @DeleteMapping("/users/{id}") public void deleteUser(@PathVariable Long id) { userService.deleteUser(id); } }
용도 : 동일한 타입의 빈이 여러 개일 때, 특정 이름을 가진 빈을 주입.
기능 : 빈 이름을 기준으로 원하는 빈을 선택적으로 주입하고, 중복된 타입의 빈들 중 특정 빈을 선택해 의존성 주입 문제를 해결.
예시 :
@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 @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가 있고, 결제 수단에는CreditCardPayment와PayPalPayment두 가지 구현체가 있다. 각 컨텍스트에서 사용할 결제 서비스를 명시적으로 구분해 주입해야 한다는 상황을 가정하면 다음과 같은 상황이 펼쳐진다.
- 인터페이스 정의
public interface PaymentService { void processPayment(double amount); }
CreditCardPayment와PayPalPayment@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타입의 빈을 주입하려고 하면CreditCardPayment와PayPalPayment둘 다 후보로 등록되므로 충돌이 발생하게 된다.이 때 위에서 설명한
@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 등록까지 마쳤으니 이제 적당한 사용처가 있다고 치고 의존관계를 주입해보면 이런 그림이 된다.
짠
물론 이건 예시를 위해 아무렇게나 짜놓은 코드이고,스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의 하는 것은 유지보수에 큰 혼란을 야기할 수 있기에 알잘딱하게 잘 써먹을 필요가 있다.