////
Search
Duplicate
🛫

Chapter 11. 알아두면 유용한 스프링 활용법

이장에서는 2부를 마무리하기에 앞서 스프링과 관련된 저자의 코딩 테크닉과 개념을 소개한다.

1. 타입 기반 주입

스프링에서 @Autowired 어노테이션을 이용한 의존성 주입은 타입을 기반으로 동작한다.
이는 의존성 주입 시, 스프링 컨테이너가 타입을 기반으로 해당 빈을 찾는다는 말이다.
@Autowired 어노테이션은 타입이 일치하는 빈을 찾아 이를 주입하고 만약 해당하는 빈을 찾지 못한다면 NoSuchBeanDefinitionException 에러를 뱉는다.
만약, 해당 타입이 인터페이스나 추상 클래스여서 구현하고 있는 빈이 2개 이상이거나 해당 타입으로 생성된 빈이 2개 이상인 경우는 어떻게 될까? 이 경우 NoUniqueBeanDefinitionException이 발생한다.
그래서 이를 해결하기 위해 @Qualifier@Primary같은 어노테이션이 제공되는데 두 어노테이션 모두 주입가능한 빈이 2개 이상인 경우 사용할 수 있는 어노텡션이다.
그러나 만약 주입받으려는 변수가 List<타입>인 경우는 어떻게 될까?
스프링이 List 타입의 멤버 변수에 주입하는 경우, 타입과 일치하는 모든 스프링 빈을 가져다 List의 요소로 넣어주는 식으로 동작한다.
이런 스프링의 동작 원리를 이해하여 사용할 수 있는 테크닉이 하나 있다.
@Service @RequiredArgsConstructor public class NotificationService { private final EmailNotificationChannel emailNotificationChannel; private final SlackNotificationChannel slackNotificaitonChannel; private final ChatNotificationCahnnel chatNotificationChannel; public void notify(Account account, String message) { ... } }
Java
복사
위 코드에서 만약 새로운 채널이 멤버 변수로 추가되었다고 하면 notify라는 메소드에 변경 사항이 발생할 것이다.
그러나 코드가 만약 다음과 같이 작성되어있었다면 어땠을까?
@Service @RequiredArgsConstructor public class NotificationService { private final List<NotificationChannel> notificationChannels; public void notify(Account account, String message) { for (NotificaitonChannel notificationChannel : notificationChannels) { if (notificationChannel.supports(account)) { notificationChannel.notify(account, message); } } } }
Java
복사
이렇게 작성한다면 새로운 NotifcationChannel 타입의 구현체가 추가되어도 NotificationService에는 변경사항이 발생하지 않는다.
프로그래머가 할 일은 단순히 충실히 구현한 후, 이를 빈으로써 제공하면 될 뿐이다.
즉, 스프링의 타입 기반 주입을 활용하면 SOLID에서 말하는 OCP를 프레임워크 수준에서도 적용할 수 있다.

2. 자가 호출

자가 호출(self invocation)은 어떤 객체가 메소드를 처리하는 와중에 자신의 메소드를 호출하는 상황을 의미한다.
class Something { public void doSomething1() { doSomething2(); } public void doSomething2() { ... } }
Java
복사
이는 보통 별문제가 없는 상황이지만 스프링의 빈 메소드에서 자가 호출이 일어나면 이야기가 달라진다.
스프링 빈의 메소드에서 발생하는 자가 호출은 개발자의 의도에서 벗어나는 결과를 만들어낼 수 있기 때문이다. 특히 AOP 어노테이션이 지정되어 있을 경우 문제가 된다.
@Controller @RequiredArgsConstructor class MyController { private final MyService myService; @GetMapping @ResponseStatus(OK) public Object doSomething() { myService.doSomething1(); return null; } } @Service @RequiredArgsContstructor class MyService { public void doSomething1() { doSomething2(); } @Transactional public void doSomething2() { ... } }
Java
복사
이 상황에서 MyServicedoSomething2()@Transactional 어노테이션은 정상적으로 동작하지 않을 가능성이 있다.
이는 자가 호출로 인한 호출에서 AOP 어노테이션이 동작하지 않기 때문인데, 이는 스프링의 AOP가 프록시 기반으로 동작하기 때문에 발생하는 현상이다.
때문에 메소드에 지정된 AOP 어노테이션이 수행되려면 반드시 이 프록시 객체를 통해 메소드가 호출되어야 한다.