本文介绍了带有注入DAO的@Transactional的Spring WS拦截器不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个基于XML的旧版配置spring-ws应用程序,该应用程序包含EndpointInterceptor,这些端点已注入DAO以从数据库中获取配置。



当我们升级到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对象,然后DAO是一个代理对象,拦截器似乎要转到正确的端点(即PayloadRootAnnotationMethodEndpointMapping),并且单元测试工作没有事务错误。



StackTrace when或EnableWS未注释掉。

  org.springframework.ws.soap.client .SoapFaultClientException:无法在org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:38)
获得当前线程
的事务同步会话.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:830)
在org.springframework.ws.client.core.WebServiceTe在org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:555)的mplate.doSendAndReceive(WebServiceTemplate.java:624)
在org.springframework.ws.client.core的
。 org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:378)
处的WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:390)
在hello.ApplicationTests.testSendAndReceive(ApplicationTests.java: 61)
在sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法)
在sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
在sun.reflect.DelegatingMethodAccessorImpl.invoke( DelegatingMethodAccessorImpl.java:43)org.junit.runners.model.FrameworkMethod $ 1.runReflectiveCall(FrameworkMethod.java:50)
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable) .java:12)org.junit.runners.model.FrameworkMethod.invokeExplosively(
) FrameworkMethod.java:47)org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
org.junit.internal.runners.statements.RunBefores.evaluate( RunBefores.java:26)在org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
在org.springframework.test.context.junit4.statements。 RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
在org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
在org.junit.runners.ParentRunner。 runLeaf(ParentRunner.java:325)
在org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
在org.springframework.test.context.junit4.SpringJUnit4ClassRunner。 runChild(SpringJUnit4ClassRunner.java:89)
在org.junit.runners.ParentRunner $ 3.run(ParentRunner.java:290)
在org.junit.run ners.ParentRunner $ 1.schedule(ParentRunner.java:71)
在org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
在org.junit.runners.ParentRunner.access $ 000( ParentRunner.java:58)org.junit.runners.ParentRunner
(评估)(ParentRunner.java:268)org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks)评估
.java:61)org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
org.junit.runners.ParentRunner.run(ParentRunner.java) :363)org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)中的
org.junit.runner.JUnitCore.run(JUnitCore.java:137)中的
b $ b在com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
在com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.jav a:212)com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
在sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
在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
公共类WebServiceConfig扩展了WsConfigurerAdapter {

@Bean
public ServletRegistrationBean messageDispatcherServlet( ApplicationContext applicationContext){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
返回新的ServletRegistrationBean(servlet, / ws / *);
}

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

@Bean
public XsdSchema countrySchema(){
return new SimpleXsdSchema(new ClassPathResource( countries.xsd));
}

/ **
*声明loggingInterceptor。
* @返回新的日志记录拦截器。
* /
@Bean
public LoggingEndpointInterceptor loggingInterceptor(){
LoggingEndpointInterceptor loggingEndpointInterceptor = new LoggingEndpointInterceptor();
return loggingEndpointInterceptor;
}

/ **
*添加拦截器。
* @param拦截器
* /
@Override
public void addInterceptors(List< EndpointInterceptor>拦截器){
//如果这些行未注释
//并且注释了payloadRootAnnotationMethodEndpointMapping方法,您将获得
//错误:SoapFaultClientException:无法获取当前线程
的事务同步Session。add(loggingInterceptor());
super.addInterceptors(interceptors);
}


/ **
*带有纯冬眠
的Spring Boot * @see {https://github.com/mdeinum/samples/ tree / master / spring-boot-plain-hibernate}
*
*还需要在application.properties中进行设置。
* spring.jpa.properties.hibernate.current_session_context_class = org.springframework.orm.hibernate4.SpringSessionContext
* @return
* /
@Bean(name = sessionFactory)
public HibernateJpaSessionFactoryBean sessionFactory(){
返回新的HibernateJpaSessionFactoryBean();
}
}

仔细检查AnnotationActionEndpointMapping的构成后,注意它实现了BeanPostProcessor。 spring doco 建议: ...由于AOP自动代理是作为BeanPostProcessor本身实现的,因此BeanPostProcessor或它们直接引用的bean都不会有资格进行自动代理,因此没有编织的方面。
,因此我知道@Transactional无法正常工作。

 公共类AnnotationActionEndpointMapping扩展了AbstractActionMethodEndpointMapping实现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-core 5.1.5和Spring-ws 3.0.7中。它与以下问题相关:。简而言之,问题来自于

  @Override 
public void addInterceptors(List< EndpointInterceptor>拦截器){

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



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



您还必须避免使用 @的事实。在注释可以发生之前,已经自动准备好了 bean。因此,您必须通过调用 ApplicationContext.getBean()方法手动创建它。

  @EnableWs 
@Configuration
//魔术既可以实现ApplicationContextAware
//可以为我们注入$ application $ Context,又可以实现BeanPostProcessor,后者可以为我们提供postProcessBeforeInitialization()
//我们正确初始化拦截器的位置
//并将其添加到集合中
公共类WebServiceConfig扩展了WsConfigurerAdapter实现ApplicationContextAware,BeanPostProcessor {

//这是使用带有@Transactional批注的依赖项的拦截器。
//不适用于@Autowired
private MyInterceptorThatHasTransactionalDependencies myInterceptorThatHasTransactionalDependencies;
//幸运的是,Spring WS使用可变集合,因此我们以后可以填充
//只要使用
private List< EndpointInterceptor>初始化它即可。拦截器;
//这是我们的应用程序上下文,其中定义了所有bean
私有ApplicationContext上下文;

@Override
public void setApplicationContext(ApplicationContext applicationContext)抛出BeansException {
//保存应用程序上下文以供以后使用
this.context = applicationContext;
}

@Nullable
public Object postProcessBeforeInitialization(Object bean,String beanName)抛出BeansException {
//此方法被调用多次,因此只初始化一次拦截器
if(myInterceptorThatHasTransactionalDependencies == null){
myInterceptorThatHasTransactionalDependencies = context.getBean(MyInterceptorThatHasTransactionalDependencies.class);
interceptors.add(myInterceptorThatHasTransactionalDependencies);
}
返回bean;
}

@Override
public void addInterceptors(List< EndpointInterceptor>拦截器){
//保存拦截器列表,以便稍后在$ b上进行修改$ b this.interceptors =拦截器;
if(myInterceptorThatHasTransactionalDependencies == null){
System.out.println( myInterceptorThatHasTransactionalDependencies如我们所料,为null);
} else {
拦截器.add(myInterceptorThatHasTransactionalDependencies);
}
}
}

只是让您知道我不是Spring bean生命周期专家,因此与 postProcessBeforeInitialization()相比,在拦截器初始化方面可能有更好的定位。也就是说,这可行。


We have a legacy XML based configuration spring-ws application that contains endpointInterceptors that have DAOs injected to obtain configuration from the database. These DAOs have the hibernate sessionFactory injected.

When we have upgraded to spring 4.2.0.RELEASE (from spring 3.2.5.RELEASE) and spring-ws 2.2.1.RELEASE (from spring-ws 2.1.4.RELEASE) I noticed that the DAO was not a proxy object and it seemed that the intercetor was going to theAnnotationActionEndpointMapping class instead of the PayloadRootAnnotationMethodEndpointMapping class.

I therefore created a spring-boot version 1.3.0.RELEASE based example that outlines our legacy application and the problem is evident in both XML base configuration and annotation based configuration. Please note thatannotation @EnableTransactionManagement exists within the example and that exists within the legacy application.

If you commented out the from the application context or @EnableWS from the @Congiuration object then the DAO was a proxy object, the interceptor seemed to be going to the correct endpoing (i.e PayloadRootAnnotationMethodEndpointMapping) and the unit test worked with no transaction error.

StackTrace when or EnableWS is not commented out.

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)

Extract of the XML configuration that causes the above exception :

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


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

Extract of the annotation configuration that causes the above exception :

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

After a closer inspection of the makeup of AnnotationActionEndpointMapping i have noticed that it implements BeanPostProcessor. The spring doco http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html suggests that "... Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessors nor the beans they reference directly are eligible for auto-proxying, and thus do not have aspects woven into them."and therefore I understand that @Transactional will not work.

public class AnnotationActionEndpointMapping extends AbstractActionMethodEndpointMapping implements BeanPostProcessor

My questions are :*what has changed that has caused spring-ws interceptors to be mapped by default to the AnnotationActionEndpointMapping class?* As per Spring documentation it is suggested that both and or @EnableWs and method addInterceptors are requred. Is there any impact if is commented out in our legacy application?

Please note we have interceptors that are only invoked for certain requests using the following and we do not want to specicially create a PayloadRootAnnotationMethodEndpointMapping bean with the list of interceptors to overcome this problem:

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

Unlike others have claimed, this bug persists still in Spring-core 5.1.5 and Spring-ws 3.0.7. It relates to this question: Why @EnableWs removed aop proxy from spring bean. 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 the bean under transaction management. It seems that the bean lifecycle initialization logic in Spring-WS is different from normal. No idea why.

Here's my take on walking past the issue. Fortunately for us Spring-WS uses mutable collections instead of immutable. When the addInterceptors() methodgets 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.

这篇关于带有注入DAO的@Transactional的Spring WS拦截器不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-23 09:08