10

I am trying to use an autowired reference from main class and am facing :

Cannot make a static reference to the non-static field zipCodeLookupService.

This is obvious. But I want to know how to handle this situation. What is the correct way of autowiring when main class is involved. My code will be as below -

Interface class

package com.example.services;
public interface IZipCodeLookup {
    String retriveCityForZip(String zipCode);
}

Service Class

package com.example.services;

import org.springframework.stereotype.Service;

@Service
public class ZipCodeLookupService implements IZipCodeLookup {

    @Override
    public String retriveCityForZip(String zipCode) {

        //below is mock code. actual code does a db lookup using a DAO.
        if(zipCode=="94123") return "San Francisco";
        return "not found in DB";
    }
}

here is the main class that requires the service class

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.example.services.IZipCodeLookup;

@SpringBootApplication
public class AutowireWithMainClassApplication {

    @Autowired
    IZipCodeLookup zipCodeLookupService;

    public static void main(String[] args) {
        SpringApplication.run(AutowireWithMainClassApplication.class, args);
        String city;
        //this will not work, compilation error
        //Cannot make a static reference to the non-static field zipCodeLookupService
        city=zipCodeLookupService.retriveCityForZip(args[0]);

        System.out.println("city for zipcode " + args[0] + " is " +city);       
    }
}

Could someone suggest - how or what is the correct way of using autowiring when main class is involved.

(As making the Autowired reference as static does not work anyway)
in AutowireWithMainClassApplication class, changing to -

@Autowired
static IZipCodeLookup zipCodeLookupService;

throws

Exception in thread "main" java.lang.NullPointerException

davidxxx
  • 125,838
  • 23
  • 214
  • 215
samshers
  • 1
  • 6
  • 37
  • 84
  • You need to get the `AutowireWithMainClassApplication` from the `ApplicationContext` and use that instance. As the compiler tells you you cannot reference a non-static field from a static method and `@Autowired` doesn't work on `static` fields. – M. Deinum Oct 07 '17 at 08:24

2 Answers2

14

You can do one of the following:

  1. Use the @Autowired object in a @PostConstruct method, which is executed after dependency injection is done, as davidxxx explained above

  2. Use Spring's getBean() in your main() to explicitly ask Spring framework to return the object after the injection completes:

    public static void main(String[] args) {
        ...
        ConfigurableApplicationContext appContext = SpringApplication.run(StartApplication.class, args);
        IZipCodeLookup service = appContext.getBean(IZipCodeLookup.class);
        ...
    }
    
  3. Use Spring's CommandLineRunner component (runs right after main), which will be responsible on autowiring your object:

    @Component
    public class MyRunner implements CommandLineRunner {
    
        @Autowired
        private IZipCodeLookup service;
    
        @Override
        public void run(String... args) throws Exception {
            ...
            service.doSomething();
            ... 
        }
    }
    
  4. Implement Spring's ApplicationRunner's run method in your main:

    @SpringBootApplication
    public class StartApplication implements ApplicationRunner {
    
        @Autowired
        private IZipCodeLookup service;
    
        public static void main(String[] args) {
            ConfigurableApplicationContext appContext = SpringApplication.run(StartApplication.class, args);
        }
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            ...
            service.doSomething();
            ... 
        }
    }
    
Naor Bar
  • 1,991
  • 20
  • 17
11

A class annotated with a @SpringBootApplication annotation is not a classic bean.
It creates the Spring context from a static method.
But autowired dependencies cannot be static.

That's why this statement :

city=zipCodeLookupService.retriveCityForZip(args[0]);

doesn't throw a Spring exception but a classic NullPointerException as you declare zipCodeLookupService as a static field.


In your case, as workaround, you could move the processing that uses the Spring bean in a instance method annotated with javax.annotation.PostConstruct method inside your main class and store the arguments passed to the main() method in a field in order to be able to use it later :

private static String[] args;
@Autowired
IZipCodeLookup zipCodeLookupService;

public static void main(String[] args) {
    AutowireWithMainClassApplication.args = args;
    SpringApplication.run(AutowireWithMainClassApplication.class, args);
}

@PostConstruct
public void init() {
    String city=zipCodeLookupService.retriveCityForZip(args[0]);
    System.out.println("city for zipcode " + args[0] + " is " +city); 
}

To answer to your comment, you should note several things about @PostConstruct

1) It is not an annotation specific to Spring. So, the official documentation may discuss about things more general than Spring or specific but different things such as EJB (it was originally introduced for them).

2) The first sentence of the javadoc summarizes the general expected behavior.

The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.

But this sentence

"executed after dependency injection is done"

means indeed :

"executed after all dependency injections are done"

We talk about dependency injection in general, not about each dependency injection.
So, yes stick you to that.

Applying it to your case should make things clearer.
The AutowireWithMainClassApplication class is considered as a Spring bean as @SpringBootApplication is annotated with @Configuration that is itself annotated with @Component.
And as any Spring bean, it may declare dependency injection.
That is a dependency injection :

@Autowired
IZipCodeLookup zipCodeLookupService;

But you could of course declare as many dependency injections that you want to :

@Autowired
IZipCodeLookup zipCodeLookupService;

@Autowired
OtherClass otherClass;

...

So only as all dependencies are effectively injected, the PostConstructwill be invoked one and once.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • 1
    wonder full answer. just to confirm - javax.annotation.PostConstruct? right? – samshers Oct 07 '17 at 07:11
  • @samshers A good question requires a good answer :) To answer, yes I refer indeed to `javax.annotation.PostConstruct`. – davidxxx Oct 07 '17 at 07:17
  • I read the PostConstruct documentation. Few things to clarify, how will it work when there are more than one beans being autowired. Does it get called once after each bean construction/injection **or** it gets called once after all the beans in the class are injected (tested, once seems to be the answer). Will appreciate if you can elaborate . – samshers Oct 07 '17 at 07:19
  • "A good question requires a good answer :) " . Never heard!!! Making a note of it. – samshers Oct 07 '17 at 07:25
  • 1
    gr8, that addresses every thing. Another thing I would like to check. Is there any benefit of marking `interface` with `@Component`. I tested, the property is not inherited. Then why do ppl some times use the annotation on interface. I was thinking of using the same (@Component annotation) on interface `IZipCodeLookup` in this question. Having seen no benifits I dropped it. Am i missing any thing important. – samshers Oct 07 '17 at 07:56
  • https://stackoverflow.com/questions/46502450/interfaces-are-annotated-with-component-annotation-in-spring-ioc-di-what-could - this is were I asked it. As this applies to this question also, I am raising it here. – samshers Oct 07 '17 at 07:58
  • `@Component` processing is another subject. It makes clearer to answer it in the specific question. I wrote a comment to your question. Don't hesitate to edit it. – davidxxx Oct 07 '17 at 09:34
  • david, `this.args = args;` in main method would not compile, further the @PostConstruct method seems to run before main method, so there seems to be no way to get the data passed through args[] in to @PostConstruct method. – samshers Oct 08 '17 at 03:32
  • 1
    @samshers Sorry, I didn't test. For the compilation, I suppose that `private String[] args;` should be `private static String[] args;` About the execution order, it is normal : argument copying should be done before running the Spring Boot application. I just see it. I updated :) – davidxxx Oct 08 '17 at 08:10
  • 2
    appreciate your patience to address this problem. Gr8. Ur unbeatable ;-). – samshers Oct 08 '17 at 15:28
  • would you like to help with this also (it is still about spring autowiring) - https://stackoverflow.com/questions/46639069/how-to-pass-constructor-parameter-while-using-spring-auto-wiring – samshers Oct 09 '17 at 08:49
  • @samshers Not an easy question but really interesting. I did an answer. – davidxxx Oct 09 '17 at 15:58