Why is Spring @Value incompatible with @Controller?

烂漫一生 提交于 2019-12-01 04:22:58

问题


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.


回答1:


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.




回答2:


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.




回答3:


I had the same problem and it was the fact that I needed BOTH

To get these two senarios to work

  1. inject an attribute (via @Value) into a Controller
  2. Inject a managed bean (via @Inject) that hasan attribute (via @Value) into the Controller

I hope this helps.

Here is the web.xml.

    <servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/spring-config/spring-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Here is spring-servlet.xml: The key was to have BOTH util:properties and context:property-placeholder. Although they locate the same file, they serve different purposes.

<util:properties id="propSource" location="classpath:/properties/prop1.properties" />
<context:property-placeholder location="classpath:/properties/prop1.properties" />

<context:component-scan base-package="com.concurrent.controller" />

<context:annotation-config/>
<mvc:annotation-driven/> 

Here is a snippet of my controller class:

@Controller
@RequestMapping("/fooController")
public class MyController {

@Value("${message1}")
public String message1;

@Inject
public ConfigProperties configProperties;

Lastly, here is my class that I inject the property into:

@Service
public class ConfigProperties {

@Value("${message1}")
public String message1;
}

That worked for me and will work for you. Good Luck!




回答4:


In my case I solve that this way: in spring-servlet.xml before <context:component-scan ... /> i put this:

<context:property-placeholder location="classpath:strings.properties"/>

While strings.properties file I put to src/main/resources/.

Note: context:property-placeholder tag has limited visibility so i have read somewhere recommendation to duplicate it in every context file, where you use strings from it.



来源:https://stackoverflow.com/questions/10102216/why-is-spring-value-incompatible-with-controller

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!