@Autowired и статический метод

101

У меня есть @Autowiredслужба, которую нужно использовать из статического метода. Я знаю, что это неправильно, но я не могу изменить текущий дизайн, так как это потребует много работы, поэтому для этого мне нужен простой взлом. Я не могу изменить его randomMethod()на нестатичность, и мне нужно использовать этот автосоединенный компонент. Есть подсказки, как это сделать?

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

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}
Такс
источник
4
Статический метод не может ссылаться на нестатическое поле / поле экземпляра.
Сотириос Делиманолис
18
вот почему я создал этот поток, есть ли способ, которым можно получить доступ к экземпляру Autowired из статического метода ...
Такс
Почему неправильно использовать @Autowired в статическом методе?
user59290

Ответы:

153

Вы можете сделать это, следуя одному из решений:

Использование конструктора @Autowired

При таком подходе будет создан компонент, требующий некоторых компонентов в качестве параметров конструктора. В коде конструктора вы устанавливаете статическое поле со значением, полученным в качестве параметра для выполнения конструктора. Образец:

@Component
public class Boo {

    private static Foo foo;

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

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

Использование @PostConstruct для передачи значения в статическое поле

Идея здесь состоит в том, чтобы передать bean-компонент статическому полю после того, как bean настроен с помощью 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();
    }
}
Франсиско Спет
источник
3
это безопасное решение?
Taks
2
Я использовал первое решение, и оно отлично сработало, спасибо!
victorleduc
1
Первое решение не поддерживает использование @Qualifier. При использовании нескольких репозиториев это остается проблематичным.
user1767316
16
Что гарантирует, что конструктор будет вызван до обращения к статическому методу?
Дэвид Домбровски
2
Метод init вызовет ошибку SonarQube, поскольку нестатический метод изменяет статическое поле.
jDub9
45

Вы должны решить эту проблему с помощью метода доступа к статическому контексту приложения:

@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);
    }

}

Затем вы можете получить доступ к экземплярам bean статическим способом.

public class Boo {

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

}
Павел Горал
источник
Мне на самом деле нравится это решение, хотя я не совсем его понимаю ... Я просто начинаю думать о Spring, и мне нужно быстро рефакторинг некоторого фрагмента кода ... и это проблема смешивания статики с autowired ... насколько безопасно это решение?
Taks
2
Это довольно безопасно, если статические вызовы находятся под вашим контролем. Наиболее очевидный отрицательный аспект заключается в том, что может случиться так, что вы вызовете getBeanдо инициализации контекста (NPE) или после того, как контекст с его компонентами будет уничтожен. Этот подход имеет то преимущество, что "уродливый" доступ к статическому контексту заключен в один метод / класс.
Pavel Horal
1
Это спасло мне жизнь. Это очень полезно по сравнению с другим подходом.
Phoenix
6

Что вы можете сделать, так это @Autowiredустановить метод установки и установить новое статическое поле.

public class Boo {
    @Autowired
    Foo foo;

    static Foo staticFoo;   

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

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

Когда bean-компонент будет обработан, Spring вставит Fooэкземпляр реализации в поле экземпляра foo. Затем он также вставит тот же Fooэкземпляр в setStaticFoo()список аргументов, который будет использоваться для установки статического поля.

Это ужасный обходной путь, и он потерпит неудачу, если вы попытаетесь использовать его randomMethod()до того, как Spring обработает экземпляр Boo.

Сотириос Делиманолис
источник
поможет ли @PostConstruct?
Taks
@Taks Конечно, это тоже работает. То setStaticFoo()есть без Fooпараметра.
Сотириос Делиманолис
вопрос в том, сделает ли это более безопасным .. :) Я думал, что spring обработает все, прежде чем позволит нам выполнять какие-либо методы ..
Taks
1
@Taks То, как вы это показали, не работает (если вы не показываете псевдокод). Есть подсказки, как это сделать? Полученные вами несколько ответов являются обходными путями, но все они имеют одну и ту же проблему: вы не можете использовать статическое поле, пока Spring не обработает ваш класс (фактически обрабатывая один экземпляр, который имеет побочный эффект). В этом смысле это небезопасно.
Сотириос Делиманолис
3

Это отстой, но вы можете получить компонент, используя ApplicationContextAwareинтерфейс. Что-то вроде :

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;
    }
}
Жан-Филипп Бонд
источник
0

Это основано на ответе @Pavel, чтобы решить возможность того, что контекст Spring не инициализируется при доступе из статического метода getBean:

@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 ();
  }
}

Важная часть здесь - это initContextметод. Это гарантирует, что контекст всегда будет инициализирован. Но учтите, что initContextэто будет предметом спора в вашем коде, поскольку он синхронизируется. Если ваше приложение сильно распараллелено (например, серверная часть сайта с высоким трафиком), это может быть не лучшим решением для вас.

Хэшкен
источник
-2

Используйте AppContext. Убедитесь, что вы создали bean-компонент в своем файле контекста.

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

public static void randomMethod() {
     foo.doStuff();
}
Виджай
источник
Что это?? В чем разница между @Autowired и getBean
madhairsilence
Обычно вы не можете превратить класс в обычный Spring @Component, это часто случается с устаревшим кодом.
carpinchosaurio 08