Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Spring @Value incompatible with @Controller?

I'm looking for a better understanding of this problem. A workaround is pretty simple, namely move the configuration data to another class that does not have proxies/advice wrapped around it, but I think understanding this better will help me avoid other related problems in the future, so I'd like whatever explanation anyone can provide.

I'm using Spring 3.1.0.RELEASE with Spring STS and the vFabric tc server. Implemented a basic little REST server using a @Controller class. That's all great (really, it is), but the @Controller is also @Transactional, and between that and the load time weaving and the vFabric tc server, it breaks @Value.

@Controller
@RequestMapping("/hello")
public class MyAPI {

    @Value("${my.property}")
    private String prop;
    ...

    @Transactional
    handleRequest(...) ...


}

And a properties file app.properties:

my.property = SUCCESS

This works fine under JUnit, with the test getting a MyAPI object that has prop set to "SUCCESS". But when the app gets loaded into vFabric, I'm guessing it gets load time weaving and proxying. Whatever happens, there are two MyAPI instances created, one which has prop == "SUCCESS" and another (which is unfortunately the one that handles the http request) which has prop == "${my.prop}".

So all-in-all I call this a failure of magic, which is my biggest concern with using stuff like AOP. Even with STS I don't know how to track down what is the cause behind the problem or figure out if this is a serious bug. If it is a bug, I don't know whether it's a bug in Spring, AspectJ, the load-time weaver, or vFabric, so I don't even know where to file a bug report.

So any help in understanding this would be appreciated. Thanks.

like image 408
Old Pro Avatar asked Feb 20 '23 20:02

Old Pro


2 Answers

I figured it out. It was, indeed, too much magic.

I used Spring Roo in STS to generate the basic app framework, then factored out Roo using STS as we didn't want to stick with it.

One thing Roo does as a "best practice" is create two Spring contexts, one for the whole app, and one limited to the dispatcher servlet. Exactly why, I still haven't gotten to, but I guess they want to keep the presentation layer stuff, such as the Controllers, from creeping into the service layer that is shared. This was nicely explained by axtavt here. This was all hidden from me by STS.

In STS with Roo, the WEB-INF source is not where I expected it, under /src/main/resources (which is where the META-INF directory is) but instead under /src/main/webapp, which is not a Java source directory and thus shown completely separately, just above the /target directory, so I mistook it for an output folder.

In the applicationContext.xml, Roo had inserted the filter to prevent the application context from constructing Controllers, as explained in axtavt's post, but it also put in another filter to eliminate scanning Roo generated classes. I took both filters out at the same time, not really knowing what they were there for but thinking they were just Roo leftovers.

So now I've got the problem of the Controllers being created twice as explained before. And the one in the application context gets the property assigned, because it is using the applicationContext.xml and finding the properties file. But why weren't they both getting the properties set?

Which gets me back to the obscured webapps folder. Inside the WEB-INF folder Roo had placed the web.xml (naturally) and a spring folder containing a webmvc-config.xml file. This config file was set up to scan, create, and set up just the Controllers. The web.xml file sets up the web app to use the applicationContext.xml and the dispatcherServlet to use the webmvc-config.xml, so I should have left the filter in the applicationContext.xml to avoid the double creation.

The final piece of the puzzle was this webmvc-config.xml file. Since that is the context in which the Controllers get set up, that file needed to have the <context:property-placeholder/> configured as well so that it could find the properties file.

like image 98
Old Pro Avatar answered Feb 23 '23 11:02

Old Pro


First of, the notation using the $ is correct, not the #

Now regarding the original poster, I think you have a conflicting behavior between the 2 annotations (@Controller and @Transactional).

Using the @Transactional annotation will proxify your implementation (I guess to detect an error and launch a rollback).

Now you are saying that you see 2 instances of your controller. That should not happen. Normally you should have only a single instance of a controller loaded in memory.

Could be related to your configuration files or due to the presence of @Transactional and its proxy nature?

As a side note, I never use @Transactional in the controllers themselves but either on a method of a service or dao. Since the actual process which can fail and need to be rollbacked is there, and can be access from different controllers/source.

Regards.

like image 32
Farid Avatar answered Feb 23 '23 11:02

Farid