Lingkup kacang @Scope ("prototipe") tidak membuat kacang baru

133

Saya ingin menggunakan kacang prototipe beranotasi di controller saya. Tapi musim semi malah membuat kacang singleton. Ini kode untuk itu:

@Component
@Scope("prototype")
public class LoginAction {

  private int counter;

  public LoginAction(){
    System.out.println(" counter is:" + counter);
  }
  public String getStr() {
    return " counter is:"+(++counter);
  }
}

Kode pengontrol:

@Controller
public class HomeController {
    @Autowired
    private LoginAction loginAction;

    @RequestMapping(value="/view", method=RequestMethod.GET)
    public ModelAndView display(HttpServletRequest req){
        ModelAndView mav = new ModelAndView("home");
        mav.addObject("loginAction", loginAction);
        return mav;
    }

    public void setLoginAction(LoginAction loginAction) {
        this.loginAction = loginAction;
    }

    public LoginAction getLoginAction() {
        return loginAction;
    }
    }

Templat kecepatan:

 LoginAction counter: ${loginAction.str}

Pegas config.xmlmengaktifkan pemindaian komponen:

    <context:annotation-config />
    <context:component-scan base-package="com.springheat" />
    <mvc:annotation-driven />

Saya mendapatkan hitungan yang bertambah setiap kali. Tidak tahu di mana saya salah!

Memperbarui

Seperti yang disarankan oleh @gkamal , saya membuat HomeController webApplicationContext-aware dan itu memecahkan masalah.

kode yang diperbarui:

@Controller
public class HomeController {

    @Autowired
    private WebApplicationContext context;

    @RequestMapping(value="/view", method=RequestMethod.GET)
    public ModelAndView display(HttpServletRequest req){
        ModelAndView mav = new ModelAndView("home");
        mav.addObject("loginAction", getLoginAction());
        return mav;
    }

    public LoginAction getLoginAction() {
        return (LoginAction) context.getBean("loginAction");
    }
}
tintin
sumber
12
Saya berharap saya dapat menggandakan dukungan Anda untuk menerapkan jawaban yang benar dalam kode Anda agar orang lain melihat perbedaan yang sebenarnya
Ali Nem

Jawaban:

156

Prototipe lingkup berarti bahwa setiap kali Anda meminta pegas (getBean atau injeksi ketergantungan) untuk sebuah instance, ia akan membuat instance baru dan memberikan referensi untuk itu.

Dalam contoh Anda, instance baru LoginAction dibuat dan disuntikkan ke HomeController Anda. Jika Anda memiliki pengontrol lain di mana Anda menyuntikkan LoginAction Anda akan mendapatkan contoh yang berbeda.

Jika Anda menginginkan contoh berbeda untuk setiap panggilan - maka Anda perlu memanggil getBean setiap kali - menyuntikkan kacang singleton tidak akan mencapai itu.

gkamal
sumber
7
Saya membuat controller ApplicationContextAware dan melakukan getBean dan saya mendapatkan kacang segar setiap saat. Terima kasih kawan !!!
tintin
Bagaimana cara kerjanya jika kacang memiliki requestlingkup alih-alih prototyperuang lingkup. Apakah Anda masih perlu mengambil kacang dengan context.getBean(..)?
dr jerry
2
Atau gunakan proxy scoped, yaitu @Scope (value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier
25

Sejak Spring 2.5 ada cara yang sangat mudah (dan elegan) untuk mencapainya.

Anda hanya dapat mengubah params proxyModedan valuedari @Scopepenjelasan.

Dengan trik ini Anda dapat menghindari menulis kode tambahan atau menyuntikkan ApplicationContext setiap kali Anda memerlukan prototipe di dalam kacang singleton.

Contoh:

@Service 
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)  
public class LoginAction {}

Dengan konfigurasi di atas LoginAction(di dalam HomeController) selalu ada prototipe meskipun controllernya adalah singleton .

db80
sumber
2
Jadi kita tidak memilikinya sekarang di musim semi 5?
Raghuveer
16

Hanya karena kacang yang disuntikkan ke pengontrol memiliki cakupan prototipe, bukan berarti pengontrolnya!

Dave Newton
sumber
11

@controller adalah objek singleton, dan jika menyuntikkan kacang prototipe ke kelas singleton akan menjadikan kacang prototipe juga sebagai singleton kecuali jika Anda menentukan menggunakan properti metode pencarian yang benar-benar membuat instance baru kacang prototipe untuk setiap panggilan yang Anda lakukan.

kartheek
sumber
5

Seperti yang disebutkan oleh nicholas.hauschild menyuntikkan konteks Musim Semi bukanlah ide yang baik. Dalam kasus Anda, @Scope ("permintaan") sudah cukup untuk memperbaikinya. Tetapi katakanlah Anda memerlukan beberapa contoh LoginActiondalam metode controller. Dalam hal ini, saya akan merekomendasikan untuk membuat kacang Pemasok ( solusi Spring 4 ):

    @Bean
    public Supplier<LoginAction> loginActionSupplier(LoginAction loginAction){
        return () -> loginAction;
    }

Kemudian masukkan ke dalam pengontrol:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  
Igor Rybak
sumber
1
Saya akan menyarankan menyuntikkan mata air ObjectFactoryyang melayani tujuan yang sama dengan pemasok, tetapi dapat didefinisikan sebagai normal @Beandengan maksud saya tidak perlu mengembalikan lambda.
xenoterracide
3

Menggunakan ApplicationContextAwaremengikat Anda ke Spring (yang mungkin atau mungkin tidak menjadi masalah). Saya akan merekomendasikan lewat LoginActionFactory, yang Anda dapat meminta contoh baru LoginActionsetiap kali Anda membutuhkannya.

nicholas.hauschild
sumber
1
Sudah ada anotasi khusus Musim Semi; sepertinya itu bukan masalah.
Dave Newton
1
@Dave, Poin bagus. Ada beberapa alternatif untuk beberapa hal DI (JSR 311), tetapi mungkin lebih sulit untuk melepaskan diri Anda dari segala yang Spring bergantung dalam contoh ini. Saya kira saya benar-benar hanya menganjurkan di factory-methodsini ...
nicholas.hauschild
1
+1 untuk menyuntikkan singleton LoginActionFactoryke Controller, tetapi factory-methodsepertinya itu tidak akan menyelesaikan masalah karena hanya menciptakan kacang musim semi lain melalui pabrik. Menyuntikkan kacang itu ke Kontroler singleton tidak akan mengatasi masalah.
Brad Cupit
Poin bagus Brad, saya akan menghapus saran itu dari jawaban saya.
nicholas.hauschild
3

gunakan cakupan permintaan @Scope("request")untuk mendapatkan kacang untuk setiap permintaan, atau @Scope("session")untuk mendapatkan kacang untuk setiap 'pengguna' sesi

Bassem Reda Zohdy
sumber
1

Kacang protoype yang disuntikkan ke dalam kacang singelton akan berperilaku seperti singelton sampai tiba-tiba dipanggil untuk membuat contoh baru dengan mendapatkan kacang.

context.getBean("Your Bean")
Ujjwal Choudhari
sumber
0

@Komponen

@Scope (value = "prototype")

TennisCoach kelas publik mengimplementasikan Coach {

// beberapa kode

}

Rakesh Singh Balhara
sumber
0

Anda dapat membuat kelas statis di dalam controller Anda seperti ini:

    @Controller
    public class HomeController {
        @Autowired
        private LoginServiceConfiguration loginServiceConfiguration;

        @RequestMapping(value = "/view", method = RequestMethod.GET)
        public ModelAndView display(HttpServletRequest req) {
            ModelAndView mav = new ModelAndView("home");
            mav.addObject("loginAction", loginServiceConfiguration.loginAction());
            return mav;
        }


        @Configuration
        public static class LoginServiceConfiguration {

            @Bean(name = "loginActionBean")
            @Scope("prototype")
            public LoginAction loginAction() {
                return new LoginAction();
            }
        }
}
Yakub
sumber
0

Secara default, kacang Spring adalah lajang. Masalahnya muncul ketika kami mencoba untuk mengirim biji dengan cakupan yang berbeda. Misalnya, kacang prototipe menjadi singleton. Ini dikenal sebagai masalah injeksi kacang scoped.

Cara lain untuk memecahkan masalah adalah metode injeksi dengan penjelasan @Lookup .

Berikut adalah artikel yang bagus tentang masalah ini dengan menyuntikkan kacang prototipe ke dalam contoh tunggal dengan beberapa solusi.

https://www.baeldung.com/spring-inject-prototype-bean-into-singleton

Saikat
sumber
-11

Pengontrol Anda juga memerlukan @Scope("prototype") ditentukan

seperti ini:

@Controller
@Scope("prototype")
public class HomeController { 
 .....
 .....
 .....

}
flyerfang
sumber
1
mengapa Anda pikir controller juga perlu prototipe?
Jigar Parekh