@Автопровідний і статичний метод


100

У мене є @Autowiredсервіс, який повинен використовуватися із статичного методу. Я знаю, що це неправильно, але я не можу змінити поточний дизайн, оскільки це зажадає багато роботи, тому для цього мені потрібен якийсь простий хак. Я не можу randomMethod()стати нестатичним, і мені потрібно використовувати цей автоматично підключений бін. Будь-які підказки, як це зробити?

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

public class Boo {
    @Autowired
    Foo foo;

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

4
Статичний метод не може посилатися на нестатичне поле / примірник.
Sotirios Delimanolis

18
саме тому я створив цей потік, чи є спосіб, щоб до Autowired екземпляра можна було отримати доступ із статичного методу ...
Займає

Чому неправильне використання @Autowired у статичному методі?
user59290

Відповіді:


151

Ви можете зробити це, дотримуючись одного з рішень:

Використання конструктора @Autowired

Цей підхід побудує квасоля, яка потребує деяких бобів як параметрів конструктора. В коді конструктора ви встановлюєте статичне поле зі значенням, отриманим як параметр для виконання конструктора. Зразок:

@Component
public class Boo {

    private static Foo foo;

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

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

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

Ідея тут полягає в тому, щоб передати боб у статичне поле після того, як боб налаштований до весни.

@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
це безпечне рішення?
Такси

2
Я використав перше рішення, і це спрацювало як шарм, дякую!
victorleduc

1
Перше рішення не підтримує використання @Qualifier. Залишається проблематичним, якщо використовується декілька сховищ.
користувач1767316

15
Що гарантуватиме виклик конструктора до доступу до статичного методу?
Девід Домбровський,

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

}

Мені насправді подобається це рішення, хоча я його до кінця не розумію .. Я просто розбираюся з весною, і мені потрібно швидко переробити якийсь шматок коду .. і це питання змішування статичного з автоматичним підключенням .. наскільки це рішення безпечне?
Займає

2
Це досить безпечно, якщо статичні дзвінки під вашим контролем. Найбільш очевидним негативним аспектом є те, що може статися, що ви будете телефонувати getBeanдо ініціалізації контексту (NPE) або після знищення контексту з його компонентами. Цей підхід має свою перевагу в тому, що "потворний" доступ до статичного контексту укладається в один метод / клас.
Pavel Horal

1
Це врятувало мені життя. Це дуже корисно в порівнянні з іншим підходом.
Фенікс

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

Коли компонент обробляється, Spring вводить Fooекземпляр реалізації в поле екземпляра foo. Потім він також введе той самий Fooекземпляр у setStaticFoo()список аргументів, який буде використовуватися для встановлення статичного поля.

Це страшне обхідне рішення, і воно не вдасться, якщо ви спробуєте використати randomMethod()до того, як Spring обробить екземпляр Boo.


чи допомогло б використання @PostConstruct?
Займає

@Taks Звичайно, це теж працює. На setStaticFoo()то є без Fooпараметра.
Сотіріос Деліманоліс

питання в тому, чи зробить це безпечнішим .. :) Я думав, що весна все обробить, перш ніж дозволити нам виконувати будь-які методи ..
Займає

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.