•
이장에서는 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
복사
•
이 상황에서 MyService의 doSomething2()의 @Transactional 어노테이션은 정상적으로 동작하지 않을 가능성이 있다.
•
이는 자가 호출로 인한 호출에서 AOP 어노테이션이 동작하지 않기 때문인데, 이는 스프링의 AOP가 프록시 기반으로 동작하기 때문에 발생하는 현상이다.
•
때문에 메소드에 지정된 AOP 어노테이션이 수행되려면 반드시 이 프록시 객체를 통해 메소드가 호출되어야 한다.