@ Metode autowired dan statis

100

Saya memiliki @Autowiredlayanan yang harus digunakan dari dalam metode statis. Saya tahu ini salah tetapi saya tidak dapat mengubah desain saat ini karena akan membutuhkan banyak pekerjaan, jadi saya perlu beberapa peretasan sederhana untuk itu. Saya tidak bisa mengubah randomMethod()menjadi non-statis dan saya perlu menggunakan kacang autowired ini. Ada petunjuk bagaimana melakukan itu?

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

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}
Taks
sumber
4
Metode statis tidak dapat mereferensikan bidang non-statis / instance.
Sotirios Delimanolis
18
itulah sebabnya saya membuat utas ini, apakah ada cara agar instance Autowired dapat diakses dari dalam metode statis ...
Taks
Mengapa menggunakan @Autowired dalam metode statis salah?
pengguna59290

Jawaban:

151

Anda dapat melakukan ini dengan mengikuti salah satu solusi:

Menggunakan konstruktor @Autowired

Pendekatan ini akan membangun kacang yang membutuhkan beberapa kacang sebagai parameter konstruktor. Dalam kode konstruktor Anda menyetel bidang statis dengan nilai yang didapat sebagai parameter untuk eksekusi konstruktor. Sampel:

@Component
public class Boo {

    private static Foo foo;

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

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

Menggunakan @PostConstruct untuk memberikan nilai ke bidang statis

Idenya di sini adalah untuk menyerahkan kacang ke bidang statis setelah kacang dikonfigurasi oleh pegas.

@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
sumber
3
apakah ini solusi yang aman?
Taks
2
Saya menggunakan solusi pertama dan bekerja dengan sangat baik, terima kasih!
victorleduc
1
Solusi pertama tidak mendukung penggunaan @Qualifier. Tetap bermasalah jika menggunakan beberapa Repositori.
pengguna1767316
15
Apa yang akan menjamin bahwa konstruktor dipanggil sebelum metode statis diakses?
David Dombrowsky
2
Metode init akan menyebabkan bug SonarQube karena metode non-statis mengubah bidang statis.
jDub9
45

Anda harus mengatasinya melalui pendekatan pengakses konteks aplikasi statis:

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

}

Kemudian Anda dapat mengakses instance bean secara statis.

public class Boo {

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

}
Pavel Horal
sumber
Saya benar-benar menyukai solusi ini meskipun saya tidak sepenuhnya memahaminya .. Saya baru saja memikirkan tentang musim semi dan saya perlu segera merefaktor beberapa bagian kode .. dan ini adalah masalah pencampuran statis dengan autowired .. seberapa aman solusi ini?
Taks
2
Ini cukup aman jika panggilan statis berada di bawah kendali Anda. Aspek negatif yang paling jelas adalah bahwa Anda dapat memanggil getBeansebelum konteks diinisialisasi (NPE) atau setelah konteks dengan biji dihancurkan. Pendekatan ini memiliki keuntungan bahwa akses konteks statis "jelek" diapit dalam satu metode / kelas.
Pavel Horal
1
Ini menyelamatkan hidup saya. Ini sangat berguna dibandingkan pendekatan lainnya.
phoenix
6

Yang dapat Anda lakukan adalah @Autowiredmetode penyetel dan menyetelnya ke bidang statis baru.

public class Boo {
    @Autowired
    Foo foo;

    static Foo staticFoo;   

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

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

Ketika kacang diproses, Spring akan memasukkan sebuah Foocontoh implementasi ke dalam field contoh foo. Ini kemudian akan memasukkan Foocontoh yang sama ke dalam setStaticFoo()daftar argumen, yang akan digunakan untuk mengatur bidang statis.

Ini adalah solusi yang buruk dan akan gagal jika Anda mencoba menggunakan randomMethod()sebelum Spring memproses instance Boo.

Sotirios Delimanolis
sumber
akan menggunakan bantuan @PostConstruct?
Taks
@Taks Tentu, itu juga berhasil. Yaitu setStaticFoo(), tanpa Fooparameter.
Sotirios Delimanolis
pertanyaannya adalah apakah itu akan membuatnya lebih aman .. :) Saya pikir pegas itu akan memproses semuanya sebelum memungkinkan kita untuk mengeksekusi metode apa pun ..
Taks
1
@Taks Cara Anda menunjukkannya tidak berhasil (kecuali Anda menunjukkan kode semu). Ada petunjuk bagaimana melakukan itu? Beberapa jawaban yang Anda dapatkan adalah solusi tetapi semuanya memiliki masalah yang sama sehingga Anda tidak dapat menggunakan bidang statis sampai Spring memproses kelas Anda (sebenarnya memproses satu contoh yang memiliki efek samping). Dalam hal ini, itu tidak aman.
Sotirios Delimanolis
3

Ini menyebalkan tetapi Anda bisa mendapatkan kacang dengan menggunakan ApplicationContextAwareantarmuka. Sesuatu seperti :

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
sumber
0

Ini dibangun berdasarkan jawaban @ Pavel , untuk memecahkan kemungkinan konteks Spring tidak diinisialisasi saat mengakses dari metode getBean statis:

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

Bagian penting di sini adalah initContextmetodenya. Ini memastikan bahwa konteks akan selalu diinisialisasi. Tapi, perhatikan bahwa itu initContextakan menjadi poin pertikaian dalam kode Anda saat disinkronkan. Jika aplikasi Anda sangat diparalelkan (misalnya: bagian belakang situs dengan lalu lintas tinggi), ini mungkin bukan solusi yang baik untuk Anda.

Hashken
sumber
-2

Gunakan AppContext. Pastikan Anda membuat kacang di file konteks Anda.

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

public static void randomMethod() {
     foo.doStuff();
}
Vijay
sumber
Apa ini?? Apa perbedaan antara @Autowired dan getBean
madhairsilence
Hal ini biasa terjadi ketika Anda tidak dapat mengubah kelas menjadi @Component pegas biasa, ini sering terjadi dengan kode lama.
carpinchosaurio