티스토리 뷰
이전 포스팅을 보지 않았다면 보고 오자.
DI와 DL
이전 포스팅에서 ApplicationContext를 이용해 getBean() 메서드를 호출하는 방식을 이용했다.
이건 DL을 사용한 것이다.
🤔
번거롭게 DL 방식을 사용하지 않고 프로토타입 빈을 직접 DI에서 사용하는 걸 어떨까?
다음은 직접 DI를 사용한 코드이다.
@Autowired
ServiceRequest serviceRequest;
public void serviceRequestFormSubmit(HttpServletRequest request) {
this.serviceRequest.setCustomerNo(request.getParameter("custno");
// ...
}
이 코드를 운영 시스템에 적용하면 매우 심각하다.
웹 컨트롤러도 다른 대부분의 빈처럼 싱글톤이다.
즉, 단 한 번만 만들어진다.
문제는 DI 작업은 빈 오브젝트가 처음 만들어질 때 단 한 번만 진행된다는 점이다.
따라서 아무리 ServiceRequest 빈을 프로토타입으로 만들었다고 하더라도 컨트롤러에 DI 하기 위해 컨테이너에 요청할 때 딱 한 번만 오브젝트가 생성되고 더 이상 새로운 ServiceRequest 오브젝트는 만들어지지 않는다.
결국 여러 사용자가 동시에 요청을 보내면 serviceRequest 오브젝트 하나가 공유되어 서로 데이터를 덮어써 버리는 문제가 발생할 수 있다.
프로토타입 빈이 DI 방식으로 사용되는 경우는 극히 드물다.
여러 스레드가 공유해서 사용하는 것은 상관없지만 DI 받는 빈마다 다른 오브젝트를 사용해야 하는 특별한 이유가 있다면 그 때는 프로토타입 빈을 DI로 사용할 수 있다.
프로토타입 빈의 DL 전략
ApplicationContext를 DI 받아둔 뒤에 코드에서 getBean() 메서드를 호출하는 방법은 가장 단순하고 직접적이다.
하지만 POJO 방식으로 개발할 수 있다는 것이 스프링의 장점인데, 스프링 API를 직접 사용하는 것이 마음에 안 들 수 있다.
게다가 단위 테스트를 작성할 때 ApplicationContext라는 거대한 인터페이스의 mock 오브젝트를 만들어야 한다.
스프링은 ApplicationContext 외에도 다양한 방법을 제공한다.
[1]. ApplicationContext, BeanFactory
이미 이전 포스팅에서 사용한 방법이다.
[2]. ObjectFactory, ObjectFactoryCreatingFactoryBean
팩토리를 사용하여 중간에 컨텍스트에 getBean()을 호출해주는 역할을 맡을 오브젝트를 두면 된다.
팩토리를 사용하는 이유는 오브젝트를 요구하면서 오브젝트를 어떻게 생성하거나 가져오는지에는 신경 쓰지 않을 수 있기 때문이다.
1번 방식을 사용하는 팩토리를 하나 만들어서 빈으로 등록해두고, 이 팩토리 역할을 하는 빈을 DI 받아서 필요할 때 getObject()와 같은 메서드를 호출해 빈을 가져올 수 있도록 만드는 방법이다.
다음 그림은 팩토리를 이용해 프로토타입 빈을 애플리케이션 컨텍스트에서 가져오는 DI 구조다.
스프링의 ObjectFactory 인터페이스는 타입 파라미터와 getObject()라는 간단한 팩토리 메서드를 가진다.
타입 파라미터가 있으므로 다음과 같이 사용할 수 있다.
ObjectFactory<ServiceReqeust> factory = ...;
ServiceRequest request = factory.getObject();
ObjectFactory의 구현 클래스는 이미 스프링에서 제공한다. (ObjectFactoryCreatingFactoryBean)
사용 방법은 다음과 같이 getBean()으로 가져올 빈의 이름을 넣어서 등록해주면 된다.
<bean id="serviceRequestFactory"
class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName" value="serviceRequest" />
</bean>
이제 serviceRequestFactory 빈을 ServiceRequest를 사용할 컨트롤러에 DI 해주고 다음과 같이 사용하면 된다.
// ObjectFactory 타입은 여러 개 있을 수 있으모로 이름으로 빈을 지정하는 편이 낫다
@Resource
private ObejctFactory<ServiceRequest> serviceRequestFactory;
public void serviceRequestFormSubmit(HttpServletRequest request) {
ServiceRequest serviceRequest = this.serviceRequestFactory.getObject();
serviceRequest.setCustomerNo(request.getParameter("custno");
// ...
}
위처럼 XML 설정을 사용하고 싶지 않다면 다음과 같이 자바 코드에 의한 빈 등록을 이용해도 된다.
@Configuration
public class ObjectFactoryConfig {
@Bean
public ObjectFactoryCreatingFactoryBean serviceRequestFactory() {
ObjectFactoryCreatingFactoryBean factoryBean =
new ObjectFactoryCreatingFactoryBean();
factoryBean.setTargetBeanName("serviceRequest");
return factoryBean;
}
}
[3]. ServiceLocatorFactoryBean
ObjectFactory가 단순하고 깔끔하지만 프레임워크의 인터페이스를 애플리케이션 코드에서 사용하는 것이 마음에 안들 수 있다.
이럴 땐 ServiceLocatorFactoryBean을 사용하면 된다.
ServiceLocatorFactoryBean은 DL 방식으로 가져올 빈을 리턴하는 임의의 이름을 가진 메서드가 정의된 인터페이스가 있으면 된다.
다음 코드를 살펴보자.
public interface ServiceLocatorFactory {
ServiceRequest getServiceFactory();
}
메서드 이름은 어떻게 지어도 상관없다.
이렇게 정의한 인터페이스를 이용해 스프링의 ServiceLocatorFactoryBean으로 빈을 등록해주면 된다.
<bean class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="serviceLocatorInterface" value=".. ServiceRequestFactory" />
</bean>
범용적으로 사용하는 ObjectFactory와 달리 ServiceRequest 전용으로 만든 인터페이스가 이 빈의 타입이 되기 때문에 @Autowired을 이용해 타입으로 가져올 수 있다.
빈을 이름으로 접근할 필요가 없을 때는 위의 빈 선언처럼 id를 생략할 수도 있다.
컨트롤러에서 사용할 때에는 다음 코드와 같이 사용하면 된다.
@Autowired
SerivceRequestFactory serviceRequestFactory;
public void serviceRequestFormSubmit(HttpServletRequest request) {
ServiceRequest serviceRequest =
this.serviceRequestFactory.getServiceFactory();
serviceRequest.setCustomerNo(request.getParameter("custno");
// ...
}
[4]. 메서드 주입
위 방법들은 빈을 새로 추가해야 하는 번거로움이 있다.
메서드 주입은 @Autowired를 메서드에 붙여서 메서드 파라미터에 의해 DI 되게 하는 메서드를 이용한 주입 방식과 혼동하면 안 된다.
메서드 주입은 메서드를 통한 주입이 아닌 메서드 코드 자체를 주입하는 것을 말한다.
메서드 주입은 일정한 규칙을 따르는 추상 메서드를 작성해두면 getBean()을 사용하여 새로운 프로토타입 빈을 가져오는 기능을 담당하는 메서드를 런타임 시에 추가해주는 기술이다.
다음 코드와 같이 컨트롤러에 추상 메서드를 선언해둔다.
abstract public ServiceRequest getServiceRequest();
public void serviceRequestFormSubmit(HttpServletRequest request) {
ServiceRequest serviceRequest = this.getServiceRequest();
serviceRequest.setCustomerNo(request.getParameter("custno");
// ...
}
추상 메서드를 가졌으므로 클래스도 추상 클래스로 정의돼야 한다.
이제 이 추상 클래스를 확장해서 getServiceRequest()라는 추상 메서드를 주입해주도록 스프링 빈을 다음과 같이 정의한다.
<bean id="serviceReqeustController"
class="...ServiceReqeustController">
<lookup-method name="getServiceRequest" bean="serviceRequest" />
</bean>
<lookup-method>라는 태그의 name이 스프링이 구현해줄 추상 메서드의 이름이고, bean은 메서드에서 getBean()으로 가져올 빈의 이름이다.
메서드 주입 방식은 그 자체로 스프링 API에 의존적이 아니므로 스프링 외의 환경에 가져다 사용이 가능하고 컨테이너의 도움없이 단위 테스트도 가능하다.
하지만 클래스 자체가 추상 클래스이므로 테스트에서 사용할 때 상속을 통해 추상 메서드를 오바라이드한 뒤에 사용해야 한다.
[5]. Provider<T>
Provider는 ObjectFactory와 거의 유사하게 <T> 타입 파라미터와 get()이라는 팩토리 메서드를 가진 인터페이스다.
기본 개념과 사용 방법은 ObejctFactory와 유사하나 빈을 등록해주지 않아도 되기 때문에 사용이 편리하다.
Provider 인터페이스를 @Inject, @Autowired, @Resource 중의 하나를 이용해 DI 되도록 지정해주기만 하면 스프링이 자동으로 Provider를 구현한 오브젝트를 생성해서 주입해준다.
팩토리 빈을 XML이나 @Configuration 자바 코드로 정의해주지 않아도 ObjectFactory처럼 손쉽게 사용할 수 있다.
Provider를 사용할 때에는 다음 코드와 같이 타입 파라미터로 생성할 빈의 타입을 넣어주기만 하면 된다.
@Inject
Provider<ServiceRequest> serviceRequestProvider;
public void serviceRequestFormSubmit(HttpServletRequest request) {
ServiceRequest serviceRequest = this.serviceRequestProvider.get();
serviceRequest.setCustomerNo(request.getParameter("custno");
// ...
}
Provider는 자바 6 표준 인터페이스이기 때문에 스프링 API인 ObjectFactory를 사용할 때보다 호환성이 좋다.
'Sping Framework' 카테고리의 다른 글
JPA에서의 bulk insert, bulk update test (0) | 2022.08.14 |
---|---|
프로토타입과 스코프 [3]. 스코프 (0) | 2022.07.15 |
프로토타입과 스코프 [1]. 프로토타입 빈의 용도 (0) | 2022.07.12 |
싱글톤 레지스트리 (0) | 2022.07.12 |
스프링의 IoC (0) | 2022.07.10 |
- Total
- Today
- Yesterday
- JPA
- 클린 코드
- Spring Boot
- 프로그래머스
- 객체지향
- 디자인 패턴
- 디자인패턴
- node.js
- C++
- 이팩티브 자바
- 클린 아키텍처
- Olympiad
- MSA
- Kotlin
- programmers
- Spring
- 코테
- kkoon9
- 알고리즘
- BOJ
- 백준
- AWS
- Effective Java
- Algorithm
- 이펙티브 자바
- kotest
- BAEKJOON
- 정규표현식
- Java
- 테라폼
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |