5

I have a @Transactional @Controller, but its methods are being invoked by the Spring MVC framework without a transaction. In the exception trace I do not find the transaction advisor intercepting the call:

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)

On the other hand, the log clearly indicates that the controller methods were detected as transactional:

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

A snippet from the controller class:

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

This is my web application initializer class, I don't have a 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);
    }
}

This is the referenced root configuration:

package org.example.config;

@Configuration
@ComponentScan
public class RootConfig
{
}

It is in the same package as these, which get picked up by the default component scan range:

@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;
  ...
}

When the same configuration is used by Spring-test's SpringJUnit4ClassRunner, the methods do get advised and transactions work.

I also tried to extract the userLoggedIn method to an @Autowired @Transactional @Component, but the result was identical.

In which direction should I inveltigate to resolve this issue?

I am on Spring 4.0.5.

Update 1

The key problem is that my root config is pulling in all other config classes as well, including WebMvcConfig, which is loaded again as the child servlet config.

Quite counterintuitively, things only start working when I remove the servlet config class, replacing

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

with

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

which goes directly against the documentation: may not be empty or null. If I do the reverse, giving null for rootConfigClasses and RootConfig for servletConfigClasses, then everything fails even harder, with "servlet context not found.".

Update 2

The failure occuring without root app context has been traced to Spring Web Security, which must apparently be configured at the root level in order to be picked up by the SecurityWebApplicationInitializer, as this seems to executed at a stage when the root app context already exists, but not the web app context. So my problem resolution was to introduce a separation between root and webapp contexts, where the root loads security and webapp everything else.

Community
  • 1
  • 1
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • One obvious question: why do you want your controllers to be transactional? Usually, controllers call methods that are transactional. – Andrei Stefan Jun 23 '14 at 13:42
  • For simplicity, at the moment. I don't need the extra layer at this point in application's lifecycle. – Marko Topolnik Jun 23 '14 at 13:43
  • have you tried class-based proxies, like ? – Nathan Hughes Jun 23 '14 at 13:46
  • @NathanHughes Isn't that the default way in Spring? I am not using AspectJ. – Marko Topolnik Jun 23 '14 at 13:47
  • @Marko: looking at your example again i'm thinking it may not be relevant. the documentation says "However if a controller must implement an interface that is not a Spring Context callback (e.g. InitializingBean, *Aware, etc), you may need to explicitly configure class-based proxying. " http://docs.spring.io/spring/docs/4.1.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc – Nathan Hughes Jun 23 '14 at 13:50
  • @NathanHughes I've checked: the appearance of "Creating CGLIB proxy" in the log is proof that *proxy-target-class* is the active proxying strategy. – Marko Topolnik Jun 23 '14 at 14:04
  • Can you share a bit more from your code? For example, are you using a `web.xml` or is it Servlet 3 `WebApplicationInitializer` app? I'm interested in how you configured the hierarchy of app contexts (if any) and the transactional configuration (if anything more than what you posted already). – Andrei Stefan Jun 23 '14 at 14:16
  • My guess is that the controller is instantiated as a component of the child, web context, whereas transactions are enabled in the root context. Make sure that the packahes scanned in the root context are mutually exclusive with the package scanned in the child context. And make sure transactions are enabled in the child context. BTW, The javadoc of ComponentScan says: "One of basePackageClasses(), basePackages() or its alias value() must be specified". I personally prefer having a single, web context. Never seen the advantage of the parent/child contexts. – JB Nizet Jun 23 '14 at 15:59
  • Yes, please show us your servlet configuration, whatever it is: web.xml, `WebApplicationInitializer`, `ServletContainerInitializer`, etc. – Sotirios Delimanolis Jun 23 '14 at 18:50
  • @JBNizet This is my first project with JavaConfig so I'm still not quite certain how the concept of context hierarchy maps to `@Configuration` annotations. I suppose that `RootConfig` is the root of the hierarchy because that's the one I specifically name in `WebApplicationInitializer`, but from then on the details are hazy. BTW the docs say "... **may** be specified" and goes on with `If specific packages are not defined scanning will occur from the package of the class with this annotation.` – Marko Topolnik Jun 24 '14 at 06:53
  • @AndreiStefan I don't have a `web.xml` and I have pasted my `WebApplicationInitializer`. – Marko Topolnik Jun 24 '14 at 07:07
  • @SotiriosDelimanolis I have added this to the question; now my main suspicion is on the way I load the `WebMvcConfig` from root by component scan, where it should only be picked up by the explicit `servletConfigCLasses` setting. – Marko Topolnik Jun 24 '14 at 07:08
  • @AndreiStefan I see your point which you present in your currently deleted answer; if you look at my edit, you'll see that we're on the same track here. However, let's say I don't need parent and child context and want everything in the servlet context: then it fails as described. And with everything in the root context it works, but violates the requirements in the docs. – Marko Topolnik Jun 24 '14 at 08:27
  • I've posted my answer too early and I temporarily delete it to have it complete. – Andrei Stefan Jun 24 '14 at 08:28
  • @JBNizet I sorted it out, parent is `rootConfigClass` and child is `servletConfigClass`. When I have no root and everything as servlet config, it fails as described in my update. You say you get away with no root config, what's your secret? :) – Marko Topolnik Jun 24 '14 at 08:37

2 Answers2

4

If you haven't already read them

The same applies to AbstractAnnotationConfigDispatcherServletInitializer's getRootConfigClasses() and getServletConfigClasses(). Basically that WebApplicationInitializer will construct (and register) a ContextLoaderListener with a AnnotationConfigWebApplicationContext registering all the @Configuration (and other @Component annotated) classes from getRootConfigClasses(). It will then construct and register a DispatcherServlet with all the @Configuration (and other...) classes from the getServletConfigClasses().

As part of the Servlet lifecycle, the container will first initialize all ServletContextListener objects. This means ContextLoaderListener will load first and refresh the AnnotationConfigWebApplicationContext that was given to it (if it wasn't already refreshed, which ideally it shouldn't be). It will also put this ApplicationContext as an attribute in the ServletContext.

The container will then initialize the registered DispatcherServlet. Here's some more reading

Basically, the DispatcherServlet will refresh the ApplicationConfigWebApplicationContext that it received by first setting its parent to the ApplicationContext in the ServletContext (set by the ContextLoaderListener), if there is one.

It will then start picking and choosing beans from its ApplicationContext to set up the MVC stack, controllers, handler methods, interceptors, etc. By default, it will only look up its handler beans, @Controller beans, in the ApplicationContext it loaded, not its parent(s).


What you seem to have done is

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

and

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

In this case, the ContextLoaderListener will load RootConfig which will create a bunch of beans, including ones for your @Controller classes which will be advised with the @Transactional configuration.

The DispatcherServlet will then load WebMvcConfig which has its own @ComponentScan and this will create new @Controller beans, but these won't be advised because no TransactionInterceptor was registered (no @EnableTransactionManagement in this context). The DispatcherServlet will then try to find all @Controller beans (and other beans that have @RequestMapping methods) in its own ApplicationContext. It will find these @Controller beans which aren't advised. Those are the ones it will register as handlers, not the ones loaded by the ContextLoaderListener.

If you look further down in your logs, you should see a new controller bean(s) being created.


Suggestions:

  • Root context: things that should be visible to the entire application
  • Servlet context: things that should be visible to the MVC stack

Controllers are not components that the whole application should have access to. Only the DispatcherServlet should care about them. Put them in the servlet context.

Now I obviously don't know your whole application, but I recommend you refactor all the transactional logic out of the handler methods and into some @Service methods. It will make it easier to maintain your configs and make your controllers more controller-y, ie. delegate to the model.

Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • The proximate cause of my problems is clear (double loading of WebMvcConfig), the question is how to fix it. My problem is the contradiction between what you and the docs say here, and observed behavior: 1. the docs state that the root context is optional and the servlet context is mandatory; 2. the servlet container doesn't execute any context loader listeners when I have no root context; 3. when all I have is the root container, everything works. – Marko Topolnik Jun 25 '14 at 14:25
  • Regarding the split into controllers and services, I am fully aware of that *best practice* and have experience with apps written along those lines; however I am generally not eager to introduce boilerplate and double the class count if I don't see a clear benefit emerging from the ordeal. At the moment the only disadvantage of "flat" controllers I can see lies with the apparently opinionated framework which seems to be forcing me in that direction. – Marko Topolnik Jun 25 '14 at 14:28
  • @MarkoTopolnik I don't know why they put that. The source code doesn't match up. You can create a `DispatcherServlet` without a `WebApplicationContext` and it will create its own with default values (but no controllers). Maybe it was meant as a warning. The `DispatcherServlet` isn't of much use without custom handlers. – Sotirios Delimanolis Jun 25 '14 at 14:32
  • @MarkoTopolnik `when all I have is the root container, everything works` Can you expand on that? Your controllers shouldn't be registered if you only have a root context. – Sotirios Delimanolis Jun 25 '14 at 14:34
  • I don't know what else to say: with `protected Class>[] getServletConfigClasses() { return null; }` the whole REST application is fully functional, all the controllers in place, URLs accessible via `curl`, etc. – Marko Topolnik Jun 25 '14 at 14:35
  • @MarkoTopolnik This has to do with `@WebMvcConfigurationSupport`. It will generate the beans and the MVC stack will scan the current context, which is your `RootConfig`. Since the `@Controller` beans are there it will pick them up. The `DispatcherServlet` looks for its MVC stack in all contexts, not just the servlet context. That's why that works. I'll brb. – Sotirios Delimanolis Jun 25 '14 at 14:52
  • Alright; that explains why it works; we are still missing an explanation of why it *doesn't* work in the opposite case. There may be some swallowed exception in that case which hides the cause of failure. I can investigate that hypothesis tomorrow. – Marko Topolnik Jun 25 '14 at 15:08
  • @marko If you could post the full stack trace of the other case, I could help further. – Sotirios Delimanolis Jun 25 '14 at 15:09
  • That's the trouble: there *isn't* one. The only observation is the void in the logs where all the initialization should have happened, and when I try to access any URL I get "no servlet context found" in the response. – Marko Topolnik Jun 25 '14 at 15:11
  • @Marko That is very troubling indeed. (I doubt Spring is swallowing anything, but could be.) If tomorrow you can create a reproducible example, I'll be more than happy to investigate with you. (A `DispatcherServlet` does not need a `ContextLoaderListener` to function.) – Sotirios Delimanolis Jun 25 '14 at 15:14
  • My mistake, when I wrote "doesn't execute any context loader listeners", I meant "any servlet initializers"---but that's hardly it because we're talking about a difference *inside* the Spring's servlet initializer. – Marko Topolnik Jun 25 '14 at 15:24
  • @MarkoTopolnik Yeah, that seems like a deployment/classpath issue. Changing the code of the initializer won't affect how your Servlet container scans the classpath for `ServletContainerInitializer` and/or how Spring's `SpringServletContainerInitializer` finds `WebApplicationInitializer`s. – Sotirios Delimanolis Jun 25 '14 at 15:47
  • OK, now I know where I got the idea about context loader listener. This is the exact error message: `>java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered? org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:252)` – Marko Topolnik Jun 26 '14 at 07:22
  • And earlier on, in the init phase, I get this in the log: `...WebApplicationInitializer - No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context`. This doesn't sound alarming---I really don't have, or need, a root context. Moreover, this is followed by what looks like a pretty regular initialization of Hibernate, WebMVC, and everything else I've got (Hazelcast, most notably). This last observation is now different from earlier, so the earlier one should be ignored. I was probably not paying enough attention before. – Marko Topolnik Jun 26 '14 at 07:23
  • Next step: created an empty "NullConfig" class for root context; now the result is `...NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined` in the log... which brings me to to realize that I have overlooked that I have a `SecurityWebApplicationInitializer` as well, and that this is an important fact :) – Marko Topolnik Jun 26 '14 at 07:38
  • ...so I moved the SecurityConfig to the root context, and things seem to be working! Final question: does that mean that web security *must* be defined in the root context? And it's still awkward that nothing at all worked when I had everything in the webapp context, security-related or otherwise. I haven't seen any such requirement in the docs. – Marko Topolnik Jun 26 '14 at 07:45
  • @Marko Yes. I don't know Spring Security very well, but I believe it is implemented with a servlet `Filter`. Spring registers a `DelegatingFilterProxy` servlet `Filter` to effectively get `Filter` injection. Remember, the Servlet container manages `*Listener`, `*Servlet`, `*Filter` objects from the `web.xml` or `WebApplicationInitializer`. To get that behavior, the `DelegatingFilterProxy` looks for the `Filter` to delegate to in the `WebApplicationContext` loaded by the `ContextLoaderListener` and stored in the `ServletContext`. – Sotirios Delimanolis Jun 26 '14 at 14:25
  • @MarkoTopolnik That's the root context. The Servlet container initializes `*Listener`s before `Filter` and `Servlet` objects. The `DelegatingFilterProxy` wouldn't have access to the `DispatcherServlet`'s `WebApplicationContext` because the order of their initialization is unspecified. Therefore it needs to access the context loaded by the `ContextLoaderListener`. – Sotirios Delimanolis Jun 26 '14 at 14:28
  • @MarkoTopolnik (It kind of is specified, but it's related to the request that the filter has to process, rather than the Servlet that will also handle the request.) – Sotirios Delimanolis Jun 26 '14 at 14:29
  • So, taking into account that the security layer needs the db, we end up with everything in the root context. I guess I'll be sticking with that layout :-) – Marko Topolnik Jun 26 '14 at 17:48
  • @MarkoTopolnik I know you're not a fan (`I am generally not eager to introduce boilerplate and double the class count if I don't see a clear benefit emerging from the ordeal.`), but consider splitting things up like `SecurityConfig`, `DatabaseConfig`, `ServicesConfig`, etc. **IMO** it's much more maintainable that way. – Sotirios Delimanolis Jun 26 '14 at 17:52
  • That's the way I have it now :-) But the interdependencies force me to have them all in the root context. I *could* split out services from controllers and then have controllers in webapp context, but I don't currently see any advantage. Perhaps if my current MockMvc tests prove too heavy, I'll want to test services directly. – Marko Topolnik Jun 26 '14 at 18:34
  • @Marko They can all be in the root context, but be split into many `@Configuration` classes. (Obviously I don't know your application :p) – Sotirios Delimanolis Jun 26 '14 at 18:42
  • 1
    Yes, that's exactly how I've got them organized now. It makes it eaaier to find what you're looking for. – Marko Topolnik Jun 26 '14 at 19:08
1

You are doing something wrong: both RootConfig and WebMvcConfig are in the same package. RootConfig does component scanning in its own package, discovers WebMvcConfig which in turn does component scanning. In the end the root application context will contain all transactional related stuff (txManager, datasource, sessionfactorybean etc) but, also, everything web related: controllers, handlermappings etc.

Then, WebMvcConfig kicks in (because it's defined in WebApplicationInitializer) and all web-related stuff is re-defined again. And I think it's happening the way it does, because the root context has one version of your controller (the transactional one) and the servlet context has another version (the simple one).

I think you need to keep your RootConfig and WebMvcConfig in separate packages.

Andrei Stefan
  • 51,654
  • 6
  • 98
  • 89
  • Try something: move everything in `org.example.businesslogic` and keep only the `RootConfig` in `org.example.config`. I think you can, also, comment `RootConfig` in `WebApplicationInitializer`. – Andrei Stefan Jun 24 '14 at 08:31
  • I've already got one working solution, which is `RootConfig` as `rootConfigClass` and nothing as `servletConfigClass`. I'm now worried because this is illegal according to the docs, but I don't know how to have it working and legal. When I make it by the book and put everything in servlet config (root config is *optional*), then it fails. – Marko Topolnik Jun 24 '14 at 08:34
  • I'd suggest trying out my approach. You said that you don't need the extra layer at the moment and prefer having your controllers transactional. Also, `@EnableTransactionManagement` looks for `@Transactional` on beans in the same app context and it makes sense to have everything defined in the servlet context. – Andrei Stefan Jun 24 '14 at 08:44
  • OK, your approach is no root config, everything in servlet config? If I keep `RootConfig` in `org.example.config`, moving everything else away, and commenting out `RootConfig` from `WebApplicationInitializer`, the end result will be that `RootConfig` is not loaded at all. – Marko Topolnik Jun 24 '14 at 08:53
  • You can keep `RootConfig`, but everything web-related and transactional stuff should be in `org.example.businesslogic`. For example: keep `RootConfig`, in the same package with it place something like `@Configuration DataSourceConfig` (where you have, for example, `LocalSessionFactoryBean` and `DataSource`). In `com.foo.businesslogic` keep your controllers (`MyController`), `WebMvcConfig`, `DataConfig` (that should contain the transactional setup). – Andrei Stefan Jun 24 '14 at 09:04
  • I have designated `o..e..businesslogic` to be reserved for business logic code (in my case, controllers), and config stuff should be in a separate package, which is `o..e..config`. I really have no use for the separation between root and child contexts; I'm perfectly OK with everything residing in either. Yet I'm forced by the spec to make the servlet context that single context, which is exactly the approach which does not work. Now I'm looking for a way to introduce some minimum to the root context and make it work. Just an empty one doesn't cut it. – Marko Topolnik Jun 24 '14 at 09:11
  • Ok. I understand your package structure, but at the same time you need to consider how `ComponentScan` works and to make some kind of compromise. If you'll ever want to give this another try or refactor it in some way, I think the basic idea which is the root of your initial issue is that `@EnableTransactionManagement` acts on beans in the same app context. Good luck! – Andrei Stefan Jun 24 '14 at 09:17