注入 DAO 的 @Transactional 的 Spring WS 拦截器不起作用

Posted

技术标签:

【中文标题】注入 DAO 的 @Transactional 的 Spring WS 拦截器不起作用【英文标题】:Spring WS interceptors with injected DAO's @Transactional not working 【发布时间】:2016-03-16 19:42:03 【问题描述】:

我们有一个遗留的基于 XML 的配置 spring-ws 应用程序,其中包含注入 DAO 以从数据库获取配置的端点拦截器。这些 DAO 注入了休眠 sessionFactory。

当我们升级到 spring 4.2.0.RELEASE(来自 spring 3.2.5.RELEASE)和 spring-ws 2.2.1.RELEASE(来自 spring-ws 2.1.4.RELEASE)时,我注意到 DAO 没有一个代理对象,似乎拦截器要去 AnnotationActionEndpointMapping 类而不是 PayloadRootAnnotationMethodEndpointMapping 类。

因此,我创建了一个基于 spring-boot 版本 1.3.0.RELEASE 的示例,该示例概述了我们的遗留应用程序,并且该问题在基于 XML 的配置和基于注释的配置中都很明显。请注意 注释@EnableTransactionManagement 存在于示例中,并且存在于遗留应用程序中。

如果您从应用程序上下文中注释掉或从@Congiuration 对象中注释掉@EnableWS,那么DAO 是一个代理对象,拦截器似乎会到达正确的端点(即PayloadRootAnnotationMethodEndpointMapping)并且单元测试在没有事务的情况下工作错误。

StackTrace 或 EnableWS 未被注释掉。

org.springframework.ws.soap.client.SoapFaultClientException: Could not obtain transaction-synchronized Session for current thread
    at org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:38)
    at org.springframework.ws.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:830)
    at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:624)
    at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:555)
    at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:390)
    at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:378)
    at hello.ApplicationTests.testSendAndReceive(ApplicationTests.java:61)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

导致上述异常的 XML 配置摘录:

    <sws:annotation-driven>
    <sws:interceptors>
           <ref bean="loggingInterceptorAU"/>
    </sws:interceptors>


<bean id="loggingInterceptorAU" class="hello.interceptor.LoggingEndpointInterceptor"/>

导致上述异常的注解配置摘录:

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter 

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

    @Bean(name = "countries")
    public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) 
        DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
        wsdl11Definition.setPortTypeName("CountriesPort");
        wsdl11Definition.setLocationUri("/ws");
        wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service");
        wsdl11Definition.setSchema(countriesSchema);
        return wsdl11Definition;
    

    @Bean
    public XsdSchema countriesSchema() 
        return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
    

    /**
     * Declaring the loggingInterceptor.
     * @return the new logging interceptor.
     */
    @Bean
    public LoggingEndpointInterceptor loggingInterceptor() 
        LoggingEndpointInterceptor loggingEndpointInterceptor = new LoggingEndpointInterceptor();
        return loggingEndpointInterceptor;
    

    /**
     * Adds interceptors.
     * @param interceptors
     */
    @Override
    public void addInterceptors(List<EndpointInterceptor> interceptors) 
          // if these rows are uncommented
          // and payloadRootAnnotationMethodEndpointMapping method is commented you get
          // Error: SoapFaultClientException: Could not obtain transaction-synchronized Session for current thread
          interceptors.add(loggingInterceptor());
          super.addInterceptors(interceptors);
    


    /**
     * Spring Boot with Plain Hibernate
     * @see https://github.com/mdeinum/samples/tree/master/spring-boot-plain-hibernate
     *
     * Need to also set within application.properties.
     * spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
     * @return
     */
    @Bean(name="sessionFactory")
    public HibernateJpaSessionFactoryBean sessionFactory() 
        return new HibernateJpaSessionFactoryBean();
    

在仔细检查 AnnotationActionEndpointMapping 的构成后,我注意到它实现了 BeanPostProcessor。 spring doco http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html 建议“......因为 AOP 自动代理是作为 BeanPostProcessor 本身实现的,所以 BeanPostProcessor 和它们直接引用的 bean 都没有资格进行自动代理,因此没有将方面编织到其中。” 因此我知道@Transactional 不起作用。

public class AnnotationActionEndpointMapping extends AbstractActionMethodEndpointMapping implements BeanPostProcessor

我的问题是: * 是什么导致 spring-ws 拦截器默认映射到 AnnotationActionEndpointMapping 类? * 根据 Spring 文档,建议使用 and 或 @EnableWs 和方法 addInterceptors。如果在我们的遗留应用程序中被注释掉,会有什么影响吗?

请注意,我们的拦截器仅针对某些使用以下请求的请求调用,我们不想专门创建带有拦截器列表的 PayloadRootAnnotationMethodEndpointMapping bean 来解决此问题:

<sws:interceptors>
 <sws:payloadRoot localPart="TestRequest" namespaceUri="http://www.test.com/test/request/1.0">
...

【问题讨论】:

这仍然是一个问题。有没有人遇到过同样的问题?我相信以下更改Spring 可能会导致此问题。在我们的旧应用程序中,我们仍在注释掉 xml 配置 sws:annotation-driven,并希望 spring 中有人检查上述问题。 我已提交 spring jira 票 SWS-974 以希望解决这个悬而未决的问题。 它可能被视为 necro-threading,但仅作为建议,请检查您的组件扫描.. 确保使用端点注释注释的类仅由 web 中指定的 web 服务 servlet 扫描组件.xml,而不是//仅通过您在根 applicationContext.xml 中执行的组件扫描。我建议这样做,因为例如,如果控制器由 applicationContext 和 servlet 扫描,则事务注释将不起作用,因此您可能会遇到相同的问题,只是使用端点而不是控制器进行上下文化:) 在一个示例中,我提供了 spring SWS-974 我修改了 WebServiceConfig 对象并添加了组件扫描 添加与否都没有区别。遇到了同样的问题。 【参考方案1】:

与其他人声称的不同,此错误仍然存​​在于 Spring-core 5.1.5 和 Spring-ws 3.0.7 中。它与这个问题有关:Why @EnableWs removed aop proxy from spring bean。简而言之,问题来自于方法

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

在 Spring 依赖注入有时间在事务管理下注册 bean 之前被调用。 Spring-WS 中的 bean 生命周期初始化逻辑似乎与平常不同。不知道为什么。

这是我对绕过这个问题的看法。幸运的是,Spring-WS 使用可变集合而不是不可变集合。当addInterceptors() 方法 被调用时,我们可以只保存集合,因此我们有一个对 Spring-WS 使用的同一集合实例的引用。稍后您可以正确初始化拦截器 bean 并将其添加到集合中。

您还必须解决这样一个事实,即如果您使用@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 生命周期专家,因此可能有比postProcessBeforeInitialization() 更好的位置来放置拦截器初始化。也就是说,这行得通。

【讨论】:

【参考方案2】:

您可以做的是将您的事务方法(拦截器将使用的方法)移动/复制到一个新类,并使用 TransactionProxyFactoryBean 以编程方式创建一个事务 bean。

类似这样的:

@Bean
@Autowired
public TransactionProxyFactoryBean transactionalBeanForInterceptor(PlatformTransactionManager
        transactionManager, SessionFactory sessionFactory) 
    TransactionProxyFactoryBean factoryBean = new TransactionProxyFactoryBean();
    factoryBean.setTransactionManager(transactionManager);
    factoryBean.setTarget(new InterceptorService(sessionFactory)); // its just an example
    Properties transactionAttributes = new Properties();
    transactionAttributes.put("validate*", "PROPAGATION_REQUIRED"); // validate* is a regex with the name of the methods which are transactionals
    factoryBean.setTransactionAttributes(transactionAttributes);
    return factoryBean;


@Bean
@Autowired
public EndpointInterceptor myInterceptor(InterceptorService interceptorService)  // this will inject a proxied instance (transactional) of InterceptorService
    return new MyEndpointInterceptor(interceptorService);

【讨论】:

感谢@Enrique 的解决方案。看起来我们将继续注释掉 sws:annotation-driven / EnableWS 以希望 Spring 提供修复。【参考方案3】:

如果还有人遇到问题,请继续阅读。一旦我们的项目升级到 spring 4.3.10.RELEASE(来自 spring 4.2.0.RELEASE)和 spring-ws 2.4.0.RELEASE(来自 spring-ws 2.2.1.RELEASE),上面报告的错误不再是即使在我们的代码库中重新引入以下代码时也会出现问题:

XML 配置:

<sws:annotation-driven>

注释:

@EnableWS from the @Congiuration object 

一旦完成,需要@Transactional 的端点拦截器就会按预期工作。此外,春季票SWS-974 仍未分配。我会尝试通知 Spring 他们可以关闭它。

【讨论】:

以上是关于注入 DAO 的 @Transactional 的 Spring WS 拦截器不起作用的主要内容,如果未能解决你的问题,请参考以下文章

关于服务和@Transactional

在哪里使用@Transactional注解和@Repository注解

@transactional注解在什么情况下会失效,为什么。

LazyInitializationException 和 @Transactional 不起作用

关于方法上的 Spring @Transactional 注释的一些说明

spring gclib final @Transactional