لماذا الربيع ApplicationContext.getBean تعتبر سيئة ؟

StackOverflow https://stackoverflow.com/questions/812415

  •  03-07-2019
  •  | 
  •  

سؤال

سألت عام الربيع السؤال: السيارات-يلقي الربيع الفول وكان عدة أشخاص الرد أن يدعو الربيع ApplicationContext.getBean() ينبغي تجنبها قدر الإمكان.لماذا هذا ؟

كيف يجب أن الوصول إلى الفول أنا تكوين الربيع إلى خلق ؟

أنا باستخدام الربيع في عدم تطبيق ويب و قد خططت على الوصول إلى مشترك ApplicationContext وجوه كما وصفها LiorH.

التعديل

أنا أقبل الإجابة أدناه ، ولكن هنا بديلة تأخذ مارتن فاولر الذي يناقش مزايا حقن التبعية مقابلوذلك باستخدام خدمة تحديد المواقع (والذي هو أساسا نفس الدعوة ملفوفة ApplicationContext.getBean()).

في جزء منه ، فاولر الدول،"مع خدمة تحديد المواقع فئة التطبيق يسأل عن ذلك [خدمة] صراحة رسالة إلى محدد.مع حقن لا يوجد طلب صريح, خدمة يظهر في فئة التطبيق - وبالتالي انعكاس السيطرة.انعكاس السيطرة هو سمة مشتركة بين الأطر ، ولكن هذا شيء يأتي في الأسعار.فإنه يميل إلى أن يكون من الصعب فهم يؤدي إلى مشاكل عند محاولة التصحيح.لذا على العموم أنا أفضل لتجنب ذلك [قلب من التحكم] إلا إذا كنت في حاجة إليها.هذا لا يعني أنه أمر سيء, فقط أعتقد أنه يحتاج إلى تبرير نفسها على مدى أكثر وضوحا البديلة."

هل كانت مفيدة؟

المحلول

ذكرت هذا في تعليق على مسألة أخرى ، ولكن الفكرة كلها من انعكاس السيطرة هو أن يكون أي من الفصول الدراسية يعرفون أو يهتمون كيفية الحصول على الأشياء التي تعتمد على.هذا يجعل من السهل تغيير ما هو نوع من تطبيق معين التبعية يمكنك استخدامها في أي وقت.كما أنه يجعل دروس سهلة لاختبار, كما يمكنك تقديم نماذج تطبيقات من التبعيات.وأخيرا ، فإنه يجعل الطبقات أبسط وأكثر تركيزا على المسؤولية الأساسية.

الدعوة ApplicationContext.getBean() ليس انعكاس السيطرة!في حين أنه لا يزال من السهل تغيير ما implemenation يتم تكوين معين فول اسم الفئة تعتمد الآن مباشرة على الربيع لتوفير أن التبعية لا يمكن الحصول عليها بأي طريقة أخرى.لا يمكنك جعل الخاص بك وهمية التنفيذ في اختبار الصف وتمرير ذلك بنفسك.هذا الأساس الهزائم الربيع الغرض كما حقن التبعية الحاوية.

في كل مكان كنت أريد أن أقول:

MyClass myClass = applicationContext.getBean("myClass");

يجب عليك بدلا من ذلك على سبيل المثال إعلان الأسلوب:

public void setMyClass(MyClass myClass) {
   this.myClass = myClass;
}

ثم في التكوين الخاص بك:

<bean id="myClass" class="MyClass">...</bean>

<bean id="myOtherClass" class="MyOtherClass">
   <property name="myClass" ref="myClass"/>
</bean>

الربيع ثم تلقائيا حقن myClass في myOtherClass.

تعلن كل شيء في هذا الطريق ، و أصل كل شيء مثل:

<bean id="myApplication" class="MyApplication">
   <property name="myCentralClass" ref="myCentralClass"/>
   <property name="myOtherCentralClass" ref="myOtherCentralClass"/>
</bean>

MyApplication هو الأكثر الطبقة الوسطى ، ويعتمد على الأقل بشكل غير مباشر على كل الخدمات الأخرى في البرنامج.عندما إلباس الحذاء ، main الطريقة, يمكنك الاتصال applicationContext.getBean("myApplication") ولكن يجب أن لا تحتاج إلى استدعاء getBean() في أي مكان آخر!

نصائح أخرى

أسباب تفضيل خدمة محدد على عكس السيطرة (IoC) هي:

  1. خدمة محدد هو أسهل بكثير لأشخاص آخرين التالية في التعليمات البرمجية الخاصة بك.اللجنة الاولمبية الدولية 'السحر' ولكن صيانة المبرمجين يجب أن تفهم الملتوية الربيع تكوينات و كل عدد لا يحصى من المواقع لمعرفة كيف السلكية الأشياء الخاصة بك.

  2. اللجنة الاولمبية الدولية الرهيبة من أجل تصحيح مشاكل في التكوين.في فئات معينة من التطبيقات التطبيق لن تبدأ عند تكوينها و قد لا تحصل على فرصة الخطوة من خلال ما يجري مع مصحح أخطاء.

  3. اللجنة الاولمبية الدولية في المقام الأول XML القائمة على (شروح تحسين الأمور ولكن لا يزال هناك الكثير من XML من هناك).هذا يعني أن المطورين لا يمكن العمل على البرنامج إلا أنهم يعرفون كل السحر فئة محددة من قبل الربيع.أنها ليست جيدة بما فيه الكفاية لمعرفة جافا بعد الآن.هذا يعيق أقل خبرة المبرمجين (ie.هو في الواقع سوء التصميم استخدام أكثر تعقيدا الحل عند أبسط حل مثل خدمة تحديد المواقع, سوف تفي بالشروط ذاتها).بالإضافة إلى دعم تشخيص XML المشاكل هو أضعف بكثير من دعم جافا المشاكل.

  4. حقن التبعية هو أكثر ملاءمة البرامج أكبر.معظم الوقت تعقيد إضافي لا يستحق ذلك.

  5. في كثير من الأحيان الربيع يستخدم في حال كنت "قد ترغب في تغيير التنفيذ في وقت لاحق".هناك طرق أخرى لتحقيق ذلك دون تعقيد الربيع اللجنة الأولمبية الدولية.

  6. من أجل تطبيقات ويب (جافا EE الحروب) الربيع السياق بشكل فعال لا بد في وقت الترجمة (إلا إذا كنت تريد مشغلي اليرقة حول السياق في انفجار الحرب).يمكنك جعل الربيع استخدام خاصية الملفات ولكن مع سيرفلتس الملكية الملفات سوف تحتاج إلى أن تكون في موقع محدد مسبقا, مما يعني أنك لا يمكن نشر عدة سيرفلتس نفس الوقت على نفس مربع.يمكنك استخدام الربيع مع JNDI إلى تغيير خصائص في بريمج وقت بدء التشغيل, ولكن إذا كنت تستخدم JNDI المسؤول-المعلمات للتعديل الحاجة إلى الربيع نفسه يقلل (منذ JNDI هو فعليا خدمة محدد).

  7. مع الربيع يمكنك أن تفقد برنامج التحكم إذا كان الربيع هو إيفاد إلى الأساليب الخاصة بك.هذا هو مريحة و يعمل على أنواع عديدة من التطبيقات, ولكن ليس كل شيء.قد تحتاج إلى برنامج التحكم في التدفق عندما تحتاج إلى إنشاء المهام (المواضيع الخ) أثناء التهيئة أو تحتاج للتعديل الموارد أن الربيع لم أكن أعرف عنه عندما كان المحتوى لا بد أن الحرب الخاصة بك.

الربيع هو جيد جدا بالنسبة إدارة المعاملات و لديه بعض المزايا.أنها مجرد أن اللجنة الأولمبية الدولية يمكن أن يكون الإفراط في الهندسة في العديد من الحالات و نقدم تعقيد لا مبرر له على والإنجاب.لا تستخدم تلقائيا الأولمبية الدولية دون التفكير في طرق لا تستخدم لأول مرة.

صحيح أن ذلك الصف في application-context.xml يتجنب الحاجة إلى استخدام getBean.ومع ذلك ، حتى التي هي في الواقع لا لزوم لها.إذا كنت تقوم بكتابة تطبيق قائم بذاته و لا ترغب في تضمين برنامج التشغيل الخاص بك في الدرجة application-context.xml يمكنك استخدام التعليمات البرمجية التالية إلى الربيع autowire السائق تبعيات:

public class AutowireThisDriver {

    private MySpringBean mySpringBean;    

    public static void main(String[] args) {
       AutowireThisDriver atd = new AutowireThisDriver(); //get instance

       ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                  "/WEB-INF/applicationContext.xml"); //get Spring context 

       //the magic: auto-wire the instance with all its dependencies:
       ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd,
                  AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);        

       // code that uses mySpringBean ...
       mySpringBean.doStuff() // no need to instantiate - thanks to Spring
    }

    public void setMySpringBean(MySpringBean bean) {
       this.mySpringBean = bean;    
    }
}

كنت بحاجة إلى القيام بذلك عدة مرات عندما يكون نوعا من مستقل الفئة التي تحتاج إلى استخدام بعض جوانب التطبيق الخاص بي (على سبيل المثال اختبار) ولكن لا أريد أن تدرج في تطبيق السياق لأنه ليس في الواقع جزء من التطبيق.نلاحظ أيضا أن هذا يتجنب الحاجة إلى البحث عن الفول باستخدام اسم السلسلة التي لطالما اعتقدت كانت قبيحة.

واحدة من أروع الفوائد من استخدام شيء مثل الربيع هو أنك لا يجب أن سلك الأشياء الخاصة بك معا.زيوس رئيس انشقت والفئات الخاصة بك تظهر تشكيلها بالكامل مع جميع التبعيات إنشاء السلكية ، حسب الحاجة.انها ساحرة و رائعة.

كلما قلت ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed");, أقل السحر الذي تحصل عليه.رمز أقل هو الأفضل دوما.إذا كان لديك فئة حقا في حاجة إلى ClassINeed الفول, لماذا لم الأسلاك ؟

وقال من الواضح أن شيئا ما يحتاج إلى إنشاء الكائن الأول.لا يوجد شيء خاطئ مع الرئيسي الخاص بك طريقة الحصول على حبة أو اثنين من طريق getBean () ، ولكن يجب تجنب ذلك لأنه كلما كنت تستخدم ذلك ، كنت حقا لا تستخدم كل من سحر الربيع.

الدافع إلى كتابة التعليمات البرمجية التي لا تعتمد صراحة على الربيع.بهذه الطريقة, إذا اخترت تبديل حاويات, لم يكن لديك إلى إعادة كتابة أي رمز.

أعتقد الحاوية كشيء غير مرئي إلى التعليمات البرمجية الخاصة بك ، سحرية توفير احتياجاتها ، دون أن يطلب منه.

حقن التبعية هو الطباق إلى "خدمات تحديد المواقع" نمط.إذا كنت تنوي البحث عن تبعيات بالاسم ، قد أيضا الحصول على التخلص من دي الحاويات استخدام شيء مثل JNDI.

باستخدام @Autowired أو ApplicationContext.getBean() هو في الحقيقة نفس الشيء.في كلا الاتجاهين تحصل على الفول الذي تم تكوينه في السياق الخاص بك و في كلا الاتجاهين التعليمات البرمجية الخاصة بك يعتمد على الربيع.الشيء الوحيد الذي يجب عليك تجنب إنشاء الخاصة بك ApplicationContext.القيام بذلك مرة واحدة فقط!وبعبارة أخرى ، مثل

ApplicationContext context = new ClassPathXmlApplicationContext("AppContext.xml");

يجب أن تستخدم إلا مرة واحدة في التطبيق الخاص بك.

الفكرة هي أنك تعتمد على حقن التبعية (انعكاس السيطرة, أو IoC).كل هذه المكونات هي تكوين المكونات التي تحتاجها.هذه التبعيات هي حقن (عن طريق منشئ أو واضعي) - أنت لا تحصل على نفسك.

ApplicationContext.getBean() يتطلب منك اسم الفول صراحة ضمن مكون الخاص بك.بدلا من ذلك باستخدام اللجنة الأوقيانوغرافية الحكومية الدولية ، التكوين الخاص بك يمكن تحديد أي عنصر سيتم استخدامها.

هذا يسمح لك أن أسلاك التطبيق الخاص بك مع مختلف مكونات تطبيقات بسهولة ، أو تكوين الأجسام للاختبار بطريقة مباشرة من خلال توفير سخر المتغيرات (مثلأ سخر DAO حتى لا ضرب قاعدة البيانات أثناء الاختبار)

والبعض الآخر أشار إلى مشكلة عامة (و هي إجابات صحيحة) ولكن أنا مجرد عرض تعليق واحد إضافي:ليس هذا يجب أن لا تفعل ذلك, ولكن بدلا من أن تفعل أقل قدر ممكن.

عادة هذا يعني أن يتم ذلك مرة واحدة:خلال إلباس الحذاء.ثم إنه فقط من أجل الوصول إلى "جذور" فول من خلالها المخدرة الأخرى يمكن حلها.وهذا يمكن أن تكون قابلة لإعادة الاستخدام رمز, مثل قاعدة بريمج (إن تطوير تطبيقات الويب).

واحدة من الربيع المباني هو تجنب اقتران.تحديد واستخدام واجهات دي اوب و تجنب استخدام ApplicationContext.getBean() :-)

أحد الأسباب هو الاختبار.نقول لديك هذه الفئة:

interface HttpLoader {
    String load(String url);
}
interface StringOutput {
    void print(String txt);
}
@Component
class MyBean {
    @Autowired
    MyBean(HttpLoader loader, StringOutput out) {
        out.print(loader.load("http://stackoverflow.com"));
    }
}

كيف يمكنك اختبار هذا الفول ؟ E. g.مثل هذا:

class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();

        // execution
        new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

سهلة, صحيح ؟

في حين كنت لا تزال تعتمد على الربيع (بسبب الشروح) يمكنك إزالة يمكنك الاعتماد على الربيع دون تغيير أي رمز (فقط الشرح التعاريف) و الاختبار المطور لا تحتاج إلى معرفة أي شيء عن كيفية الربيع يعمل (ربما كان يجب على كل حال ، لكنه يسمح مراجعة و اختبار التعليمات البرمجية بشكل منفصل عن ما يفعله الربيع).

فإنه لا يزال من الممكن أن تفعل الشيء نفسه عند استخدام ApplicationContext.ومع ذلك فأنت بحاجة إلى صورية ApplicationContext وهو ضخمة واجهة.تحتاج إما وهمية أو تنفيذ أو يمكنك استخدام ساخرا إطار مثل Mockito:

@Component
class MyBean {
    @Autowired
    MyBean(ApplicationContext context) {
        HttpLoader loader = context.getBean(HttpLoader.class);
        StringOutput out = context.getBean(StringOutput.class);

        out.print(loader.load("http://stackoverflow.com"));
    }
}
class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();
        ApplicationContext context = Mockito.mock(ApplicationContext.class);
        Mockito.when(context.getBean(HttpLoader.class))
            .thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get);
        Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);

        // execution
        new MyBean(context);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

هذا هو تماما احتمال, ولكن أعتقد أن معظم الناس يتفقون على أن الخيار الأول هو أكثر أناقة و يجعل الاختبار أكثر بساطة.

الخيار الوحيد الذي هو في الحقيقة المشكلة هي هذه:

@Component
class MyBean {
    @Autowired
    MyBean(StringOutput out) {
        out.print(new HttpLoader().load("http://stackoverflow.com"));
    }
}

اختبار وهذا يتطلب جهودا ضخمة أو فول الخاص بك هو الذهاب الى محاولة الاتصال ستاكوفيرفلوو على كل اختبار.في أقرب وقت كما كنت قد فشل الشبكة (أو مدراء في ستاكوفيرفلوو كتلة بسبب الإفراط في الوصول إلى معدل) سوف يكون عشوائيا فشله في الاختبارات.

وذلك الختام لن أقول أن استخدام ApplicationContext مباشرة تلقائيا خاطئة و يجب تجنب في جميع التكاليف.ومع ذلك إذا كان هناك خيارات أفضل (وهناك في معظم الحالات) ، ثم استخدم خيارات أفضل.

لقد وجدت اثنين من الحالات التي getBean() المطلوب:

ذكر آخرون باستخدام getBean() في main() لجلب "الرئيسية" الفول عن برنامج مستقل.

استخدام آخر لقد جعلت من getBean() في الحالات التي تفاعلية تكوين المستخدم يحدد فول ماكياج على حالة معينة.بحيث على سبيل المثال جزء من التمهيد نظام الحلقات من خلال بيانات الجدول باستخدام getBean() مع نطاق='النموذج' فول التعريف ثم تعيين خصائص إضافية.ويفترض أن هناك واجهة المستخدم الذي يضبط جدول قاعدة البيانات التي من شأنها أن تكون أكثر ودا من محاولة (إعادة)كتابة التطبيق السياق XML.

هناك آخر الوقت عند استخدام getBean المنطقي.إذا كنت إعادة تشكيل نظام موجود بالفعل ، حيث تبعيات لا صراحة يسمى في الربيع سياق الملفات.يمكنك أن تبدأ هذه العملية عن طريق وضع يدعو إلى getBean ، بحيث لم يكن لديك إلى سلك كل شيء في مرة واحدة.بهذه الطريقة يمكنك بناء ببطء الربيع التكوين ووضع كل قطعة في مكانها مع مرور الوقت و الحصول على بت اصطف بشكل صحيح.المكالمات إلى getBean في نهاية المطاف سوف يتم استبداله لكن كما فهمت هيكل رمز ، أو عدم وجود ، يمكنك البدء في عملية الأسلاك المزيد والمزيد من الفول باستخدام أقل وأقل يدعو إلى getBean.

ومع ذلك ، لا تزال هناك حالات حيث كنت في حاجة إلى خدمة محدد نمط.على سبيل المثال لدي تحكم فول, وحدة التحكم هذه قد يكون لها بعض الافتراضي خدمة الفاصوليا ، والتي يمكن الاعتماد حقن عن طريق التكوين.في حين يمكن أن يكون هناك أيضا العديد إضافية أو خدمات جديدة هذا يمكن أن تحكم الاحتجاج الآن أو في وقت لاحق ، ثم في حاجة إلى خدمة محدد لاسترداد خدمة الفاصوليا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top