0

I've inherited some Java code. Several classes have their instance variables initialized from property values in /WEB-INF/servlet.properties like this:

@Value("${context.root}")
private String contextRoot;

When I try this in a new class, the instance variable is not initialized. My class is constructed similarly to the one that works, but it is in a different package (com.company.app.utilities vs. com.company.app.service) Both import the same class:

import org.springframework.beans.factory.annotation.Value;

Both have corresponding public getter and setter methods.

I've reviewed some Spring documentation and /WEB-INF/applicationContext.xml, but I don't see anything obvious that I need to configure.

Any assistance is greatly appreciated.

Update: I see the following entries in the log:

[localhost-startStop-1] 03 Dec 2014 02:47:10,791 INFO : org.springframework.context.support.PropertySourcesPlaceholderConfigurer - Loading properties file from ServletContext resource [/WEB-INF/servlet.properties]
[localhost-startStop-1] 03 Dec 2014 02:47:10,938 INFO : org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@601e8f7d: defining beans [...,contactServiceImpl,s3Transfer,...]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@32f5f812

I've omitted all other singletons from the log entry, to highlight that both the original class and my class are in the list; however, I created a constructor, whereas the original class has no constructor. The next line in the log is the exception I throw in a getter method when the value is null, caught inside the constructor:

[localhost-startStop-1] 03 Dec 2014 02:47:12,730 ERROR: com.company.app.utilities.S3Transfer - bucketName is null!
java.lang.Exception: bucketName is null!
    at com.company.app.utilities.S3Transfer.getBucketName(S3Transfer.java:129)
    at com.company.app.utilities.S3Transfer.<init>(S3Transfer.java:48)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:148)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1000)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:953)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:487)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:626)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
    at org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:651)
    at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:599)
    at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:665)
    at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:518)
    at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:459)
    at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136)
    at javax.servlet.GenericServlet.init(GenericServlet.java:160)

...

Should I remove the constructor? If so, is there additional Spring configuration to instantiate a singleton of this class? Thanks.

Update 2014-12-03: I think I've been away from Java too long, as the last time I coded AOP, there was no Spring Framework, and it's got me a bit confused. When you say "instantiated by Spring", does this mean to place @Autowired in the class that uses my new class? I've made this change and I've rewritten my class to implement an interface, but now Tomcat fails to restart properly. Code, properties and logs below:

package com.company.app.utilities;

import com.company.app.bean.Contact;
import java.io.InputStream;

public interface S3Transfer {
    String storeContactProfilePicture(Long idUser, Contact contact);
    String storeContactProfilePicture(Long idUser, Long idContact, InputStream inStream);
    String storeUserProfilePicture(Long idUser, String fileName, String accountType);
}

package com.company.app.utilities;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.company.app.bean.Contact;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;

public class S3TransferImpl implements S3Transfer {

    private final AmazonS3 s3client = new AmazonS3Client();

    private final Logger logger = Logger.getLogger(getClass());

    @Value("${context.root}")
    private String contextRoot;

    @Value("${s3.bucket.name}")
    private String bucketName;

    @Value("${contact.profile.picture}")
    private String contactProfilePictureKey;

    @Value("${user.profile.picture}")
    private String userPictureKey;

    public String storeContactProfilePicture(Long idUser, Contact contact) {
    String keyName = getContactProfilePictureKey().replaceAll("<<idUsr>>", idUser.toString()).replaceAll("<<idContact>>", contact.getIdContact().toString());
    String fileName = contact.getPicture();

    storeObjectFromFileName(fileName, keyName);
    return contextRoot + keyName;
    }

    public String storeContactProfilePicture(Long idUser, Long idContact, InputStream inStream) {
    String keyName = getContactProfilePictureKey().replaceAll("<<idUsr>>", idUser.toString()).replaceAll("<<idContact>>", idContact.toString());

    storeObject(inStream, keyName);
    return contextRoot + keyName;
    }

    public String storeUserProfilePicture(Long idUser, String fileName, String accountType) {
    String keyName = getUserPictureKey().replaceAll("<<idUsr>>", idUser.toString()).replace("<<socialNetwork>>", accountType);

    storeObjectFromFileName(fileName, keyName);
    return contextRoot + keyName;
    }

    private void storeObjectFromFileName(String fileName, String keyName) {
    try {
        logger.info("Uploading " + fileName + " to " + keyName);
        InputStream inStream = new URL(fileName).openStream();
        storeObject(inStream, keyName);
    } catch (IOException e) {
        logger.error(e.getMessage(), e);
    }
    }

    private void storeObject(InputStream inStream, String keyName) {
    try {
        this.s3client.putObject(new PutObjectRequest(getBucketName(), keyName, inStream, null));
    } catch (AmazonServiceException ase) {
        logger.error("Caught an AmazonServiceException, which "
            + "means your request made it "
            + "to Amazon S3, but was rejected with an error response"
            + " for some reason.");
        logger.error("Error Message:    " + ase.getMessage());
        logger.error("HTTP Status Code: " + ase.getStatusCode());
        logger.error("AWS Error Code:   " + ase.getErrorCode());
        logger.error("Error Type:       " + ase.getErrorType());
        logger.error("Request ID:       " + ase.getRequestId());
    } catch (AmazonClientException ace) {
        logger.error("Caught an AmazonClientException, which "
            + "means the client encountered "
            + "an internal error while trying to "
            + "communicate with S3, "
            + "such as not being able to access the network.");
        logger.error("Error Message: " + ace.getMessage());
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    }

    public String getContextRoot() {
    return contextRoot;
    }

    public void setContextRoot(String contextRoot) {
    this.contextRoot = contextRoot;
    }

    public String getBucketName() {
    return bucketName;
    }

    public void setBucketName(String bucketName) {
    this.bucketName = bucketName;
    }

    private String getContactProfilePictureKey() {
    return contactProfilePictureKey;
    }

    private void setContactProfilePictureKey(String contactProfilePictureKey) {
    this.contactProfilePictureKey = contactProfilePictureKey;
    }

    private String getUserPictureKey() {
    return userPictureKey;
    }

    private void setUserPictureKey(String userPictureKey) {
    this.userPictureKey = userPictureKey;
    }

}

The classes that use S3Transfer now have the following code:

@Autowired
private S3Transfer s3Transfer;

Both /WEB-INF/applicationContext.xml and /WEB-INF/app-web-servlet.xml have the following elements within the complex bean element:

<context:annotation-config />
<context:component-scan base-package="com.company.app" />
<context:property-placeholder location="/WEB-INF/servlet.properties" />

When I restart Tomcat, initialization fails. Here are some of the relevant log entries:

    [localhost-startStop-1] 03 Dec 2014 18:39:03,407 INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
[localhost-startStop-1] 03 Dec 2014 18:39:03,926 INFO : org.springframework.context.annotation.ClassPathBeanDefinitionScanner - JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning
[localhost-startStop-1] 03 Dec 2014 18:39:05,990 INFO : org.springframework.context.annotation.ClassPathBeanDefinitionScanner - JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning
[localhost-startStop-1] 03 Dec 2014 18:39:07,983 INFO : org.springframework.context.support.PropertySourcesPlaceholderConfigurer - Loading properties file from ServletContext resource [/WEB-INF/servlet.properties]
[localhost-startStop-1] 03 Dec 2014 18:39:08,708 INFO : org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@63655d7a: defining beans [... {long list, but s3TransferImpl is not in the list} ...]; root of factory hierarchy
[localhost-startStop-1] 03 Dec 2014 18:39:10,667 INFO : org.springframework.jdbc.datasource.DriverManagerDataSource - Loaded JDBC driver: com.mysql.jdbc.Driver
[localhost-startStop-1] 03 Dec 2014 18:39:20,152 ERROR: org.springframework.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is ... {long list of nested exceptions}...; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.company.app.utilities.S3Transfer] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:329)

Update 2014-12-03 14:50 EST: Apparently, both my interface and class needed an @Service annotation, as Tomcat restarted successfully. What is the purpose of this annotation?

user4240162
  • 13
  • 1
  • 6
  • Check http://stackoverflow.com/questions/11890544/spring-value-annotation-in-controller-class-not-evaluating-to-value-inside-pro – Sully Dec 02 '14 at 21:05
  • for singleton you mentioned, do you mean singleton-scoped bean in Spring, or the singleton as in Singleton design pattern? Have you make sure that, in the app context instantiating your bean, there is actually a PropertyPlaceHolderConfigurer created (by any means) which points to your desired property file? It is very likely that you only have the configurer in the main app context, but not in the app context of your dispatcher servlet. – Adrian Shum Dec 03 '14 at 03:49
  • The following entries are in both applicationContext.xml and app-web-servlet.xml: – user4240162 Dec 03 '14 at 04:18
  • Are you sure that such property exists in your servlet.properties? Personal advise: make a as-small-as-possible testing app and make sure it works. It is also not very advisable to touch the web-app resources (WEB-INF/servlet.properties) for your supposed-to-be-webapp-neutral parent app context (though I think it is not the source of problem) – Adrian Shum Dec 06 '14 at 08:09

2 Answers2

0

The package of the new class i.e. com.company.app.service needs to be added to the xml file which has the spring configuration. You need to add this to your spring XML configuration file

<beans>
    <context:component-scan base-package="com.package.containing.yourclass" />
</beans>

You need to this so that Spring understands which classes it needs to scan for annotations and/or creating beans.

Harshul Pandav
  • 1,016
  • 11
  • 23
  • Thanks, but that didn't seem to work. There are two files in the /WEB-INF folder, applicationContext.xml and app-web-servlet.xml that have the complex element. the former had two declarations: and the latter had just I added to both of them and re-started Tomcat, but it had no effect. – user4240162 Dec 02 '14 at 21:58
  • Could you add some part of your spring configuration file and also how are you initializing the class? For the annotations to work, the classes need to be initialized from the spring context. – Harshul Pandav Dec 02 '14 at 22:00
  • Thanks again Harshul. I've listed some log entries from the Tomcat startup. Should I not be defining a constructor? – user4240162 Dec 03 '14 at 03:25
  • 1
    wrong answer. `@Value` (or other DI annotation) does not require component scan – Adrian Shum Dec 03 '14 at 03:47
-1

Could you please share the code where you are trying to access the variable. If it is inside contructor, then you will get error. As singleton beans being instiated during loading will not be able to access these values from property configurator. You may consider to move your code to in-it method, so that your variable will set while accessing the field. Also, confirm if your class is being instantited by spring container, not by some other means like new operator

Panther
  • 3,312
  • 9
  • 27
  • 50