Why @EnableWs removed aop proxy from spring bean

后端 未结 2 1034
旧巷少年郎
旧巷少年郎 2021-01-15 17:58

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         


        
相关标签:
2条回答
  • 2021-01-15 18:24

    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.

    0 讨论(0)
  • 2021-01-15 18:42

    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.

    0 讨论(0)
提交回复
热议问题