하는 방법은 검색해보면 쉽게 찾을 수 있다.
글을 쓰는 이유는 실무에서 어떤 경우에 발생하는지 기록을 하기 위함이다.
스프링이 관리하는 객체, 즉 빈은 의존하고 있는 것이 명시적으로 드러날 때는 초기화 과정에서 필요한 빈들을 먼저 초기화를 해준다.
하지만 간접적으로 빈을 사용하는 경우 초기화 시점에 필요한 빈이 없을 수 있기 때문에 애플리케이션에서 실패가 발생한다.
내가 경험한 사례는 다음과 같다.
TCP/IP 전문 통신을 해야해서 Spring Integration프로젝트 중 TCP and UDP Support 기능을 이용했다.
Spring Integration 5.0 부터는 Java DSL을 통한 설정이 가능하기에 Configuration은 아래와 같이 구성했다.
@Configuration
@EnableIntegration
public class R2D2Configuration {
@Bean
public IntegrationFlow client(TcpMessageMapper mapper) {
return IntegrationFlows.from(R2d2Gateway.class)
.handle(Tcp.outboundGateway(Tcp.netClient(r2d2Properties.getServerAddress(), r2d2Properties.getServerPort())
.serializer(ByteArrayLfSerializer.INSTANCE)
.deserializer(ByteArrayNulSerializer.INSTANCE)
.mapper(mapper)))
.get();
}
실제 빈으로 주입받는 타입은 아래와 같은 인터페이스이다.
public interface R2D2Gateway {
R2D2pprovalResponse approve(@Payload R2D2ApprovalRequest r2d2ApprovalRequest);
R2D2CancellationResponse cancel(@Payload R2D2CancellationRequest r2d2CancelRequest);
}
IntegrationFlowBuilder 에 의한 빈 초기화 과정
IntegrationFlowBuilder 의 from 인자로는 serviceInterface를 받는다.
이 넘겨받은 serviceInterface는 GatewayProxyFactoryBean의 생성자의 파라미터로 전달된다.
public class GatewayProxyFactoryBean extends AbstractEndpoint
implements TrackableComponent, FactoryBean<Object>, MethodInterceptor, BeanClassLoaderAware {
private volatile Class<?> serviceInterface;
public GatewayProxyFactoryBean(Class<?> serviceInterface) {
this.serviceInterface = serviceInterface;
}
@Override
public Class<?> getObjectType() {
return (this.serviceInterface != null ? this.serviceInterface : null);
}
GatewayProxyFactoryBean 은 FactoryBean 인터페이스를 구현하는데, 객체타입을 반환하는 getObjectType() 에 전달받은 serviceInterface를 돌려준다.
IntegrationFlowBeanPostProcessor 에 의해 빈 후처리기가 호출되면 registerComponent라는 메서드를 통해 beanFactory에 빈으로 등록이 되게 된다.
public class IntegrationFlowBeanPostProcessor
implements BeanPostProcessor, ApplicationContextAware, SmartInitializingSingleton {
private Object processStandardIntegrationFlow(StandardIntegrationFlow flow, String flowBeanName) {
String flowNamePrefix = flowBeanName + ".";
if (this.flowContext == null) {
this.flowContext = this.beanFactory.getBean(IntegrationFlowContext.class);
}
boolean useFlowIdAsPrefix = this.flowContext.isUseIdAsPrefix(flowBeanName);
int subFlowNameIndex = 0;
int channelNameIndex = 0;
Map<Object, String> integrationComponents = flow.getIntegrationComponents();
Map<Object, String> targetIntegrationComponents = new LinkedHashMap<>(integrationComponents.size());
for (Map.Entry<Object, String> entry : integrationComponents.entrySet()) {
Object component = entry.getKey();
if (component instanceof ConsumerEndpointSpec) {
// ..
}
else {
if (noBeanPresentForComponent(component, flowBeanName)) {
if (component instanceof AbstractMessageChannel || component instanceof NullChannel) {
// ..
}
else if (component instanceof MessageChannelReference) {
// ..
}
else if (component instanceof FixedSubscriberChannel) {
// ..
}
else if (component instanceof SourcePollingChannelAdapterSpec) {
// ..
}
else if (component instanceof StandardIntegrationFlow) {
// ..
}
else if (component instanceof AnnotationGatewayProxyFactoryBean) {
AnnotationGatewayProxyFactoryBean gateway = (AnnotationGatewayProxyFactoryBean) component;
String gatewayId = entry.getValue();
if (gatewayId == null) {
gatewayId = gateway.getComponentName();
}
if (gatewayId == null) {
gatewayId = flowNamePrefix + "gateway";
}
registerComponent(gateway, gatewayId, flowBeanName,
beanDefinition -> {
((AbstractBeanDefinition) beanDefinition)
.setSource(new DescriptiveResource("" + gateway.getObjectType()));
});
private void registerComponent(Object component, String beanName, String parentName,
BeanDefinitionCustomizer... customizers) {
BeanDefinition beanDefinition =
BeanDefinitionBuilder.genericBeanDefinition((Class<Object>) component.getClass(), () -> component)
.applyCustomizers(customizers)
.getRawBeanDefinition();
((BeanDefinitionRegistry) this.beanFactory).registerBeanDefinition(beanName, beanDefinition);
if (parentName != null) {
this.beanFactory.registerDependentBean(parentName, beanName);
}
this.beanFactory.getBean(beanName);
}
결국 R2D2Gateway 타입의 빈이 등록이 되는 것이다. (프록시 객체이다.)
R2D2Gateway 사용처
실제 R2D2Gateway를 사용하는 서비스는 아래와 같다. 생성자 주입을 받는다.
@Service
public class R2D2Service {
private final R2D2Gateway r2d2Gateway;
public R2D2Service(R2D2Gateway r2d2Gateway) {
this.r2d2Gateway = r2d2Gateway;
}
IntelliJ IDEA 에서는 R2D2Gateway가 빈으로 표시된 것이 없기에 빨간 색으로 나온다.
인터페이스에 @Component 을 붙이면 IDE의 경고는 없어지지만 의존성 문제는 해결되지 않는다.
발생하는 에러
초기화시 아래와 같은 에러가 발생한다.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 1 of constructor in com.tistory.namocom.service.r2d2Service required a bean of type 'com.tistory.namocom.R2D2Gateway' that could not be found.
Action:
Consider defining a bean of type 'com.tistory.namocom.R2D2Gateway' in your configuration.
Process finished with exit code 1
R2D2Configuration 에서 생성되는 빈의 타입은 IntegrationFlow 이지만 필요로 하는 것은 R2kGateway 타입이다.
따라서 아래와 같이 @Qualifier 를 지정하는 것은 마찬가지로 실패이다.
@Service
public class R2D2Service {
private final R2D2Gateway r2d2Gateway;
public R2D2Service(@Qualifier("client") R2D2Gateway r2d2Gateway) {
this.r2d2Gateway = r2d2Gateway;
}
결국은 R2D2Service에 IntegrationFlow 에 대한 의존성을 거는 것으로 해결을 하였다.
@Service
@DependsOn("client")
public class R2D2Service {
private final R2D2Gateway r2d2Gateway;
public R2D2Service(R2D2Gateway r2d2Gateway) {
this.r2d2Gateway = r2d2Gateway;
}
빈 이름이 너무 일반적인 이름(client)라서 Configuration 의 빈 이름을 바꾸는 것이 좋다고 생각이 들었다.
@Configuration
@EnableIntegration
public class R2D2Configuration {
@Bean
public IntegrationFlow r2d2Client(TcpMessageMapper mapper) {
그 외
ArgumentsHolder argsHolder at org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor
org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency 의 finally
org.springframework.beans.factory.support.ConstructorResolver#setCurrentInjectionPoint
'Programing > Framework' 카테고리의 다른 글
[Spring] mvc 예외처리 (0) | 2020.02.07 |
---|---|
[Spring] ServiceLocatorFactoryBean (0) | 2020.01.31 |
[Spring] ClientHttpResponse 인터페이스 계층 구조 (0) | 2020.01.08 |
[Sonarqube] Spring 기본 테스트 (0) | 2019.12.12 |
[Spring] Bean 생성시 필드주입 시점은? (0) | 2019.10.17 |