2

I have a servlet packaged in an ear file, and I cannot get it to resolve @Value annotated properties.

The app is in two parts: a servlet packaged in a war file, which is then included by different applications packaged in an ear file. The application supplies the implementation of a class (ResourceManager) defined by an interface in the servlet, and also the property file containing the value of the property for the @Value annotated field.

The war file contains:

web.xml:

<servlet>
  <servlet-class>... extends MessageDispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:/spring-ws.xml
      classpath:/spring-app.xml
    </param-value>
  </init-param>
</servlet>

spring-ws.xml:

<context:annotation-config/>
<context:property-placeholder location="classpath:/spring-ws.properties"/>

The values in spring-ws.properties are referenced directly from spring-ws.xml, and all work fine.

The servlet class contains:

public class MyServlet extends MessageDispatcherServlet

  @Autowired private ResourceManager resourceManager;  // original annotation - got this to work
  @Value("${app.name:}") private String appName;       // added later - cannot get this to work

The app.name is optional, hence the trailing ":".

The ear file adds (packaged in a jar file in the ear lib directory):

spring-app.xml:

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

  <bean class="ResourceManagerImpl"> 
    ...
  </bean>

spring-app.properties:

app.name=myApp

My first problem was with the @Autowired annotation: not sure I ever got to the bottom of that correctly, but I managed to get it to work by adding this code to the servlet:

@Override
protected void initFrameworkServlet() throws ServletException {

  AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
  bpp.setBeanFactory(getWebApplicationContext().getAutowireCapableBeanFactory());
  bpp.processInjection(this);
}

This may not be the best way to fix this, but it was the first I found and it worked, so I used it and moved on. Then I added a @Value annotated field, and I can't find a way to make this work. The javadocs for AutowiredAnnotationBeanPostProcessor say that it should process @Value annotations as well, but it doesn't appear to: the appName property is always blank (presumably from the default).

I do have a workaround, using beans:

@Autowired @Qualifier("appName") private String appName;

<bean id="appName" class="java.lang.String">
  <constructor-arg value="myApp"/>
</bean>

But I'm sure there must be a better way of doing this. I've read this post Difference between applicationContext.xml and spring-servlet.xml in Spring Framework, but I'm not sure it applies: I don't think I even have an application context here, just a servlet context defined in the web.xml for the (single) servlet class.

I've also seen this: Spring 3.0.5 doesn't evaluate @Value annotation from properties, which looks like the same problem, but the solution there appeared to be to move the "<context:property-placeholder>" from the application context to the servlet context, whereas as I said before, I don't think I even have an application context here...

Finally, I've had a quick go at simplifying the problem, removing the separate app and packaging everything in a single war, but I don't think that is the problem (although could obviously be wrong...): spring can resolve the class path for the app.xml file so it must be able to resolve the class path for the app.properties file.

Any help much appreciated, but I guess at this point it is a bit of an academic interest, as I have a workable solution (using beans) and the people that pay the bills don't really care how it works as long as it works! I'd just like to know so next time I don't have to copy (what I think is) a flawed workaround.

Using spring-ws-core 2.1.4, which pulls in spring 3.2.4 via maven dependencies.

Thanks

Update - Solved

Turns out the key to fixing this was M Deinum's comment that "You also have a duplicate element...". I had two separate PropertyPlaceholderConfigurers in the same context, so the second was ignored and its properties never used. There was no problem with the context itself (which is a servlet context not a root context), or the annotation processing.

So the fix was simply to remove the property placeholder from the spring-app.xml, and combine both property files in the spring-ws.xml

<context:property-placeholder
    location="classpath:/spring-ws.properties,
              classpath:/spring-app.properties"/>

TL;DR's answer here Spring: namespace vs contextConfigLocation init parameters in web.xml is also an awesome explanation of how the different contexts are processed!

Cheers,

3 Answers 3

4

Your servlet isn't a spring managed bean it is managed by the Servlet container. Hence the reason why @Autowired doesn't work in the first place. Spring will only process beans it knows.

Both the ContextLoaderListener and your servlet (basically any servlet that extends FrameworkServlet) have there own instance of an ApplicationContext. The one from the ContextLoaderListener is used as a parent by the context constructed in the servlet. So you indeed have an ApplicationContext in your servlet.

Now for this @Value to resolve properly you need to have a PropertyPlaceHolderConfigurer to resolve the placeholders. As PropertyPlaceHolderConfigurer is a BeanFactoryPostProcessor it only operates on beans in the same context, so the one loaded/configured by the ContextLoaderListener doesn't do anything for beans related to the context from your servlet. (Hence you need to add <context:property-placeholder ... /> element to the servlet context).

You also have a duplicate element in general you want to avoid that kind of situation as it will lead to problems at same time, either one overriding the other or you will get exceptions that placeholders cannot be resolved by either one of the PropertyPlaceHolderConfigurer

Your init code is also overly complex and you don't need the AutowiredAnnotationBeanPostProcessor simply use the available methods.

getWebApplicationContext().getAutowireCapableBeanFactory().autowireBean(this);

Which also does more then what only the AutowiredAnnotationBeanPostProcessor does.

1
  • Thanks - the duplicate PropertyPlaceholderConfigurer was the problem - see update above. I've also updated the init code as you suggested.
    – Barney
    Commented Feb 27, 2014 at 22:59
0

for @Autowired annotation, you just need to have a class with those annotations:

@Configuration
@PropertySource("classpath:myFile.properties")
@ComponentScan("com.company.package")

The @Configuration is used by Spring to tell it's a class for configuration (it replaces the xml files that you talked about, xml conf file is Spring 2, annotation is new with Spring 3 and 4)

The @ComponentScan manage the IOC (@Autowire)

The @PropertySource load the prop file

Then the class to initialized at startup with the prop file:

 @Component("myFileProperties")
 public class MyFileProperties{

 @Value("${app.name}")
 private String appName;

 public String getAppName() {
   return appName;
 }

 public void setAppName(String appName) {
   this.appName= appName;
 }
}

Take a look here if you want it can inspire you (a conf file in a web context that I developped): https://github.com/ebrigand/RSSLiker/blob/master/src/main/java/com/mrm/rss/init/WebAppConfig.java

0

There is a workaround for this.

You can create a static field in a java class to store the value in.

public class SystemPropertyHelper {
    public static String propertyValue;
}

And create a java class annotated with @Service or @Component, which you can inject the property in, and pass this value to the static field.

@Component
public class SpringBeanClass {

    @Value("${propertyName}")
    private String propertyValue;

    @PostConstruct
    public void init() {
        SystemPropertyHelper.propertyValue = propertyValue;
    }
}

This way you can always access the property value through the static field SystemPropertyHelper.propertyValue

Not the answer you're looking for? Browse other questions tagged or ask your own question.