Неделя Spring на Хабре, судя по всему, открыта. Хочется сказать спасибо переводчику и комментаторам статьи "Почему я ненавижу Spring", которая не смотря на сильный негативный посыл в названии вызвала ряд интересных дискуссий, а так же тем, кто отреагировал на мою прошлую статью Как писать на Spring в 2017. Во многом благодаря комментариям к прошлой статье и появилась эта.


В этот раз мы погрузимся в пучины Spring фреймворка, разоблачим его магию, посмотрим как базовое веб приложение выглядит изнутри, и разберемся, какую-же задачу и как решает Spring Boot.


В комментариях к предыдущей статье несколько человек очень справедливо указали, что пример Hello World-а на Spring все же не очень показателен. Spring, особенно с использованием Spring Boot, дает ощущение простоты и всемогущества, но непонимание основ и внутренностей фреймворка ведет к большой опасности получить стектрейсом по логу. Что ж, чтобы немного развеять ощущение полной магии происходящего, сегодня мы возьмем приложение из предыдущей статьи и разберем, как и что происходит внутри фреймворка и от каких проблем нас отгораживает Boot. Целевая аудитория все же начинающие разработчики, но с некоторым опытом и базовыми знаниями Java и Spring. Хотя, возможно, и опытным пользователям Spring будет интересно освежить знания того, что происходит под капотом.


Ключевые понятия


Бины


Начнем срывать покровы с самых базовых понятий Spring. Бин (bean) — это не что иное, как самый обычный объект. Разница лишь в том, что бинами принято называть те объекты, которые управляются Spring-ом и живут внутри его DI-контейнера. Бином является почти все в Spring — сервисы, контроллеры, репозитории, по сути все приложение состоит из набора бинов. Их можно регистрировать, получать в качестве зависимостей, проксировать, мокать и т.п.


DI контейнер


Ключевой и фундаментальный механизм Spring. Внешне очень простой, но внутри он предоставляет очень много механизмов для тонкой настройки зависимостей. По сути, любое приложение Спринг — это набор бинов, связанных вместе через DI контейнер.


Очень часто при обсуждении Spring звучит аргумент, что его можно легко заменить на любой леговесный DI контейнер (Guice, например) и получить то же самое, но легче и проще. И здесь очень важно понять — ценность Spring DI не в самом факте его наличия, а в его фундаментальности. Все библиотеки в экосистеме Spring, по сути, просто регистрируют свои бины в этом контейнере (включая и сам Spring) — и через иньекцию зависимостей разработчики приложения смогут получить нужные компоненты. Простой пример: при использовании Spring Security OAuth если сконфигурить параметры OAuth в application.properties, то Spring Security предоставит бин OAuth2RestTemplate который мы можем просто заинжектить в своем коде. И этот бин при обращении к внешнему API будет знать, куда и как пойти, чтобы получить OAuth токен, как его обновлять, в какое место нашего запроса его добавлять и т.п. Так вот ценность DI тут в том, что это просто механизм общения между нашим кодом и Spring Security. И простой заменой реализации DI на Guice не добиться, чтобы Spring Security тоже начал его использовать. А если в этом новом DI не будет интеграции со всеми библиотеками Spring-а, то и ценность его сильно падает.


Еще один очень важный момент, который многие упускают при обсуждении DI контейнера, это то, что использование инъекции зависимостей не подразумевает создания интерфейсов для каждого компонента. Это очень простая мысль, но я много раз видел, что из-за своей простоты она не всегда очевидна. Более того, создание интерфейса, если у него лишь одна реализация — считается плохой практикой. Т.е. классы вполне могут сами по себе быть объектами DI. Более того, отсутствие интерфейса даже не мешает их мокать в тестах, т.к. Mockito, например, вполне умеет мокать классы.


Контекст


Представлен интерфейсом ApplicationContext. По сути, представляет собой само приложение Spring. Так же контекст предоставляет возможности реагировать на различные события, которые происходят внутри приложения, управлять жизненным циклом бинов (создавать как синглтон или на каждый запрос, например).


Конфигурация


Итак, если приложение — это набор бинов, чтобы оно заработало нам нужно этот набор описать.


Конфигурация — это просто описание доступных бинов. Spring дает несколько вариантов, как можно описать набор бинов, которые сформируют приложение. Исторический вариант — это через набор xml файлов. В наши дни ему на смену пришли Java аннотации. Spring Boot построен на аннтациях чуть более, чем полностью и большинство современных библиотек в принципе тоже можно сконфигурить через аннотации. В третьем своем поколении, конфигурация бинов пришла к подходу функциональной регистрации (functional bean registration), которая станет одной из важных новых фич готовящегося к выходу Spring 5.


Типичный класс конфигурации может, выглядеть, например так:


@Configuration
class PaymentsServiceConfiguration {

    @Bean
    public PaymentProvider paymentProvider() {
        return new PayPalPaymentProvider();
    }

    @Bean
    public PaymentService paymentService(PaymentProvider paymentProvider) {
        return new PaymentService(paymentProvider);
    }

}

Эта конфигурация определяет два бина, причем второй зависит от первого. И здесь в игру вступит Spring – когда мы просим предоставить инстанс PaymentProvider — Spring найдет его в контексте и предоставит нам.


Конфигурацию не обязательно описывать в одном огромном файле, можно разбить на несколько и объединять их с помощью @Import аннотаций.


Сканирование компонентов


Достаточно важный компонент Spring Framework, еще один подход к упрощению конфигурации приложения. Идея очень простая — если мы знаем, что наш класс MyCoolComponent должен регистрировать бин с именем myCoolComponent, зачем каждый раз писать @Bean MyCoolComponent myCoolComponent(dependencies...) { return new MyCoolComponent(dependencies...); }? Почему просто не дать Spring–у автоматом зарегистрировать и создать бин на основании нужного класса? Эту задачу и решает сканирование компонентов. Т.е. если мы объявим наш класс как


@Component
class MyCoolComponent {
    MyCoolComponent(dependencies...) {
    }
}

и разрешим сканирование компонентов — то Spring сам создаст и зарегистрирует бин с именем myCoolComponent, использовав конструктор класса и заинжектив туда все зависимости.


Со сканированием компонентов надо быть осторожным, т.к. по сути оно неявно меняет контекст приложения. Например, если у нас есть интерфейс и две реализации — и на каждом указан @Component, то при попытке заинжектить зависимость на интерфейс Spring бросит исключение, что есть два бина, которые удовлетворяют запросу.

Резюме


Итак, вещи которые нужно запомнить: приложение Spring, описанное интерфейсом ApplicationContext, представляет собой набор объектов (бинов), управляемых DI контейнером. Конфигурация набора бинов осуществляется с помощью классов конфигурации (аннотация @Configuration), которые могут быть комбинированы с помощью импортов (аннотация @Import).


Spring Boot


Теперь переходим к следующей части. Допустим, нам надо сконфигурить подключение к MySQL базе данных. Если мы хотим использовать Spring Data JPA с Hibernate в качестве провайдера, нам потребуется сконфигурировать несколько бинов — EntityManagerFactory (основной класс JPA), DataSource для подключения непосредственно к базе через JDBC драйвер и т.п. Но с другой стороны, если мы это делаем каждый раз и, по сути, делаем одно и то же — почему бы это не автоматизировать? Скажем, если мы указали строку подключения к базе и добавили зависимость на MySQL драйвер — почему бы чему-то автоматически не создать все нужные бины для работы с MySQL? Именно это и делает Spring Boot. По сути, Spring Boot это просто набор классов конфигурации, которые создают нужные бины в контексте. Точно так же их можно создать руками, просто Boot это автоматизирует.


Автоконфигурация


Важное понятие Spring Boot это автоконфигурация. По сути, это просто набор конфигурационных классов, которые создают и регистрируют определенные бины в приложении. По большому счету, даже сам Embedded Servlet Container — это просто еще один бин, который можно сконфигурировать! Пара важных моментов, которые важно знать об автоконфигурации:


  • Включается аннотацией @EnableAutoConfiguration
  • Работает в последнюю очередь, после регистрации пользовательских бинов
  • Принимает решения о конфигурации на основании доступных в classpath классов, свойств в application.properties и т.п.
  • Можно включать и выключать разные аспекты автоконфигурации, и применять ее частично (например, только MySQL + JPA, но не веб)
  • Всегда отдает приоритет пользовательским бинам. Если ваш код уже зарегистрировал бин DataSource — автоконфигурация не будет его перекрывать

Условия и порядок регистрации бинов


Логика при регистрации бинов управляется набором @ConditionalOn* аннотаций. Можно указать, чтобы бин создавался при наличии класса в classpath (@ConditionalOnClass), наличии существующего бина (@ConditionalOnBean), отсуствии бина (@ConditionalOnMissingBean) и т.п.


Spring Boot активно использует эти аннотации чтобы оставаться как можно более незаметным и не перекрывать пользовательские конфигурации.


Погружение в Hello World


Теперь, имея в запасе базовые теоретические знания, разберем что же происходит при запуске приложения.


Итак, наше приложение включает такой код:


@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Давайте разберем что здесь происходит по шагам.


Класс DemoApplication


Этот класс помечен аннотацией @SpringBootApplication, что является мета-аннотацией, т.е. по сути, является алиасом для нескольких аннотаций:


  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan.

Т.е. наличие @SpringBootApplication включает сканирование компонентов, автоконфигурацию и показывает разным компонентам Spring (например, интеграционным тестам), что это Spring Boot приложение


SpringApplication.run()


Это просто хелпер, который делает пару вещей — используя список предоставленных конфигураций (а класс DemoApplication сам по себе конфигурация, см. выше) создает ApplicationContext, конфигурирует его, выводит баннер в консоли и засекает время старта приложения и т.п. Его можно заменить на ручное создание контекста: new AnnotationConfigApplicationContext(DemoApplication.class). Как можно понять из названия, это контекст приложения, который конфигурируется с помощью аннотаций. Однако, этот контекст не знает ничего об embedded servlet container-ах, и совершенно точно не умеет себя запускать. Его наследник, уже из Spring BootAnnotationConfigEmbeddedWebApplicationContext делать это вполне умеет, и если мы в методе main напишем просто


@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext applicationContext =
                new AnnotationConfigEmbeddedWebApplicationContext(DemoApplication.class);
    }
}

То получим точно такое же работающее приложение, т.к. класс AnnotationConfigEmbeddedWebApplicationContext найдет в контексте бин типа EmbeddedServletContainerFactory и через него создаст и запустит встроенный контейнер. Обратите внимание, что все это работает в рамках общего DI контейнера, то есть этот класс можно реализовать самим.


@EnableAutoConfiguration


Эта аннотация включает автоконфигурацию. И здесь, пожалуй, ключевой момент в развенчании магии Spring. Вот как объявлена эта аннотация:


...
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

Т.е. это самый обычный импорт конфигурации, про который мы говорили выше. Класс же EnableAutoConfigurationImportSelector (и его преемник в Spring Boot 1.5+ — AutoConfigurationImportSelector) это просто конфигурация, которая добавит несколько бинов в контекст. Однако, у этого класса есть одна тонкость — он не объявляет бины сам, а использует так называемые фабрики.


Класс EnableAutoConfigurationImportSelector смотрит в файл spring.factories и загружает оттуда список значений, которые являются именами классов (авто)конфигураций, которые Spring Boot импортирует.


Кусочек файла spring.factories (он находится в папке META-INF внутри spring-boot-autoconfigure.<version>.jar), который нам сейчас нужен это:


org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,... (100 lines)
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

Т.е. аннотация @EnableAutoConfiguration просто импортирует все перечисленные конфигурации, чтобы предоставить нужные бины в контекст приложения.


По сути, ее можно заменить на ручной импорт нужных конфигураций:


@Import({
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration.class,
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.class,
    ...})
public class DemoApplication {
    ...
}

Однако, особенность в том, что Spring Boot пытается применить все конфигурации (а их около сотни). Я думаю, у внимательного читателя уже появилась пара вопросов, которые стоит прояснить.


  • "Но это же медленно!". И да, и нет — под рукой нет точных цифр, но сам по себе процесс автоконфигурации очень быстрый (порядка сотни миллисекунд на абстрактной машине в вакууме)


  • "Но это же излишне, зачем мне конфигурить Rabbit (RabbitAutoConfiguration) если я его не использую?". Наличие автоконфигурции не значит, что бин будет создан. Автоконфигурационные классы активно используют @ConditionalOnClass аннотации, и в большинстве случаев конфигурация ничего делать и создавать не будет (см. выше "Условия и порядок регистрации бинов").

Краткое резюме


В основе "магии" Spring Boot нет ничего магического, он использует совершенно базовые понятия из Spring Framework. В кратком виде процесс можно описать так:


  1. Аннотация @SpringBootApplication включает сканирование компонентов и авто-конфигурацию через аннотацию @EnableAutoConfiguration
  2. @EnableAutoConfiguration импортирует класс EnableAutoConfigurationImportSelector
  3. EnableAutoConfigurationImportSelector загружает список конфигураций из файла META-INF/spring.factories
  4. Каждая конфигурация пытается сконфигурить различные аспекты приложения (web, JPA, AMQP etc), регистрируя нужные бины и используя различные условия (наличие / отсутствие бина, настройки, класса и т.п.)
  5. Созданный в итоге AnnotationConfigEmbeddedWebApplicationContext ищет в том же DI контейнере фабрику для запуска embedded servlet container
  6. Servlet container запускается, приложение готово к работе!

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


Диагностика


Auto-configuration report


В случае, когда что-то идет не так, Spring Boot позволяет запустить диагностику автоконфигурации и посмотреть, какие именно бины были созданы. Чтобы увидеть эту информацию, нужно запустить приложение с ключом --debug.


java -jar my-app.jar --debug

В ответ Spring выдаст детальный Auto-configuration report:


=========================
AUTO-CONFIGURATION REPORT
=========================

Positive matches:
-----------------

DataSourceAutoConfiguration matched:
  - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

DataSourceAutoConfiguration#dataSourceInitializer matched:
  - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer; SearchStrategy: all) did not find any beans (OnBeanCondition)

...

Negative matches:
-----------------

ActiveMQAutoConfiguration:
  Did not match:
     - @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition)
...

Строчка в Positive / Negative matches будет для каждой примененной автоконфигурации, более того, Boot сообщит, почему тот или иной бин был создан (т.е. укажет, какие из условий регистрации были выполнены).


Actuator


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


После добавления Actuator к проекту, Spring Boot опубликует список доступных бинов через URL http://localhost:8080/beans. Этот список так же доступен через JMX (Java Management Extensions), и последняя версия Intellij IDEA умеет показывать все бины приложения прямо из окна запуска.


Резюме


Spring все же остается большим и не самым простым фреймворком, но это цена высокоуровневых абстракций, которые он предоставляет. И хотя знать все тонкости работы фреймворка в ежедневной разработке не нужно, знать, как он работает изнутри, все же, полезно. Надеюсь, что эта статья помогла понять важность и ценность Spring именно как экосистемы и убрала немного "магичности" в происходящем, особенно при использовании Spring Boot. Мой совет — не бойтесь углубляться в недра фреймворка, читайте исходники и документацию, благо они у Spring-a почти эталонные, на мой взгляд.


Так же стоит отметить, что в готовящемся к выходу в сентябре Spring 5 появится несколько новых концепций, направленных на создание простых приложений, и понижение уровня "магии" (хотя, как мы выяснили, магии там особо и нет). Одна из концепций это Functional Bean Registration, которая позволяет регистрировать бины в контексте с помощью функций, или даже с помощью неплохого DSL на Kotlin (а Spring 5 добавит много хорошего для поддержки Kotlin). Следующая, но еще более важная вещь, это комбинация Functional Web Framework и WebFlux (reactive web framework), которая позволит создавать веб-приложения вообще без зависимости на Spring MVC и запускать их без сервлет контейнеров. Приложение вполне сможет работать без контекста приложений и DI, и описываться просто как набор функций request -> response. Об этом можно чуть больше почитать здесь (на английском).

Поделиться с друзьями
-->

Комментарии (22)


  1. sshikov
    30.07.2017 20:56

    После добавления Actuator к проекту, Spring Boot опубликует список доступных бинов

    Ну, на самом деле публиковать бины в JMX можно было давно, еще до появления Boot. И достаточно легко. И потом какая-нибудь консоль типа hawtio позволяет их удобно смотреть и рулить ими.


    Ну т.е. опять же, в этом и в метриках нет никакой магии — это просто сконфигурированные неким стандартным образом и доступные отдельно вещи (типа metrics.dropwizard.io).


  1. Hixon10
    30.07.2017 22:28
    +5

    Про Спринг Бут недавно Женя Борисов доклад сделал. Вроде бы этот же доклад будет и на следующем Джокере:

    https://www.youtube.com/watch?v=8xa0RWMwAOE

    https://jokerconf.com/2017/talks/2epnldoaiuqc0iwecmq4ms/


  1. Bonart
    31.07.2017 08:42

    То есть Spring предлагает выбор из программирования на xml и программирования на аннотациях.
    А программировать точку сборки на Java нельзя?
    Одна реализация на интерфейс в контексте в общем случае мало. Есть ли способ иметь много реализаций на интерфейс?
    Можно ли делать локальные контексты?


    1. Borz
      31.07.2017 08:43
      +2

      судя по вопросам вы знаете ответы и то, что они положительные


    1. alek_sys
      31.07.2017 10:10

      Spring дает несколько способов сконфигурить контекст, через XML или аннотации. Их даже можно смешивать, в общем-то, хотя не очень приветствуется. Не уверен, что понял вопрос про точку сборки. Имеется в виду, можно ли на Java описать "используй этот класс для регистрации этого бина"? Можно, это и делают классы помеченные @Configuration с помощью методов, помеченных @Bean.


      Конечно, реализаций может быть сколько угодно.


      Думаю, здесь нужно пояснить что вы имеете в виду под "локальным" контекстом. Иметь "локальный" контейнер DI, в рамках пакета, например? Насколько я знаю — нет, контейнер общий.


      1. olegchir
        31.07.2017 12:23

        А что мешает зарегистрировать вручную сколько угодно контейнеров?


      1. Bonart
        31.07.2017 14:53

        В современных контейнерах основным способом конфигурации является код, а не аннотации или xml.
        Это логично — нет никаких оснований писать точку сборки на специальном языке, теряя тьюринг-полноту, поддержку от ide, сопровождаемость и повышая порог вхождения.
        Тем более, что имея хорошее API для кода, сделать поддержку любого другого способа конфигурирования не проблема.


        Spring бросит исключение, что есть два бина, которые удовлетворяют запросу.

        Конечно, реализаций может быть сколько угодно.

        И как это сочетается без противоречий?


        Думаю, здесь нужно пояснить что вы имеете в виду под "локальным" контекстом

        Возможность иметь точки сборки, время жизни которых меньше времени работы приложения. Например для окна с редактором или для обработки конкретного запроса.


        PS: Точка сборки, она же Composition Root — объект, отвечающий за композицию компонентов в том или ином контексте. Обычно при ее реализации используется DI-контейнер.
        Если я правильно понимаю, Spring реализует точку сборки сам, не давая это сделать программисту.


        1. AstarothAst
          31.07.2017 15:51
          +1

          И как это сочетается без противоречий?

          Аннотация Qualifier спасет отца русской демократии!


        1. grossws
          31.07.2017 15:56
          +1

          В случае спринга никто не мешает создавать новый контекст, когда понадобилось. Так что вы можете иметь столько composition roots, сколько надо.


          1. Bonart
            31.07.2017 16:16

            А как эти новые точки сборки конфигурировать?
            Аннотации с xml тут не слишком удобны.


            1. Borz
              31.07.2017 16:36

              Примеры:
              0) создать некий класс-контейнер бинов, или загрузить отдельный xml с бинами
              1) при создании новой точки указать какие пакеты просканировать
              2) при создании новой точки накидать в неё классы-бины


            1. grossws
              31.07.2017 17:13
              +1

              Я для таких вещей предпочитаю новый @Configuration-класс, а дальше AnnotationConfigApplicationContext, причём лучше явно указывать @ComponentScan с отдельный package, чтобы не притащить лишнего.


              CDI 1.x в этом плане куда менее приятен в обращении.


              Хотя для небольших вещей я последнее время предпочитаю использовать гуглёвый guice.


        1. hippoage
          04.08.2017 09:39

          > И как это сочетается без противоречий?

          Есть 2 места уточнения реализации: в определении и в месте вставки. Самое простое — в одном из определений прописать Default. Тогда в месте вставки не будет неопределенности какой бин использовать.

          Так же можно создать собственную аннотацию, использовать ее в определении и в месте вставки. Тогда могут существовать и другие реализации того же интерфейса/класса, но они не будут использованы.

          Еще в месте определения можно добавлять условия на используемый профиль Спринга или любое конфигурационное свойство. Таким образом, можно во время запуска можно подключать одну реализацию из нескольких.

          > Возможность иметь точки сборки, время жизни которых меньше времени работы приложения.

          Обычно смотрят на это немного по другому: точка сборки та же, но вот время жизни компонента выставляют через соотвествующий scope. В принципе, получается то же самое. Есть стандартные scope для веб-приложений, можно делать собственные.

          > Если я правильно понимаю, Spring реализует точку сборки сам, не давая это сделать программисту.

          Основной способ работы со Спрингом сейчас — через аннотации. Либо Component (и специализации), либо сочетание @Configuration/@Bean. Плюс, есть возможность через xml прописать. Я практически уверен, что можно подменить стандартный класс, который это делает, только не вижу особой причины.


  1. AndreyRubankov
    31.07.2017 10:07
    +2

    Так вот ценность DI тут в том, что это просто механизм общения между нашим кодом и Spring Security. И простой заменой реализации DI на Guice не добиться, чтобы Spring Security тоже начал его использовать. А если в этом новом DI не будет интеграции со всеми библиотеками Spring-а, то и ценность его сильно падает.
    Когда говорится про то, чтобы заменить Spring DI на тот же Guice обычно предполагается, что Spring полностью уйдет из проекта. Spring Security – это отличная штука, но часто она бывает избыточной. Особенно с появлением JWT и его популяризацией. Далеко не во многих проектах необходима система ролей и ограничение доступов прям на методах, часто достаточно просто сделать фильтрацию для URI.

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

    Более того, создание интерфейса, если у него лишь одна реализация — считается плохой практикой.
    Depends on… Если класс используется для внутренних целей – да, это плохая практика, но тут и DI становится не нужным. Но если он нужен для связывания отдельных модулей, то даже с единственной реализацией – это будет хорошая практика.

    ps: Spring – это хороший фреймворк, но многие его возводят в «абсолют» и приравнивают Java == Spring, что в корне не верно. Да, на нем можно быстро собрать «шаблонное» приложение и оно будет работать вполне вменяемо по ресурсам и по производительности. Но есть задачи, которые требуют меньшего расхода ресурсов и большей производительности на выходе, в этом случае Spring не стоит использовать.


    1. alek_sys
      31.07.2017 10:24
      +3

      А, ок, в контексте проекта вообще без Spring замена DI контейнера имеет смысл.


      Spring Security это одна из самых непростых и запутанных (может, из-за попытки сделать супер-абстракцию над такой специфической областью, как безопасность?) библиотек. Но здесь она лишь как пример, любая библиотека из экосистемы Spring будет работать через DI, это мысль, что я хотел донести.


      Про использование классов vs создание интерфейсов. Идея проста до безобразия — если есть лишь один класс-реализация, который должен управляться DI — для него нет смысла создавать интерфейс, а потом регистрировать бин "для интерфейса А используй класс Б". Его можно инжектить самого по себе. Более того, это даже не будет мешать его замокать в тестах. Т.е. не нужно использовать интерфейсы если того не требует бизнес-логика. Я предупреждал, что мысль примитивная :) Просто иногда можно увидеть огромные файлы бутстраппинга DI которые настраивают 1-1 маппинги из интерфейса на класс, я хотел сказать, что этого можно избежать.


      Насчет абсолюта — я не верю в абсолюты вообще. И не думаю, что Spring это one size fits all. Просто хотелось развеять ощущение "магии" фреймворка и показать, что ничего особо магического и сложного там нет. Просто обычный, не самый плохой и достаточно современный фреймворк для разработки на JVM.


      1. shapovalex
        31.07.2017 13:15

        Насчет подхода — у нас же одна реализация, зачем нам интерфейс?
        Я много раз видел как таким образом люди стреляли себе в ногу. Причем вообще не понимая что происходит.

        Приведу пример — в сервисе стоит Transactional, сервис сам по себе — все работает отлично. А потом он начинает имплементить какой-то интерфейс. Вообще левый. И все падает, контекст не находит реализацию.
        Почему так просходит — можете посмотреть доклады Жене Борисова.

        Спринг сложный фреймворк и когда с ним начинает работать команда — надо всю команду научить работать с ним и понимать что происходит под капотом. Без фанатизма, просто базовым вещам — когда работают инъекции, когда накручиваются прокси и все такое. Разработчик понимающий эти вещи уже не будет делать обиднейшие ошибки.


        1. zlob
          31.07.2017 20:51

          У того же Жени есть история про мальчика который не любил интерфейсы и там без него не работало, т.ч. зависит от обстоятельств.


  1. Throwable
    31.07.2017 10:23

    На мой взгляд основная проблема спринга, превращающая его из фреймворка в книгу заклинаний, состоит в повсеместном использовании аннотаций. Аннотации в java были придуманы для определения метаданных в коде. Но строить на их основе некий расширяемый метаязык для определения контекста приложения — это вкорне неверная идея, аннотации для этого не приспособлены. Расставленные в коде аннотации сематнически никем не контролируются, кроме рантайма: @EnableWebMvc я могу вообще поставить на любой объект, не обязательно на @Configuration — никто меня не ограничит.
    Есть гораздо более удобные, правильные и открытые способы задания конфигурации контекста без аннотаций, например Guice.
    Спринг же пытается всю задачу замести под ковер, оставляя пользователю лишь набор заклинаний и рецептов для описания бизнес логики, которые нужно использовать в определенном порядке, иначе просто не взлетит. Это очень эффектно для демонстраций и презентаций, но в реальных проектах при попытке заглянуть под ковер и что-то там изменить всегда сопряжена с головной болью и геморроем.


  1. Scf
    31.07.2017 21:14
    +1

    Спасибо за адекватную статью. Попробую объяснить, почему я когда-то ушел со спринга и добровольно не вернусь.

    — конфигурация что xml, что аннотациями реализуется приличным количеством кода. Таким образом, нет простого способа понять, какие аннотации куда можно вешать и как оно работает. Документация — это хорошо, но она не покрывает все случаи, а вручную курить несколько тысяч строк кода разнообразных бинов и расширений спринга — накладно. Конфигурация через банальные билдеры значительно удобнее — автокомплит, проверка на этапе комппиляции, простая реализация.

    — инициализация spring-beans по дизайну однопоточна. Поэтому большие приложения на спринге стартуют существенное количество времени и ускорить процесс непросто. На дворе 21 век — если приложение в дев-режиме стартует больше трех секунд, то это негодное приложение.

    — в спринге «умеем всё из коробки» имеет неприглядную обратную сторону — длиннющие стектрейсы в spring mvc и очень большие проблемы при желании сделать что-то нестандартное. Приходится лезть в конфигурационный код (да-да, тот самый, который обрабатывает аннотации и читает красивый XML) и надеяться, что у нужного бина разработчики нужный метод сделали protected, а не private.


    1. Borz
      31.07.2017 23:51

      а можно пример "нестандартного" для которого приходилось лезть и переобределять дебри?


      1. Scf
        01.08.2017 08:23

        Уже не скажу, давно это было. Но в исходниках спринга я провел времени немало.


        1. furic
          02.08.2017 23:02
          +1

          Уверен что можно было избежать курения кода почитав документацию… Но конечно курение и чтение друг друга дополняют. За всё время дружбы со Спрингом не было ничего что бы я не смог переопределить.