Почему SpringCon ApplicationContext.getBean считается плохим? - PullRequest
249 голосов
/ 01 мая 2009

Я задал общий вопрос Spring: Auto-cast Spring Beans , и несколько человек ответили, что вызова Spring'а ApplicationContext.getBean() следует избегать, насколько это возможно. Это почему?

Как еще мне получить доступ к bean-компонентам, которые я настроил для создания Spring?

Я использую Spring в не-веб-приложении и планировал доступ к общему ApplicationContext объекту , как описано LiorH .

Поправка

Я принимаю ответ ниже, но вот альтернативный вариант Мартина Фаулера, который обсуждает преимущества внедрения зависимостей по сравнению с использованием локатора служб (что по сути то же самое, что вызов свернутого ApplicationContext.getBean()) .

В частности, Фаулер заявляет: « С помощью локатора службы класс приложения запрашивает его [службу] явным образом посредством сообщения локатору. При внедрении явный запрос отсутствует, служба появляется в классе приложения - отсюда и инверсия контроля. Инверсия контроля - это общая черта фреймворков, но это то, что имеет свою цену. Как правило, это трудно понять и приводит к проблемам при попытке отладки. В общем, я предпочитаю избегать этого [инверсии контроля], если мне это не нужно. Это не значит, что это плохо, просто я думаю, что нужно оправдать себя более простой альтернативой."

Ответы [ 13 ]

194 голосов
/ 01 мая 2009

Я упоминал об этом в комментарии к другому вопросу, но вся идея Inversion of Control заключается в том, чтобы ни один из ваших классов не знал или не заботился о том, как они получают объекты, от которых они зависят * . Это позволяет легко изменить тип реализации данной зависимости, которую вы используете в любое время. Это также облегчает тестирование классов, поскольку вы можете предоставить имитационные реализации зависимостей. Наконец, это делает классы проще и более ориентированы на их основную ответственность.

Вызов ApplicationContext.getBean() - это не инверсия контроля! В то время как все еще легко изменить то, что реализация настроена для данного имени компонента, класс теперь полагается непосредственно на Spring, чтобы обеспечить эту зависимость и не может получить ее любым другим способом. Вы не можете просто сделать свою собственную ложную реализацию в тестовом классе и передать ее себе. Это в основном противоречит цели Spring как контейнера для инъекций зависимостей.

Везде, где вы хотите сказать:

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>

Затем Spring автоматически добавит myClass в myOtherClass.

Объявите все таким образом, и в корне всего этого есть что-то вроде:

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

MyApplication является самым центральным классом и зависит, по крайней мере, косвенно, от любой другой службы в вашей программе. При начальной загрузке в вашем методе main вы можете вызвать applicationContext.getBean("myApplication"), но вам не нужно вызывать getBean() где-либо еще!

63 голосов
/ 13 ноября 2009

Причины, по которым сервисный локатор предпочтительнее инверсии управления (IoC):

  1. Сервисный локатор намного, намного проще для других людей следовать в вашем коде. IoC - «волшебство», но программисты по техническому обслуживанию должны понимать ваши замысловатые конфигурации Spring и все множество мест, чтобы выяснить, как вы подключили свои объекты.

  2. IoC ужасен для отладки проблем конфигурации. В некоторых классах приложений приложение не запускается при неправильной настройке, и у вас может не быть возможности прокрутить то, что происходит с отладчиком.

  3. IoC, в основном, основан на XML (аннотации улучшают ситуацию, но XML по-прежнему существует). Это означает, что разработчики не могут работать с вашей программой, если они не знают всех магических тегов, определенных Spring. Это недостаточно хорошо, чтобы знать Java больше. Это мешает программистам с меньшим опытом (то есть, на самом деле плохой дизайн - использовать более сложное решение, когда более простое решение, такое как Service Locator, будет отвечать тем же требованиям). Кроме того, поддержка диагностики проблем XML намного слабее, чем поддержка проблем Java.

  4. Внедрение зависимостей больше подходит для более крупных программ. Большую часть времени дополнительная сложность не стоит.

  5. Часто Spring используется в случае, если вы «захотите изменить реализацию позже». Есть и другие способы достижения этого без сложности Spring IoC.

  6. Для веб-приложений (Java EE WARs) контекст Spring эффективно связывается во время компиляции (если вы не хотите, чтобы операторы разбирались с контекстом в разнесенной войне). Вы можете заставить Spring использовать файлы свойств, но с сервлетами файлы свойств должны будут находиться в заранее определенном месте, что означает, что вы не можете развернуть несколько сервлетов одновременно в одном окне. Вы можете использовать Spring с JNDI для изменения свойств во время запуска сервлета, но если вы используете JNDI для изменяемых администратором параметров, необходимость в самом Spring уменьшается (поскольку JNDI фактически является локатором служб).

  7. С Spring вы можете потерять управление программой, если Spring отправляет ваши методы. Это удобно и работает для многих типов приложений, но не для всех. Вам может понадобиться контролировать поток программ, когда вам нужно создавать задачи (потоки и т. Д.) Во время инициализации или вам нужны изменяемые ресурсы, о которых Spring не знал, когда контент был привязан к вашей WAR.

Spring очень хорош для управления транзакциями и имеет некоторые преимущества. Просто во многих ситуациях IoC может быть чрезмерно сложным и создавать необоснованную сложность для сопровождающих. Не используйте автоматически IoC, не подумав о способах его не использовать.

25 голосов
/ 07 сентября 2009

Это правда, что включение класса в application-context.xml избавляет от необходимости использовать getBean. Однако даже это на самом деле не нужно. Если вы пишете отдельное приложение и не хотите включать свой класс драйвера в application-context.xml, вы можете использовать следующий код, чтобы Spring автоматически связывал зависимости драйвера:

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

Мне нужно было сделать это пару раз, когда у меня есть какой-то отдельный класс, который должен использовать какой-то аспект моего приложения (например, для тестирования), но я не хочу включать его в контекст приложения, потому что это на самом деле не является частью приложения. Также обратите внимание, что это избавляет от необходимости искать бин, используя строковое имя, которое я всегда считал уродливым.

21 голосов
/ 01 мая 2009

Одним из самых классных преимуществ использования чего-то вроде Spring является то, что вам не нужно соединять свои объекты вместе. Голова Зевса распадается, и ваши классы появляются полностью сформированными со всеми зависимостями, созданными и подключенными по мере необходимости. Это волшебно и фантастично.

Чем больше вы говорите ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed");, тем меньше магии вы получаете. Меньше кода почти всегда лучше. Если вашему классу действительно нужен компонент ClassINeed, почему вы просто не подключили его?

Тем не менее, очевидно, что что-то должно создать первый объект. В вашем основном методе нет ничего плохого в том, чтобы получить один или два компонента с помощью getBean (), но вам следует избегать этого, потому что всякий раз, когда вы его используете, вы на самом деле не используете всю магию Spring.

16 голосов
/ 01 мая 2009

Мотивация - написать код, который явно не зависит от Spring. Таким образом, если вы решите переключать контейнеры, вам не нужно переписывать код.

Думайте о контейнере как о чем-то невидимом для вашего кода, волшебно обеспечивающем его потребности, без лишних вопросов.

Внедрение зависимостей является контрапунктом к шаблону «поиск сервисов». Если вы собираетесь искать зависимости по имени, вы можете также избавиться от контейнера DI и использовать что-то вроде JNDI.

10 голосов
/ 29 марта 2012

Использование @Autowired или ApplicationContext.getBean() - это одно и то же. В обоих случаях вы получаете компонент, настроенный в вашем контексте, и в обоих случаях ваш код зависит от Spring Единственное, чего вам следует избегать - это создать экземпляр ApplicationContext. Делай это только один раз! Другими словами, строка типа

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

следует использовать только один раз в вашем приложении.

4 голосов
/ 25 января 2017

Одной из причин является тестируемость. Скажем, у вас есть этот класс:

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

Как вы можете проверить этот боб? Например. как это:

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

Легко, правда?

Хотя вы по-прежнему зависите от Spring (из-за аннотаций), вы можете удалить свою зависимость от Spring без изменения какого-либо кода (только определения аннотаций), и разработчику теста не нужно ничего знать о том, как работает Spring (возможно, он должен в любом случае, но это позволяет просматривать и тестировать код отдельно от того, что делает весна).

Все еще возможно сделать то же самое при использовании 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"));
    }
}

Тестирование этого требует огромных усилий, иначе ваш bean-компонент будет пытаться подключаться к stackoverflow в каждом тесте. И как только у вас произойдет сбой в сети (или администраторы в stackoverflow заблокируют вас из-за чрезмерной скорости доступа), у вас будут случайно провальные тесты.

Итак, в заключение я бы не сказал, что прямое использование ApplicationContext автоматически неправильно и его следует избегать любой ценой. Однако, если есть лучшие варианты (и они есть в большинстве случаев), используйте лучшие варианты.

4 голосов
/ 02 мая 2009

Одно из помещений Spring - избегать сцепления . Определите и используйте интерфейсы, DI, AOP и избегайте использования ApplicationContext.getBean (): -)

4 голосов
/ 01 мая 2009

Другие указали на общую проблему (и являются действительными ответами), но я просто предложу еще один комментарий: это не значит, что вы НИКОГДА не должны делать этого, а скорее делать это как можно меньше.

Обычно это означает, что это делается ровно один раз: во время начальной загрузки. И тогда это просто доступ к «корневому» компоненту, через который можно разрешить другие зависимости. Это может быть повторно используемый код, например базовый сервлет (при разработке веб-приложений).

4 голосов
/ 01 мая 2009

Идея состоит в том, что вы полагаетесь на внедрение зависимостей ( инверсия управления или IoC). То есть ваши компоненты настроены так, как им нужно. Эти зависимости вводятся (через конструктор или установщики) - тогда вы сами не получите.

ApplicationContext.getBean() требует от вас явно указывать имя компонента в вашем компоненте. Вместо этого, используя IoC, ваша конфигурация может определить, какой компонент будет использоваться.

Это позволяет легко перемонтировать ваше приложение с различными реализациями компонентов или настроить объекты для тестирования простым способом, предоставляя имитированные варианты (например, поддельный DAO, чтобы вы не попали в базу данных во время тестирования)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...