1

I am trying to @Autowire a @Configuration class inside a @Service class. basically my @Configuration class contains mapping to my custom .properties file. When i try to autowire my configuration class inside my service class, BeanCreationException occurs. I am not sure what happen. Just followed the guide on creating Property classes from spring. There must be something i missed out.

Also, when i try to autowire @Configuration class to another @Configuration class, it runs smoothly

Currently, i know that, prop is always null because when i remove prop.getUploadFileLocation() call, everything will be fine. There must be something wrong during autowiring.

Here is my Service class

    @Service
    public class ImageService {

        public static Logger logger = Logger.getLogger(ImageService.class.getName());

        @Autowired
        MyProperties prop;

        private final String FILE_UPLOAD_LOCATION = prop.getUploadFileLocation() +"uploads/images/";

        public void upload(String base64ImageFIle) throws IOException {
            logger.info(FILE_UPLOAD_LOCATION);
        }
    }

Here is my Configuration class

    @Data
    @Configuration
    @ConfigurationProperties (prefix = "my")
    public class MyProperties {

        private String resourceLocation;

        private String resourceUrl;

        public String getUploadFileLocation() {
            return getResourceLocation().replace("file:///", "");
        }

        public String getBaseResourceUrl() {
            return getResourceUrl().replace("**", "");
        }
    }

And here is where i can successfully use MyProperties

    @Configuration
    public class StaticResourceConfiguration implements WebMvcConfigurer {

        @Autowired
        MyProperties prop;

        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler(prop.getResourceUrl())
                    .addResourceLocations(prop.getResourceLocation());
        }
    }
Junayed
  • 91
  • 6
lemoncodes
  • 2,371
  • 11
  • 40
  • 66
  • Use constructor injection. Field injection isn't applied until after the object is already constructed. – chrylis -cautiouslyoptimistic- Apr 10 '20 at 04:43
  • @chrylis-onstrike- isnt that the whole autowiring thing about? At least thats what i understand though – lemoncodes Apr 10 '20 at 04:47
  • 2
    It's still autowired if you use constructor injection, it's just implicit. It's generally discouraged by the spring team themselves to use `autowired` on fields – 123 Apr 10 '20 at 04:50
  • 2
    The issue isn't you autowiring the field, it's that you are trying to use it in a final declaration when the autowiring happens after the class is instantiated. Therefore it could never not be null. – 123 Apr 10 '20 at 04:52
  • ohh i see, i failed to notice that part. Yep, i agree, i got the timing of the instantiation wrong. It got no problem with autowiring things, its just that the timing where i used the autowired field is incorrect. Please post an answer so i can accept this. basically i forgot to consider the life cycle of things. – lemoncodes Apr 10 '20 at 05:00
  • "Autowiring" means that Spring takes care of finding and connecting the beans for you. It can use the constructor, setter methods, or fields directly. There are lots of problems with direct field access; use the constructor instead. – chrylis -cautiouslyoptimistic- Apr 10 '20 at 06:25

2 Answers2

3

The issue is that you are trying to use an autowired field to set the value in an inline field assignment.

That means

private final String FILE_UPLOAD_LOCATION = prop.getUploadFileLocation() +"uploads/images/";

is executed before the prop is autowired, meaning it will always be null

The way to mitigate this would be to use constructor injection instead.

@Service
public class ImageService {

    //Fine since you are using static method
    public static Logger logger = Logger.getLogger(ImageService.class.getName());

    //Not needed if you are only using it to set FILE_UPLOAD_LOCATION
    //Allows field to be final
    private final MyProperties prop;

    //Still final
    private final String FILE_UPLOAD_LOCATION;

    //No need for @Autowired since implicit on component constructors
    ImageService(MyProperties prop){

        //Again not needed if you aren't going to use anywhere else in the class
        this.prop = prop;

        FILE_UPLOAD_LOCATION = prop.getUploadFileLocation() +"uploads/images/";
    }

    public void upload(String base64ImageFIle) throws IOException {
        logger.info(FILE_UPLOAD_LOCATION);
    }
}

See this question for why constructor is preferred over @autowired in general

123
  • 10,778
  • 2
  • 22
  • 45
  • 2
    Two notes: `@Autowired` is unnecessary only when you have exactly one constructor (but this is usually the case for services), and it is often/usually preferable to use an `@Bean` method if you're pulling from an `@ConfigurationProperties` to avoid having an unnecessary direct link between the configuration mechanism and the service class. – chrylis -cautiouslyoptimistic- Apr 10 '20 at 06:26
  • @chrylis-onstrike- Can you elaborate on your second note, not sure I follow? – 123 Apr 10 '20 at 07:00
  • 1
    `ImageService` needs a `String fileUploadLocation`, and that's all. There's no need for it to know about `MyProperties`. Using an `@Bean` method, you can receive the `MyProperties` and provide the `fileUploadLocation` to the `ImageService` without having to directly link them. – chrylis -cautiouslyoptimistic- Apr 10 '20 at 07:33
0

If you need MyProperties bean to be created before StaticResourceConfiguration bean, you can put @ConditionalOnBean(MyProperties.class) as following. Spring will make sure MyProperties is there before processing StaticResourceConfiguration.

@Configuration
@ConditionalOnBean(MyProperties.class)
public class StaticResourceConfiguration implements WebMvcConfigurer {
Mr.J4mes
  • 9,168
  • 9
  • 48
  • 90