복붙노트

[SPRING] @EnableWs가 Spring 빈에서 aop 프록시를 제거한 이유

SPRING

@EnableWs가 Spring 빈에서 aop 프록시를 제거한 이유

스프링 부트 웹 서비스 프로젝트에 사용자 정의 인터셉터를 추가하려고합니다. 나는이 예제를 따르고이 설정을 만들었다.

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 메소드에 새로운 인터셉터를 추가 할 때 config 클래스의 remove aop proxy에 문제가 있습니다. 어떤 생각? 전체 프로젝트는 자식이다.

해결법

  1. ==============================

    1.문제는 Spring의 초기화 순서이다. 기술적으로, WS 엔드 포인트 (Spring-ws의 AnnotationActionEndpointMapping)를위한 BeanPostProcessor가 있기 때문에, 이것은 모든 의존성 (특히 EndpointInterceptor beans)의 초기 초기화를 강요합니다.

    문제는 Spring의 초기화 순서이다. 기술적으로, WS 엔드 포인트 (Spring-ws의 AnnotationActionEndpointMapping)를위한 BeanPostProcessor가 있기 때문에, 이것은 모든 의존성 (특히 EndpointInterceptor beans)의 초기 초기화를 강요합니다.

    이를 막기위한 한 가지 방법은 BeanPostProcessor를 재배치하거나, ​​심지어 자신의 하나를 생성하는 것입니다. 그러나 초기화 시퀀스의 다른 곳에서도 비슷한 놀라움을 피하기 위해 Spring의 기본 구성을 유지하는 것이 더 간단합니다.

    문제를 피하는 간단한 방법은 EndpointInterceptor 빈에서 ObjectFactory를 사용하는 것입니다. 이것은 참조 될 때까지 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 기술을 사용하는 포크가 있습니다. 이것은 WsConfig.testAop (), CountryEndpoint 및 CustomValidatingInterceptor 모두에서 Aop 프록시로서 config bean을 보여 주었고, SoapUI에서 요청을 보냈습니다.

  2. ==============================

    2.이 문제를 해결할 또 다른 가능성이 있습니다. 스택 오버플로 문제는 다음과 관련이 있습니다. 삽입 된 DAO의 @Transactional이 작동하지 않는 Spring WS 인터셉터. 요컨대,이 문제는이 방법이

    이 문제를 해결할 또 다른 가능성이 있습니다. 스택 오버플로 문제는 다음과 관련이 있습니다. 삽입 된 DAO의 @Transactional이 작동하지 않는 Spring WS 인터셉터. 요컨대,이 문제는이 방법이

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

    Spring 의존성 주입 전에 Spring AOP beans를 등록 할 시간이 있기 전에 호출된다. 제 경우에는 @Transactional이 Spring-WS에 의해 무시되었지만 그것은 아무것도 될 수 있습니다.

    다행스럽게도 Spring-WS는 변경 불가능한 컬렉션 대신 변경 가능한 컬렉션을 사용합니다. addInterceptors () 메소드가 실행될 때 콜렉션을 저장할 수 있기 때문에 Spring-WS가 사용하는 콜렉션 인스턴스에 대한 참조가있다. 나중에 당신은 인터셉터 빈을 적절하게 초기화하고 그것을 콜렉션에 추가 할 수 있습니다.

    또한 @Autowired를 사용하면 주석이 준비되기 전에 bean이 준비된다는 사실을 알아야합니다. 따라서 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);
            }
        }
    }
    

    필자가 Spring bean lifecycle 전문가가 아니므로 postProcessBeforeInitialization ()보다 인터셉터 초기화의 위치를 ​​정할 수있는 좋은 곳이있을 수 있습니다. 즉, 이것은 나를 위해 작동합니다.

  3. from https://stackoverflow.com/questions/50072312/why-enablews-removed-aop-proxy-from-spring-bean by cc-by-sa and MIT license