即使在日志中看到“添加事务方法”,方法也不被事务顾问拦截

我有一个@Transactional @Controller ,但它的方法是由Spring MVC框架调用的,没有事务。 在exception跟踪中,我找不到拦截调用的事务顾问:

 org.hibernate.HibernateException: No Session found for current thread org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:106) org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014) org.example.businesslogic.MyController.userLoggedIn(SwiperRest.java:48) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.lang.reflect.Method.invoke(Method.java:483) org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215) org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132) org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689) org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83) org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938) 

另一方面,日志清楚地表明控制器方法被检测为事务性:

 DEBUG osbfsDefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor' DEBUG osbfsDefaultListableBeanFactory - Returning cached instance of singleton bean 'metaDataSourceAdvisor' DEBUG ostaAnnotationTransactionAttributeSource - Adding transactional method 'MyController.userLoggedIn' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' DEBUG osafaInfrastructureAdvisorAutoProxyCreator - Creating implicit proxy for bean 'myController' with 0 common interceptors and 1 specific interceptors DEBUG osafCglibAopProxy - Creating CGLIB proxy: target source is SingletonTargetSource for target object [org.example.businesslogic.MyController@7c0f1b7c] DEBUG osafCglibAopProxy - Unable to apply any optimisations to advised method: public java.lang.String org.example.businesslogic.MyController.userLoggedIn(java.lang.String,java.lang.String) DEBUG ostaAnnotationTransactionAttributeSource - Adding transactional method 'MyController.locationProfiles' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' DEBUG osafCglibAopProxy - Unable to apply any optimisations to advised method: public java.util.List org.example.businesslogic.MyController.locationProfiles(java.lang.String) 

来自控制器类的片段:

 @Transactional @Controller @RequestMapping("/zendor") public class MyController { @Autowired private SessionFactory sf; @RequestMapping(method=POST, value="userLoggedIn") public @ResponseBody String userLoggedIn(@RequestParam String u_id, @RequestParam String d_id) { Session hb = sf.getCurrentSession(); ... } } 

这是我的web应用程序初始化类,我没有web.xml

 public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class[] getRootConfigClasses() { return new Class[] { RootConfig.class }; } @Override protected Class[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override public void onStartup(ServletContext ctx) throws ServletException { ctx.setInitParameter("spring.profiles.active", "production"); super.onStartup(ctx); } } 

这是引用的根配置:

 package org.example.config; @Configuration @ComponentScan public class RootConfig { } 

它与这些包在同一个包中,可以通过默认的组件扫描范围获取:

 @Configuration @EnableWebMvc @ComponentScan("org.example.businesslogic") public class WebMvcConfig extends WebMvcConfigurationSupport { } @Configuration @EnableTransactionManagement @ComponentScan("org.example.businesslogic") public class DataConfig implements TransactionManagementConfigurer { @Autowired private DataSource dataSource; ... } 

当Spring-test的SpringJUnit4ClassRunner使用相同的配置时,方法会得到建议并且事务可以正常工作。

我还试图将userLoggedIn方法提取到@Autowired @Transactional @Component ,但结果是相同的。

我应该在哪个方向上解决这个问题?

我在Spring 4.0.5上。

更新1

关键问题是我的root配置也包括所有其他配置类,包括WebMvcConfig ,它再次作为子servlet配置加载。

非常违反直觉,当我删除 servlet配置类,替换时,事情才开始工作

  @Override protected Class[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; } 

  @Override protected Class[] getServletConfigClasses() { return null; } 

它直接针对文档: may not be empty or null 。 如果我执行相反的操作,为rootConfigClassesRootConfigservletConfigClasses赋予null ,那么一切都会失败,因为“找不到servlet上下文。”。

更新2

没有root应用程序上下文的故障已经追溯到Spring Web Security,它必须在根级别配置才能被SecurityWebApplicationInitializer选中,因为这似乎是在根应用程序上下文已经存在的阶段执行的,但不是网络应用程序上下文。 所以我的问题解决方案是在root和webapp上下文之间引入分离,其中root加载安全性和webapp其他所有内容。

如果你还没有读过它们

  • Spring MVC中ApplicationContext和WebApplicationContext有什么区别?
  • Spring Framework中applicationContext.xml和spring-servlet.xml之间的区别

这同样适用于AbstractAnnotationConfigDispatcherServletInitializergetRootConfigClasses()getServletConfigClasses() 。 基本上, WebApplicationInitializer将构造(并注册)一个ContextLoaderListener ,其中AnnotationConfigWebApplicationContextgetRootConfigClasses()注册所有@Configuration (以及其他@Component注释)类。 然后,它将使用getServletConfigClasses()所有@Configuration (和其他…)类构造和注册DispatcherServlet

作为Servlet生命周期的一部分,容器将首先初始化所有ServletContextListener对象。 这意味着ContextLoaderListener将首先加载并refresh给它的AnnotationConfigWebApplicationContext (如果它尚未刷新,理想情况下它不应该刷新)。 它还将此ApplicationContext作为ServletContext的属性。

然后容器将初始化已注册的DispatcherServlet 。 这里有更多的阅读

  • Web容器如何管理弹簧控制器的生命周期
  • SpringMVC生命周期 – 整体视图

基本上, DispatcherServletrefresh它收到的ApplicationConfigWebApplicationContext ,方法是首先将其父级设置为ServletContextApplicationContext (由ContextLoaderListener设置)(如果有的话)。

然后它将开始从其ApplicationContext选择和选择bean来设置MVC堆栈,控制器,处理程序方法,拦截器等。默认情况下 ,它只会在它加载的ApplicationContext查找其处理程序bean, @Controller bean,而不是其父母。


你似乎做的是

 @Override protected Class[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; } 

 @Override protected Class[] getRootConfigClasses() { return new Class[] { RootConfig.class }; } 

在这种情况下, ContextLoaderListener将加载RootConfig ,它将创建一堆bean,包括@Controller类的bean,将通过@Transactional配置建议。

DispatcherServlet然后将加载具有自己的@ComponentScan WebMvcConfig ,这将创建新的@Controller bean,但是不会建议这些,因为没有注册TransactionInterceptor (在此上下文中没有@EnableTransactionManagement )。 然后, DispatcherServlet将尝试在自己的ApplicationContext查找所有@Controller bean(以及其他具有@RequestMapping方法的bean)。 它会找到这些@Controller bean,不建议使用它们。 这些是它将注册为处理程序的那些,而不是ContextLoaderListener加载的那些。

如果您在日志中向下看,您应该会看到正在创建的新控制器bean。


建议:

  • 根上下文:整个应用程序应该可见的内容
  • Servlet上下文:MVC堆栈应该可见的内容

控制器不是整个应用程序应该访问的组件。 只有DispatcherServlet应该关心它们。 将它们放在servlet上下文中。

现在我显然不知道你的整个应用程序,但我建议你从处理程序方法和一些@Service方法中重构所有事务逻辑。 这将使您更容易维护您的配置并使您的控制器更多控制器,即。 委托给模特。

你做错了什么: RootConfigWebMvcConfig都在同一个包中。 RootConfig在自己的包中进行组件扫描,发现WebMvcConfig ,后者又进行组件扫描。 最后,根应用程序上下文将包含所有与事务相关的内容(txManager,datasource,sessionfactorybean等),但也包含与Web相关的所有内容:controllers,handlermappings等。

然后, WebMvcConfig启动(因为它在WebApplicationInitializer定义)并且所有与Web相关的东西再次被重新定义。 而且我认为它正在以它的方式发生,因为根上下文有一个版本的控制器(事务性的)和servlet上下文有另一个版本(简单版本)。

我认为您需要将RootConfigWebMvcConfig在单独的包中。