I am trying to add custom interceptor in my spring boot web service project. I follow this example and created this config:
package org.example;
import java
Here's another possibility to solve this issue. This relates to following Stack overflow question: Spring WS interceptors with injected DAO's @Transactional not working. In short the issue comes from the fact that the method
@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
gets called before Spring dependency injection has time to register Spring AOP beans. In my case it was @Transactional that was ignored by Spring-WS but it could be anything.
Fortunately for us Spring-WS uses mutable collections instead of immutable. When the addInterceptors()
method
gets called, we can just save the collection and thus we have a reference to the same collection instance that is used by Spring-WS. Later on you can initialize your interceptor bean properly and add it to the collection.
You also have to get around the fact that if you use @Autowired
the bean gets prepared before the annotations can take place. Thus you have to create it manually by calling ApplicationContext.getBean()
method.
@EnableWs
@Configuration
// The magic is to implement both ApplicationContextAware
// that injects the applicationContext for us
// and BeanPostProcessor that gives us postProcessBeforeInitialization()
// where we initialize our interceptor correctly
// and add it to the collection
public class WebServiceConfig extends WsConfigurerAdapter implements ApplicationContextAware, BeanPostProcessor {
// This is the interceptor that uses dependencies with @Transactional annotation.
// It will not work with @Autowired
private MyInterceptorThatHasTransactionalDependencies myInterceptorThatHasTransactionalDependencies;
// Fortunately Spring WS uses mutable collections so we can fill
// this list later on as long as we just initialize it with
private List<EndpointInterceptor> interceptors;
// This is our application context where all the beans are defined
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// save application context for later use
this.context = applicationContext;
}
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// This method gets called multiple times so initialize interceptor just once
if(myInterceptorThatHasTransactionalDependencies == null){
myInterceptorThatHasTransactionalDependencies = context.getBean(MyInterceptorThatHasTransactionalDependencies.class);
interceptors.add(myInterceptorThatHasTransactionalDependencies);
}
return bean;
}
@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
// Save the list of interceptors so we can modify it later on
this.interceptors = interceptors;
if (myInterceptorThatHasTransactionalDependencies == null) {
System.out.println("myInterceptorThatHasTransactionalDependencies was null like we expected");
} else {
interceptors.add(myInterceptorThatHasTransactionalDependencies);
}
}
}
Just to let you know that I am not Spring bean lifecycle expert, so there might be a better place to situate the interceptor initialization than postProcessBeforeInitialization()
. That said, this works for me.
The problem is the initialization sequence in Spring. Technically, because there is a BeanPostProcessor
for the WS Endpoint (AnnotationActionEndpointMapping
in spring-ws), it will force the early initialization of any dependencies this needs - especially any EndpointInterceptor
beans.
One way to counter this is to rearrange the BeanPostProcessor's, or even create one of your own, but usually its simpler to stay with the default configuration in Spring - to avoid similar surprises elsewhere in the initialization sequence.
Perhaps a simpler way to avoid the problem is to use an ObjectFactory
in the EndpointInterceptor
bean. This will delay instantiating the AppConfig
bean until it is referenced, by which time the Aop weaving will also have taken place.
@Component
public class CustomValidatingInterceptor extends PayloadValidatingInterceptor {
@Autowired
private ObjectFactory<AppConfig> konfigurace;
@Override
public boolean handleRequest(MessageContext messageContext, Object endpoint)
throws IOException, SAXException, TransformerException {
System.out.println("is config aop proxy in interceptor: " +
AopUtils.isAopProxy(konfigurace.getObject()));
return super.handleRequest(messageContext, endpoint);
}
Clearly, this then means the CustomValidatingInterceptor
must be referenced from WsConfig
as an injected (autowired) bean.
Thanks for the example - there's a fork here that uses the ObjectFactory
technique. This showed the config
bean as an Aop proxy in all of WsConfig.testAop()
, the CountryEndpoint
and the CustomValidatingInterceptor
when I sent a request in from SoapUI.