我们有一个基于XML的遗留配置spring-ws应用程序,它包含注入DAO以从数据库获取配置的endpointInterceptors。这些DAO注入了hibernate sessionFactory。
当我们升级到春季4.2.0.RELEASE(从春季3.2.5.RELEASE)和spring-ws 2.2.1.RELEASE(从spring-ws 2.1.4.RELEASE)我注意到DAO不是一个代理对象似乎是拦截者要去的 AnnotationActionEndpointMapping类,而不是PayloadRootAnnotationMethodEndpointMapping类。
因此,我创建了一个基于Spring引导版本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本身实现的,所以BeanPostProcessors和它们直接引用的bean都没有资格进行自动代理,因此没有编入方面的方面“。 因此我明白@Transactional不起作用。
public class AnnotationActionEndpointMapping extends AbstractActionMethodEndpointMapping implements BeanPostProcessor
我的问题是: *改变了什么导致spring-ws拦截器默认映射到AnnotationActionEndpointMapping类? *根据Spring文档,建议同时使用和/或@EnableWs和方法addInterceptors。如果在我们的遗留应用程序中被注释掉,是否有任何影响?
请注意我们使用以下内容仅针对某些请求调用拦截器,我们不希望特别创建带有拦截器列表的PayloadRootAnnotationMethodEndpointMapping bean来克服此问题:
<sws:interceptors>
<sws:payloadRoot localPart="TestRequest" namespaceUri="http://www.test.com/test/request/1.0">
...
答案 0 :(得分:0)
您可以做的是将事务方法(拦截器将使用的方法)移动/复制到新类,并使用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);
}
答案 1 :(得分:0)
如果有人仍有问题,请继续阅读。一旦我们的项目升级到春季4.3.10.RELEASE(从春季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他们可以关闭它。
答案 2 :(得分:0)
与其他人声称的不同,该错误仍然存在于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()
更好。也就是说,这可行。