140

I have @Autowired service which has to be used from within a static method. I know this is wrong but I cannot change the current design as it would require a lot of work, so I need some simple hack for that. I can't change randomMethod() to be non-static and I need to use this autowired bean. Any clues how to do that?

@Service
public class Foo {
    public int doStuff() {
        return 1;
    }
}

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}
Ajinkya
  • 22,324
  • 33
  • 110
  • 161
Taks
  • 2,033
  • 4
  • 18
  • 23

7 Answers7

193

You can do this by following one of the solutions:

Using constructor @Autowired

This approach will construct the bean requiring some beans as constructor parameters. Within the constructor code you set the static field with the value got as parameter for constructor execution. Sample:

@Component
public class Boo {

    private static Foo foo;

    @Autowired
    public Boo(Foo foo) {
        Boo.foo = foo;
    }

    public static void randomMethod() {
         foo.doStuff();
    }
}

Using @PostConstruct to hand value over to static field

The idea here is to hand over a bean to a static field after bean is configured by spring.

@Component
public class Boo {

    private static Foo foo;
    @Autowired
    private Foo tFoo;

    @PostConstruct
    public void init() {
        Boo.foo = tFoo;
    }

    public static void randomMethod() {
         foo.doStuff();
    }
}
Francisco Spaeth
  • 23,493
  • 7
  • 67
  • 106
  • 3
    is this a safe solution? – Taks Jul 15 '13 at 17:51
  • 3
    I used the first solution and it worked like a charm, thanks ! – victorleduc Jul 13 '16 at 10:43
  • 1
    First solution doesn't support use of @Qualifier. It remains problematic if using several Repositories. – user1767316 Dec 16 '16 at 12:15
  • If `doStuff` is static, Second solution seems also to fail if using several Repositories using the @Qualifier annotation because in that case `foo` need be an Interface and since static method need be invoked on "containing interface class" (implementation class) only, the `doStuff` method can't be used. – user1767316 Dec 16 '16 at 12:32
  • 26
    What will guarantee that the constructor is called before the static method is accessed? – David Dombrowsky Aug 18 '17 at 20:34
  • 4
    init method will cause SonarQube bug because non-static method modifying static field. – wheeleruniverse Oct 16 '18 at 17:19
  • In your first approach, is `@Autowired` required on top of the constructor? Or is it optional? Asking because I currently do not have this annotation(as I read somewhere that this is optional) and my constructor isn't called before accessing the static method. – Ram Patra May 01 '20 at 15:50
  • Update: I tried with the annotation as well and the first approach doesn't work for me. – Ram Patra May 01 '20 at 16:11
  • If you don't have the annotation in the classpath maybe spring is not even being used. By the way, on spring 5 an constructor autowiring isn't really required if you have the class added to your context and eligible to component scan. – Francisco Spaeth May 01 '20 at 16:28
  • 1
    I'm trying to use both implementations inside of a library and inject it as a dependency into another project. But when it is called by the importing project Annotation PostConstruct methods, the constructor hasn't been called and the bean hasn't been Annotation Autowired yet unless I inject the library's bean manually or use Annotation DependsOn in the importing project class using the Annotation PostConstruct. Is there a way to force any of these implementations to be called before the caller Annotation PostConstruct? – White_King May 12 '21 at 09:37
  • Sonar throws issue for first - `constructor @Autowired` approach - "Static fields should not be updated in constructors" – Ashutosh Sharma May 18 '22 at 12:59
55

You have to workaround this via static application context accessor approach:

@Component
public class StaticContextAccessor {

    private static StaticContextAccessor instance;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void registerInstance() {
        instance = this;
    }

    public static <T> T getBean(Class<T> clazz) {
        return instance.applicationContext.getBean(clazz);
    }

}

Then you can access bean instances in a static manner.

public class Boo {

    public static void randomMethod() {
         StaticContextAccessor.getBean(Foo.class).doStuff();
    }

}
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
Pavel Horal
  • 17,782
  • 3
  • 65
  • 89
  • I actually like this solution although I dont fully understand it.. Im just getting my head around spring and I need to quickly refactor some piece of code.. and this is the issue of mixing static with autowired.. how safe is this solution? – Taks Jul 15 '13 at 17:53
  • 2
    It is fairly safe if the static calls are under your control. The most obvious negative aspect is that it can happen that you will call `getBean` before the context is initialized (NPE) or after the context with its beans is destroyed. This approach has its benefit that *the "ugly" static context access* is enclosed in one method / class. – Pavel Horal Jul 15 '13 at 18:25
  • 1
    This saved my life. Its very useful over the other approach. – phoenix Jan 18 '15 at 11:18
  • This is a working solution, just don't forget to annotate `Boo` as `@Component`. – Cortex Jan 01 '21 at 16:35
12

What you can do is @Autowired a setter method and have it set a new static field.

public class Boo {
    @Autowired
    Foo foo;

    static Foo staticFoo;   

    @Autowired
    public void setStaticFoo(Foo foo) {
        Boo.staticFoo = foo;
    }

    public static void randomMethod() {
         staticFoo.doStuff();
    }
}

When the bean gets processed, Spring will inject a Foo implementation instance into the instance field foo. It will then also inject the same Foo instance into the setStaticFoo() argument list, which will be used to set the static field.

This is a terrible workaround and will fail if you try to use randomMethod() before Spring has processed an instance of Boo.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
8

The easiest way to create a static context is naturally, when the application starts up. This will prevent the need for an unnatural implementation with an additional class.

@SpringBootApplication
public class MyApplication {

    private static ApplicationContext appContext;


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

    public static ApplicationContext getAppContext() {
        return appContext;
    }
}

Then, anywhere you need to access a bean statically, you can use the ApplicationContext to get the instance of the class.

public class Boo {
    public static void randomMethod() {
         MyApplication.getAppContext()
                            .getBean(Foo.class).doStuff();
    }
}

Regards..

Bludwood
  • 101
  • 1
  • 5
5

It is not the best but you can get the bean by using the ApplicationContextAware interface. Something like :

public class Boo implements ApplicationContextAware {

    private static ApplicationContext appContext;

    @Autowired
    Foo foo;

    public static void randomMethod() {
         Foo fooInstance = appContext.getBean(Foo.class);
         fooInstance.doStuff();
    }

    @Override
    public void setApplicationContext(ApplicationContext appContext) {
        Boo.appContext = appContext;
    }
}
Jean-Philippe Bond
  • 10,089
  • 3
  • 34
  • 60
0

This builds upon @Pavel's answer, to solve the possibility of Spring context not being initialized when accessing from the static getBean method:

@Component
public class Spring {
  private static final Logger LOG = LoggerFactory.getLogger (Spring.class);

  private static Spring spring;

  @Autowired
  private ApplicationContext context;

  @PostConstruct
  public void registerInstance () {
    spring = this;
  }

  private Spring (ApplicationContext context) {
    this.context = context;
  }

  private static synchronized void initContext () {
    if (spring == null) {
      LOG.info ("Initializing Spring Context...");
      ApplicationContext context = new AnnotationConfigApplicationContext (io.zeniq.spring.BaseConfig.class);
      spring = new Spring (context);
    }
  }

  public static <T> T getBean(String name, Class<T> className) throws BeansException {
    initContext();
    return spring.context.getBean(name, className);
  }

  public static <T> T getBean(Class<T> className) throws BeansException {
    initContext();
    return spring.context.getBean(className);
  }

  public static AutowireCapableBeanFactory getBeanFactory() throws IllegalStateException {
    initContext();
    return spring.context.getAutowireCapableBeanFactory ();
  }
}

The important piece here is the initContext method. It ensures that the context will always get initialized. But, do note that initContext will be a point of contention in your code as it is synchronized. If your application is heavily parallelized (for eg: the backend of a high traffic site), this might not be a good solution for you.

Hashken
  • 4,396
  • 7
  • 35
  • 51
-2

Use AppContext. Make sure you create a bean in your context file.

private final static Foo foo = AppContext.getApplicationContext().getBean(Foo.class);

public static void randomMethod() {
     foo.doStuff();
}
Vijay
  • 5
  • 1