티스토리 뷰
1. 빈을 찾기 위한 다양한 의존성 검색 방법, DL(Dependency LookUp)
Spring으로 개발을 하다보면 의존성을 주입(DI, Dependency Injection) 받는 것 뿐만 아니라 의존성을 갖는 빈을 검색(DL, Dependency LookUp)해야 할 때가 있다. 예를 들어 프로토타입 스코프의 빈을 이용해야 하는 경우가 대표적이다.
Spring에서는 빈을 검색하기 위한 다양한 방법들을 제공하는데, 크게 5가지가 있다.
[ ApplicationContext를 이용한 방법 ]
Spring에서는 빈 팩토리를 구현한 DI 컨테이너인 ApplicationContext를 관리하고 있다. 그래서 우리는 ApplicationContext를 통해서 필요로 하는 빈을 다음과 같이 검색할 수 있다.
@Service
@RequiredArgsConstructor
public class UserService {
private final ApplicationContext context;
public User dependencyLookUp() {
final UserRequest userRequest = context.getBean(UserRequest.class);
...
return user;
}
}
하지만 ApplicationContext를 이용하는 방법은 일반 애플리케이션 코드에 스프링의 API가 사용하게 되는 문제점을 내포하게 된다.
Spring의 가장 큰 장점은 환경과 기술에 종속되지 않는 POJO 방식으로 개발을 지원하는 것인데, 이렇게 애플리케이션 코드에 Spring에 종속적인 코드가 추가되는 것은 바람직하지 못하다. 또한 만약 이에 대한 단위 테스트를 작성하려면 ApplicationContext라는 거대한 인터페이스의 목 객체를 만들어야 하므로 테스트의 작성도 어렵다.
[ ObjectFactory를 이용한 방법 ]
위의 ApplicationContext를 이용하는 방식은 너무 무거우며 지나치게 낮은 레벨의 객체를 이용해야 한다. 그래서 ApplicationContext를 대신하여 중간에서 getBean()을 호출해주는 팩토리 객체인 ObjectFactory를 사용하는 방식이 있다.
Spring이 제공하는 ObjectFactory를 이용하면 ApplicationContext의 getBean()처럼 너무 로우레벨의 API를 사용할 필요가 없어 깔끔하며, 테스트를 작성하기에 비교적 편리하다는 장점이 있다. (Spring이 역할과 계층을 잘 분리했기에 가능한 것이다.)
ObjectFactory 인터페이스는 getObject()라는 간단한 팩토리 메소드를 가지고 있다.
@Bean
public ObjectFactoryCreatingFactoryBean userRequestFactory() {
ObjectFactoryCreatingFactoryBean factoryBean = new ObjectFactoryCreatingFactoryBean();
factoryBean.setTargetBeanName("userRequest");
return factoryBean;
}
@Service
@RequiredArgsConstructor
public class UserService {
private final ObjectFactory<UserRequest> userRequestFactory;
public User dependencyLookUp() {
final UserRequest userRequest = userRequestFactory.getObject();
...
return user;
}
}
ObjectFactory는 매우 단순한 인터페이스이기 때문에 단위 테스트를 작성하기에도 용이하다. 하지만 ObjectFactory도 결국 Spring의 API이기 때문에일반 애플리케이션 코드에 스프링의 API가 사용 되며, 애플리케이션 코드가 Spring에 의존하는 문제는 해결하지 못했다. 또한 번거롭게 ObjectFactoryCreatingFactoryBean을 등록해주어야 하는 단점도 가지고 있다.
[ ServiceLocatoryFactoryBean를 이용한 방법 ]
ObjectFactory는 ApplicationContext에 비해 확실히 단순하고 깔끔하진만 Spring에 의존적이라는 것은 확실히 좋지 못하다. 그래서 이러한 문제를 해결한 ServiceLocatorFactoryBean을 이용하는 방법이 있다.
ServiceLocatorFactoryBean는 ObjectFactory처럼 Spring이 정의해둔 인터페이스 대신 DL 방식으로 빈을 찾아 반환하는 메소드를 정의한 인터페이스만 있으면 된다. 여기서 메소드의 이름은 어떻게 지어도 상관이 없다. 그리고 정의한 인터페이스를 스프링의 ServiceLocatorFactoryBean에 등록해주면 된다.
public interface UserRequestFactory {
UserRequest getUserRequest();
}
@Bean
public ServiceLocatorFactoryBean serviceLocatorFactoryBean() {
final ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
factoryBean.setServiceLocatorInterface(UserRequestFactory.class);
return factoryBean;
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRequestFactory userRequestFactory;
public User dependencyLookUp() {
final UserRequest userRequest = userRequestFactory.getUserRequest();
...
return user;
}
}
이러한 방식은 타입 파라미터를 사용해야 하는 ObjectFactory 방식보다 간결하다. 하지만 이러한 방식은 새로운 인터페이스와 그에 맞는 코드를 작성하여 빈을 등록해야 한다는 번거로움이 있다.
[ 메소드 주입을 이용한 방법 ]
Spring에서는 이러한 두 가지 문제점(스프링에 의존적인 코드를 작성하는 것, 새로운 빈을 등록해야 하는 것)을 모두 해결해 줄 수 있는 메소드 주입 기술을 제공해준다. 여기서 설명한 메소드 주입은 @Autowired를 사용하여 빈을 주입하는 메소드 주입이 아니라 메소드 코드 자체를 주입하여 오버라이딩하는 것을 말한다.
우리가 빈을 반환하는 메소드를 작성하고 @LookUp 어노테이션을 추가하면 Spring은 ApplicationContext와 getBean() 메소드를 사용해 빈을 가져오는 메소드로 오버라이드한다.
@RestController
public class TestController {
@GetMapping("/test")
public ResponseEntity<Result> test() {
final TestController testController = getTestController();
...
}
@Lookup
public TestController getTestController(){
//spring will override this method
return null;
}
}
우리의 코드에서는 null을 반환하고 있지만 Spring은 실행 시에 LookUp 어노테이션이 붙은 메소드를 오버라이드 한다. 이러한 메소드 주입 방식은 Spring API에 의존적이지도 않으며 컨테이너의 도움 없이 단위 테스트를 작성할 수도 있다. 하지만 이러한 방식은 단위 테스트를 작성하기에 번거로운 점들이 많다. 그래서 Spring은 빈을 찾아야 하는 경우에 다음의 방법을 사용할 것을 권장한다.
[ 메소드 주입을 이용한 방법 ]
만약 우리가 원하는 객체를 찾기 위해 의존성 검색(Dependency LookUp, DL)을 사용해야 한다면 Provider를 사용하면 된다.
Provider는 JSR-330에 추가된 자바 표준 기술로, ObjectFactory와 거의 유사하게 <T> 타입 파리미터와 get()이라는 팩토리 메소드를 갖는 인터페이스이다.
기존의 ObjectFactory는 ObjectFactoryCreatingFactoryBean를 빈으로 등록해주어야 하는 번거로움이 있었지만, Provider는 Spring이 Provider의 구현체를 생성해준다는 점에서 훨씬 편리하다. 또한 ObjectFactory처럼 사용 방법이 단순하여 쉽게 사용가능하다.
@RestController
@RequiredArgsConstructor
public class TestController {
private final Provider<TestService> provider;
@GetMapping("/test")
public ResponseEntity<TestService> test() {
final TestService testService = provider.get();
return ResponseEntity.ok(testService);
}
}
Provider 인터페이스는 Spring API가 아니라 JavaEE6에 추가된 표준 인터페이스이기 때문에 Spring API인 ObjectFactory보다 호환성이 좋다. 또한 별도로 빈을 등록해주어야 하는 코드가 없어 사용에 용이하며, 테스트 코드를 작성하기 편리하다.
하지만 만약 여러 타입의 빈이 있다면 get() 호출 시에 org.springframework.beans.factory.NoUniqueBeanDefinitionException가 발생하므로 주의해야 한다.
'Spring' 카테고리의 다른 글
[Spring] Spring에서 트랜잭션의 사용법 - (3/3) (6) | 2021.07.09 |
---|---|
[Spring] Spring 트랜잭션의 세부 설정(전파 속성, 격리수준, 읽기전용, 롤백/커밋 예외 등) - (2/3) (0) | 2021.07.09 |
[Spring] 설정 값 분리의 필요성과 @Value의 사용법 및 동작 과정 (0) | 2021.07.04 |
[Spring] AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)의 이해 - (1/3) (4) | 2021.06.02 |
[Spring] 도메인 주도 설계 및 개발과 도메인 계층(도메인 객체 중심 개발) (9) | 2021.06.02 |