@Scope (“ ต้นแบบ”) ขอบเขตของ bean ไม่ได้สร้าง bean ใหม่


136

ฉันต้องการใช้ Bean ต้นแบบที่มีคำอธิบายประกอบในคอนโทรลเลอร์ของฉัน แต่ฤดูใบไม้ผลิกำลังสร้างถั่วซิงเกิลตันแทน นี่คือรหัสสำหรับ:

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

  private int counter;

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

รหัสคอนโทรลเลอร์:

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

เทมเพลต Velocity:

 LoginAction counter: ${loginAction.str}

Spring config.xmlเปิดใช้งานการสแกนส่วนประกอบ:

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

ฉันได้รับการนับที่เพิ่มขึ้นทุกครั้ง คิดไม่ออกว่าฉันผิดตรงไหน!

อัปเดต

ตามที่แนะนำโดย @gkamalฉันได้ทำการHomeController webApplicationContextตรวจสอบและแก้ไขปัญหาได้

อัปเดตรหัส:

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

14
ฉันหวังว่าฉันจะเพิ่มคะแนนให้คุณเป็นสองเท่าสำหรับการใช้คำตอบที่ถูกต้องในรหัสของคุณเพื่อให้คนอื่นเห็นความแตกต่างที่แท้จริง
Ali Nem

คำตอบ:


159

Scope ต้นแบบหมายความว่าทุกครั้งที่คุณถาม spring (getBean หรือ dependency injection) สำหรับอินสแตนซ์มันจะสร้างอินสแตนซ์ใหม่และอ้างอิงถึงสิ่งนั้น

ในตัวอย่างของคุณอินสแตนซ์ใหม่ของ LoginAction ถูกสร้างขึ้นและฉีดเข้าไปใน HomeController ของคุณ หากคุณมีตัวควบคุมอื่นที่คุณฉีด LoginAction คุณจะได้รับอินสแตนซ์อื่น

หากคุณต้องการอินสแตนซ์ที่แตกต่างกันสำหรับการโทรแต่ละครั้งคุณต้องโทรหา getBean ทุกครั้งการฉีดเข้าไปในเมล็ดซิงเกิลตันจะไม่บรรลุ


7
ฉันสร้าง ApplicationContextAware ตัวควบคุมและได้รับถั่วและฉันได้รับถั่วสดทุกครั้ง ขอบคุณเพื่อน!!!
tintin

วิธีนี้จะทำงานอย่างไรถ้าถั่วมีrequestขอบเขตแทนที่จะเป็นprototypeขอบเขต คุณยังต้องดึงถั่วด้วยcontext.getBean(..)หรือไม่?
dr jerry

2
หรือใช้พร็อกซีที่กำหนดขอบเขตเช่น @Scope (value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
svenmeier

26

ตั้งแต่ฤดูใบไม้ผลิ2.5มีวิธีที่ง่ายมาก (และสง่างาม) ในการบรรลุเป้าหมายนั้น

คุณก็สามารถเปลี่ยนพารามิเตอร์proxyModeและvalueของ@Scopeคำอธิบายประกอบ

ด้วยเคล็ดลับนี้คุณสามารถหลีกเลี่ยงการเขียนโค้ดพิเศษหรือฉีด ApplicationContext ทุกครั้งที่คุณต้องการต้นแบบภายในถั่วเดี่ยว

ตัวอย่าง:

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

ด้วยการกำหนดค่าด้านบนLoginAction(ด้านในHomeController) จะเป็นต้นแบบเสมอแม้ว่าคอนโทรลเลอร์จะเป็นซิงเกิลตันก็ตาม


2
ตอนนี้เราไม่มีมันในฤดูใบไม้ผลิ 5?
Raghuveer

16

เพียงเพราะถั่วที่ฉีดเข้าไปในคอนโทรลเลอร์เป็นขอบเขตต้นแบบไม่ได้หมายความว่าคอนโทรลเลอร์คือ!


11

@controller เป็นอ็อบเจกต์แบบซิงเกิลตันและถ้าฉีดถั่วต้นแบบไปยังคลาสซิงเกิลตันจะทำให้บีนต้นแบบเป็นซิงเกิลตันด้วยเว้นแต่คุณจะระบุโดยใช้คุณสมบัติการค้นหาวิธีซึ่งสร้างอินสแตนซ์ของ bean ต้นแบบใหม่สำหรับการโทรทุกครั้งที่คุณโทร


5

ตามที่nicholas.hauschildกล่าวถึงการฉีด Spring context ไม่ใช่ความคิดที่ดี ในกรณีของคุณ @Scope ("request") ก็เพียงพอที่จะแก้ไขได้ แต่สมมติว่าคุณต้องการอินสแตนซ์ของLoginActionวิธีการควบคุมหลายอินสแตนซ์ ในกรณีนี้ฉันขอแนะนำให้สร้าง bean ของ Supplier ( โซลูชันSpring 4 ):

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

จากนั้นฉีดลงในคอนโทรลเลอร์:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  

1
ฉันขอแนะนำให้ฉีดสปริงObjectFactoryที่มีจุดประสงค์เดียวกับซัพพลายเออร์ แต่สามารถกำหนดได้ว่าเป็นเรื่องปกติ@Beanโดยที่ฉันหมายความว่าไม่จำเป็นต้องส่งคืนแลมด้า
xenoterracide

3

การใช้ApplicationContextAwareกำลังผูกคุณเข้ากับ Spring (ซึ่งอาจเป็นปัญหาหรือไม่ก็ได้) ฉันขอแนะนำให้ส่งผ่าน a LoginActionFactoryซึ่งคุณสามารถขออินสแตนซ์ใหม่ได้LoginActionทุกครั้งที่คุณต้องการ


1
มีคำอธิบายประกอบเฉพาะฤดูใบไม้ผลิอยู่แล้ว ดูเหมือนจะไม่น่ากังวลเท่าไหร่
Dave Newton

1
@ เดฟจุดดี. มีทางเลือกอื่นสำหรับบางส่วนของ DI (JSR 311) แต่อาจเป็นการยากที่จะกำจัดทุกสิ่งที่ Spring ขึ้นอยู่กับในตัวอย่างนี้ ฉันคิดว่าฉันแค่สนับสนุนที่factory-methodนี่จริงๆ...
nicholas.hauschild

1
+1 สำหรับการฉีดซิงเกิ้ลLoginActionFactoryลงในคอนโทรลเลอร์ แต่factory-methodดูเหมือนว่าจะไม่สามารถแก้ปัญหาได้เนื่องจากเพิ่งสร้างถั่วสปริงขึ้นมาจากโรงงาน การฉีดถั่วนั้นลงในตัวควบคุมซิงเกิลตันจะไม่ช่วยแก้ปัญหา
Brad Cupit

ขอแสดงความนับถือแบรดฉันจะลบคำแนะนำนั้นออกจากคำตอบของฉัน
nicholas.hauschild

3

ใช้ขอบเขตคำขอ@Scope("request")เพื่อรับ bean สำหรับแต่ละคำขอหรือ@Scope("session")รับ bean สำหรับแต่ละเซสชัน 'ผู้ใช้'


1

Protoype bean ที่ฉีดเข้าไปในถั่ว singelton จะทำงานเหมือน singelton จนถูกเรียกให้สร้างอินสแตนซ์ใหม่โดย get bean

context.getBean("Your Bean")


0

คุณสามารถสร้างคลาสแบบคงที่ภายในคอนโทรลเลอร์ของคุณได้ดังนี้:

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

0

โดยค่าเริ่มต้นถั่วฤดูใบไม้ผลิเป็นสายเดี่ยว ปัญหาเกิดขึ้นเมื่อเราพยายามต่อสายถั่วที่มีขอบเขตต่างกัน ตัวอย่างเช่นถั่วต้นแบบในซิงเกิลตัน สิ่งนี้เรียกว่าปัญหาการฉีดสเกลถั่ว

อีกวิธีในการแก้ปัญหาคือวิธีการฉีดด้วยคำอธิบายประกอบ@Lookup

ต่อไปนี้เป็นบทความที่ดีเกี่ยวกับการฉีดถั่วต้นแบบลงในอินสแตนซ์ซิงเกิลตันที่มีวิธีแก้ปัญหาหลายอย่าง

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


-11

ตัวควบคุมของคุณยังต้องมีการ@Scope("prototype")กำหนด

แบบนี้:

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

}

1
ทำไมคุณถึงคิดว่าคอนโทรลเลอร์จำเป็นต้องเป็นต้นแบบด้วย?
Jigar Parekh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.