Issue
What Spring Framework hook do I use on dynamically registered third-party beans?
I have a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/support/BeanDefinitionRegistryPostProcessor.html#postProcessBeanDefinitionRegistry-org.springframework.beans.factory.support.BeanDefinitionRegistry-" rel="nofollow noreferrer">BeanDefinitionRegistryPostProcessor
that I am using to dynamically classpath-scan and instantiate multiple third-party beans (gRPC AbstractStub
instances). I need to register ClientInterceptors
on the stub so that the augmented AbstractStub
is ready for application processing. I use dynamically-created *Stub
@Beans
to eliminate all the @Bean
boilerplate and ensure consistent channel configurations.
Constraints
- The
AbstractStub
implementations are gRPC-generated classes. My classes extendAbstractStub
. - The preferred static factory method to use is the
builder(Channel)
method; this is what is used when manually boilerplating@Bean
declarations. - Each stub requires a
Channel
as a dependency. There are multipleChannel
@Beans
.
Attempts
I tried three approaches:
Approach 1: BeanDefinitionBuilder
+ Supplier
Function
BeanDefinitionBuilder.genericBeanDefinition(Class, Supplier)
did not allow injecting the Channel
dependency.
void registerBeanDefintion(final Class<S> clazz, final BeanDefinitionRegistry registry) {
Supplier<S> stubSupplier = () -> {
clazz.getConstructor({Channel.class});
return BeanUtils.instantiateClass(constructor, null); // fails here; no Channel
}
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(clazz, stubSupplier);
builder.addDependsOn(MANAGED_CHANNEL_BEAN_NAME);
builder.addConstructorArgReference(MANAGED_CHANNEL_BEAN_NAME);
registry.registerBeanDefinition(clazz.getName(), builder.getBeanDefinition());
Approach 2: BeanDefinitionBuilder
with CallOption
hooks
Unable to register a ClientInterceptor
on a BeanDefinition.
void registerBeanDefintion(final Class<S> clazz, final BeanDefinitionRegistry registry) {
builder.addDependsOn(MANAGED_CHANNEL_BEAN_NAME);
builder.addConstructorArgReference(MANAGED_CHANNEL_BEAN_NAME);
CallOptions callOptions = CallOptions.DEFAULT;
// no hook in CallOptions to register ClientInterceptor
registry.registerBeanDefinition(clazz.getName(), builder.getBeanDefinition());
Approach 3: postProcessBeanFactory()
postProcessBeanFactory
doesn't operate on instantiated beans, so dependencies are not pre-resolved.
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
Iterator<String> iterator = configurableListableBeanFactory.getBeanNamesIterator();
while (iterator.hasNext()) {
String beanName = iterator.next();
if (beanName.endsWith("Stub")) {
AbstractStub stub = (AbstractStub) configurableListableBeanFactory.getBean(beanName); //fails
stub.withInterceptors(newClientInterceptor()); // never gets executed
}
}
}
Solution
I was over-complicating it a bit since I had some separate modules: The solution is to use a simple BeanPostProcessor
and invoke withInterceptors()
for only the AbstractStub
instances:
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof AbstractStub) {
AbstractStub stub = (AbstractStub) bean;
log.debug("modify bean '{}': add timeout client interceptor", beanName);
ClientInterceptor timeoutClientInterceptor = this.newTimeoutClientInterceptor(stub);
AbstractStub result = stub.withInterceptors(timeoutClientInterceptor);
return result;
}
return bean;
}
ClientInterceptor newTimeoutClientInterceptor(final AbstractStub stub) {
final Deadline deadline = this.getDeadlineTimeout(stub);
return new ClientInterceptor() {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
final ClientCall<ReqT, RespT> clientCall = next.newCall(method, callOptions.withDeadline(deadline));
return new ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT>(clientCall) {
@Override
protected void checkedStart(Listener<RespT> listener, Metadata metadata) {
log.debug("execute call with deadline {}", deadline);
delegate().start(listener, metadata);
}
};
}
};
}
Answered By - JJ Zabkar
Answer Checked By - Cary Denson (JavaFixing Admin)