Почему @EnableWs удалил прокси из Spring bean - PullRequest
0 голосов
/ 28 апреля 2018

Я пытаюсь добавить пользовательский перехватчик в моем проекте веб-службы весенней загрузки. Я следую этому примеру и создал этот конфиг:

package org.example;

import java.util.List;

import org.aspect.PersistentAspect;
import org.springframework.aop.support.AopUtils;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.server.EndpointInterceptor;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class WsConfig extends WsConfigurerAdapter {

    @Bean
    public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
        final MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        servlet.setTransformWsdlLocations(true);
        return new ServletRegistrationBean(servlet, "/v1/*");
    }

    @Bean
    public XsdSchema schema() {
        return new SimpleXsdSchema(new ClassPathResource("country.xsd"));
    }

    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        String[] jaxbContext = new String[] { "io.spring.guides.gs_producing_web_service" };
        marshaller.setContextPaths(jaxbContext);
        return marshaller;
    }

    @Override
    public void addInterceptors(List<EndpointInterceptor> interceptors) {
        // aop not working
        interceptors.add(new CustomValidatingInterceptor(schema(), config()));
        // aop working
        // interceptors.add(new CustomValidatingInterceptor(schema(), null));
    }

    @Bean
    public AppConfig config() {
        return new AppConfig();
    }

    @Bean
    public PersistentAspect persistentAspect() {
        PersistentAspect persistentAspect = new PersistentAspect();
        return persistentAspect;
    }

    @Bean
    public Object testAop() {
        System.out.println("is config aop proxy: " + AopUtils.isAopProxy(config()));

        return null;
    }
}

Однако, когда я добавляю новый перехватчик в методе addInterceptors, у меня проблема с удаленным прокси-сервером в моем классе конфигурации. Есть идеи почему? Весь проект на git .

Ответы [ 2 ]

0 голосов
/ 14 мая 2019

Вот еще одна возможность решить эту проблему. Это относится к следующему вопросу переполнения стека: Перехватчики Spring WS с введенным @Transactional DAO не работают . Короче говоря, проблема заключается в том, что метод

@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {

вызывается до того, как внедрение зависимостей Spring успеет зарегистрировать bean-компоненты Spring AOP. В моем случае это был @Transactional, который был проигнорирован Spring-WS, но это могло быть что угодно.

К счастью для нас, Spring-WS использует изменяемые коллекции вместо неизменяемых. Когда метод addInterceptors() вызывается, мы можем просто сохранить коллекцию, и, таким образом, у нас есть ссылка на тот же экземпляр коллекции, который используется Spring-WS. Позже вы можете правильно инициализировать ваш bean-объект-перехватчик и добавить его в коллекцию.

Вы также должны обойти тот факт, что если вы используете @Autowired, бин готовится до того, как аннотации могут иметь место. Таким образом, вы должны создать его вручную, вызвав метод ApplicationContext.getBean().

@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);
        }
    }
}

Просто чтобы вы знали, что я не являюсь экспертом по жизненному циклу bean-компонента Spring, так что может быть лучшее место для инициализации перехватчика, чем postProcessBeforeInitialization(). Тем не менее, это работает для меня.

0 голосов
/ 28 апреля 2018

Проблема в последовательности инициализации в Spring. Технически, поскольку существует BeanPostProcessor для конечной точки WS (AnnotationActionEndpointMapping в spring-ws), это вызовет раннюю инициализацию любых необходимых зависимостей, особенно любых EndpointInterceptor bean-компонентов.

Один из способов противостоять этому - переставить BeanPostProcessor или даже создать свой собственный, но обычно проще оставаться в конфигурации по умолчанию в Spring - чтобы избежать подобных неожиданностей в других местах последовательности инициализации.

Возможно, более простой способ избежать этой проблемы - использовать ObjectFactory в компоненте EndpointInterceptor. Это задержит создание экземпляра бина AppConfig до тех пор, пока на него не будут ссылаться, к этому времени также произойдет ткачество Aop.

@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);
    }

Понятно, что тогда это означает, что CustomValidatingInterceptor должен ссылаться на WsConfig как внедренный (с автоматическим подключением) компонент.

Спасибо за пример - здесь есть вилка , использующая технику ObjectFactory. Это показало bean-компонент config в качестве прокси Aop во всех WsConfig.testAop(), CountryEndpoint и CustomValidatingInterceptor, когда я отправил запрос из SoapUI.

...