@Autowired and static method
Asked Answered
F

7

145

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();
    }
}
Firstrate answered 15/7, 2013 at 17:11 Comment(3)
A static method cannot reference a non-static/instance field.Verditer
that is why I created this thread, is there a way that Autowired instance could be accessed from within static method...Firstrate
Why is using @Autowired in static method wrong?Wellordered
A
202

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();
    }
}
Ashtray answered 15/7, 2013 at 17:48 Comment(11)
is this a safe solution?Firstrate
I used the first solution and it worked like a charm, thanks !Gripper
First solution doesn't support use of @Qualifier. It remains problematic if using several Repositories.Glasswork
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.Glasswork
What will guarantee that the constructor is called before the static method is accessed?Stelu
init method will cause SonarQube bug because non-static method modifying static field.Bricole
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.Raymundorayna
Update: I tried with the annotation as well and the first approach doesn't work for me.Raymundorayna
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.Ashtray
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?Manor
Sonar throws issue for first - constructor @Autowired approach - "Static fields should not be updated in constructors"Parasitize
C
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();
    }

}
Candless answered 15/7, 2013 at 17:26 Comment(4)
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?Firstrate
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.Candless
This saved my life. Its very useful over the other approach.Leukoderma
This is a working solution, just don't forget to annotate Boo as @Component.Lamprey
V
13

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.

Verditer answered 15/7, 2013 at 17:25 Comment(0)
A
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..

Aer answered 8/7, 2021 at 2:41 Comment(0)
G
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;
    }
}
Georgiegeorgina answered 15/7, 2013 at 17:25 Comment(0)
P
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.

Pileous answered 1/3, 2019 at 3:51 Comment(0)
T
-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();
}
Tartar answered 3/6, 2014 at 12:11 Comment(2)
What is this?? Whats the difference between @Autowired and getBeanAventine
It is usual when you can't turn the class into a regular spring @Component, it happens a lot with legacy code.Dubrovnik

© 2022 - 2024 — McMap. All rights reserved.